Easylogging++
高性能で拡張可能な軽量C++ロギングライブラリ。カスタマイズ可能なフォーマット、クラス・サードパーティライブラリ・STLコンテナのロギングサポート、スレッドセーフ設計を提供。使いやすさと機能性のバランスが特徴。
ライブラリ
Easylogging++
概要
Easylogging++は「高性能で拡張可能な軽量C++ロギングライブラリ」として開発された、使いやすさと機能性のバランスが特徴のツールです。「カスタマイズ可能なフォーマット、クラス・サードパーティライブラリ・STLコンテナのロギングサポート、スレッドセーフ設計」をコンセプトに、ヘッダーオンリー設計による導入の容易さと豊富な機能セットを提供。C++プロジェクトでの包括的なログ管理を単一ヘッダーファイルで実現し、設定の柔軟性とパフォーマンスの両立により中規模から大規模アプリケーションまで幅広く対応します。
詳細
Easylogging++ 2025年版は中規模プロジェクトでの採用が安定した、バランス型C++ロギングライブラリとして確固たる地位を維持しています。単一ヘッダーファイル(easylogging++.h)による簡単導入、設定ファイルやプログラマティック設定による柔軟なカスタマイズ、マルチスレッド対応、ローテーション・日次ログファイル、カラーコンソール出力、パフォーマンス測定機能など包括的な機能を提供。spdlogほどの超高速性はないものの、設定の容易さと機能の豊富さにより、学習コストを抑えながら本格的なログ管理を実現できる選択肢として評価されています。
主な特徴
- ヘッダーオンリー設計: 単一ヘッダーファイルによる簡単導入
- 豊富な設定オプション: ファイル設定とプログラマティック設定両対応
- スレッドセーフ: マルチスレッド環境での安全な動作
- パフォーマンス測定: TIMED_FUNCとTIMED_SCOPEマクロによる実行時間計測
- カスタムクラス対応: 独自クラスのログ出力サポート
- クラッシュハンドリング: 例外発生時の自動ログ出力
メリット・デメリット
メリット
- ヘッダーオンリー設計による導入とビルドの簡素化
- 豊富な設定オプションによる詳細なカスタマイズ性
- 包括的な機能セットで中規模プロジェクトに最適
- 設定ファイルによるランタイム設定変更が可能
- STLコンテナや独自クラスの直接ログ出力サポート
- 学習コストが低く、ドキュメントが充実
デメリット
- spdlogと比較してパフォーマンスが劣る
- 大規模・高負荷システムでは性能面で制約
- 設定の複雑さが増すとメンテナンスが困難
- 最新のC++機能への対応が他ライブラリより遅い
- ヘッダーオンリーのためコンパイル時間が増加する可能性
- モダンなC++プロジェクトでは他の選択肢が優先される傾向
参考ページ
書き方の例
インストールと基本セットアップ
# Gitからソースをダウンロード
git clone https://github.com/abumq/easyloggingpp.git
# ヘッダーファイルのみコピー
cp easyloggingpp/src/easylogging++.h ./include/
cp easyloggingpp/src/easylogging++.cc ./src/
# vcpkgを使用した場合
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install easyloggingpp
# Conanを使用した場合
# conanfile.txtに追加
echo "[requires]" > conanfile.txt
echo "easyloggingpp/9.97.1" >> conanfile.txt
# CMakeでのビルド
mkdir build && cd build
cmake -Dtest=ON ../
make
make install
基本的なログ出力
#include "easylogging++.h"
// 必須の初期化マクロ(メインファイルで一度だけ)
INITIALIZE_EASYLOGGINGPP
int main(int argc, char* argv[]) {
// 起動時引数対応の初期化
START_EASYLOGGINGPP(argc, argv);
// 基本的なログ出力
LOG(INFO) << "アプリケーション開始";
LOG(DEBUG) << "デバッグ情報: 設定読み込み完了";
LOG(WARNING) << "警告: 設定ファイルが見つかりません";
LOG(ERROR) << "エラー: データベース接続失敗";
// データの出力
int userId = 123;
std::string userName = "田中太郎";
LOG(INFO) << "ユーザー処理: ID=" << userId << ", 名前=" << userName;
// STLコンテナの出力
std::vector<int> numbers = {1, 2, 3, 4, 5};
LOG(INFO) << "数値配列: " << numbers;
std::map<std::string, int> scores = {
{"数学", 85}, {"英語", 92}, {"国語", 78}
};
LOG(INFO) << "成績: " << scores;
return 0;
}
// コンパイル例
// g++ main.cpp easylogging++.cc -o app -std=c++11 -DELPP_THREAD_SAFE
設定ファイルによるカスタマイズ
// logging.conf
/*
* GLOBAL:
FORMAT = "%datetime %level %msg"
FILENAME = "/tmp/logs/myapp.log"
ENABLED = true
TO_FILE = true
TO_STANDARD_OUTPUT = true
SUBSECOND_PRECISION = 6
PERFORMANCE_TRACKING = true
MAX_LOG_FILE_SIZE = 2097152 ## 2MB
LOG_FLUSH_THRESHOLD = 100 ## 100ログごとにフラッシュ
* DEBUG:
FORMAT = "%datetime{%d/%M} %func %msg"
FILENAME = "/tmp/logs/debug.log"
* ERROR:
FORMAT = "%datetime %level [%loc] %msg"
FILENAME = "/tmp/logs/error.log"
*/
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
int main(void) {
// 設定ファイルからの読み込み
el::Configurations conf("/path/to/logging.conf");
// デフォルトロガーの再設定
el::Loggers::reconfigureLogger("default", conf);
// または全ロガーの再設定
el::Loggers::reconfigureAllLoggers(conf);
// グローバル設定ファイルの使用
el::Loggers::configureFromGlobal("global.conf");
LOG(INFO) << "設定ファイルからの設定完了";
LOG(DEBUG) << "デバッグログはdebug.logに出力";
LOG(ERROR) << "エラーログはerror.logに出力";
return 0;
}
プログラマティック設定とカスタマイズ
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
int main(int argc, const char** argv) {
// プログラマティック設定
el::Configurations defaultConf;
defaultConf.setToDefault();
// 基本設定
defaultConf.set(el::Level::Info,
el::ConfigurationType::Format, "%datetime %level %msg");
defaultConf.set(el::Level::Info,
el::ConfigurationType::Filename, "./logs/info.log");
defaultConf.set(el::Level::Info,
el::ConfigurationType::Enabled, "true");
// グローバル設定
defaultConf.setGlobally(
el::ConfigurationType::Format, "%datetime [%level] %msg");
defaultConf.setGlobally(
el::ConfigurationType::ToFile, "true");
defaultConf.setGlobally(
el::ConfigurationType::ToStandardOutput, "true");
// 設定の適用
el::Loggers::reconfigureLogger("default", defaultConf);
// 新しいロガーの作成と設定
el::Logger* businessLogger = el::Loggers::getLogger("business");
el::Configurations businessConf;
businessConf.setToDefault();
businessConf.set(el::Level::Info,
el::ConfigurationType::Format, "%datetime [BUSINESS] %msg");
businessConf.set(el::Level::Info,
el::ConfigurationType::Filename, "./logs/business.log");
el::Loggers::reconfigureLogger("business", businessConf);
// 使用例
LOG(INFO) << "デフォルトロガーのログ";
CLOG(INFO, "business") << "ビジネスロガーのログ";
// インライン設定解析
el::Configurations c;
c.setToDefault();
c.parseFromText("*GLOBAL:\n FORMAT = %level %msg");
el::Loggers::reconfigureLogger("default", c);
return 0;
}
条件付きログとパフォーマンス機能
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
// 重い処理の例
void performHeavyTask(int iter) {
// 関数全体の実行時間測定
TIMED_FUNC(timerObj);
LOG(INFO) << "重い処理を開始: " << iter << "回繰り返し";
// 初期化処理
usleep(5000);
while (iter-- > 0) {
// ブロック単位の実行時間測定
TIMED_SCOPE(timerBlkObj, "heavy-iter");
// 条件付きログ
LOG_IF(iter % 10 == 0, INFO) << "進捗: 残り" << iter << "回";
// N回に1回のログ
LOG_EVERY_N(5, DEBUG) << "5回ごとのデバッグログ";
// 最初のN回だけのログ
LOG_N_TIMES(3, INFO) << "最初の3回のみ: " << iter;
// N回後のログ
LOG_AFTER_N(10, WARNING) << "10回経過後の警告";
// 重い処理のシミュレーション
usleep(iter * 1000);
// パフォーマンスチェックポイント
if (iter % 3 == 0) {
PERFORMANCE_CHECKPOINT(timerBlkObj);
}
}
LOG(INFO) << "重い処理完了";
}
int main() {
// パフォーマンストラッキング有効化
el::Loggers::addFlag(el::LoggingFlag::PerformanceTracking);
LOG(INFO) << "パフォーマンステスト開始";
performHeavyTask(20);
// 複数ロガーでの同時出力
CLOG(INFO, "default", "performance") << "複数ロガーでの出力";
return 0;
}
カスタムクラスとフォーマッタ
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
// el::Loggableを継承したカスタムクラス
class User : public el::Loggable {
public:
User(int id, const std::string& name, int age)
: m_id(id), m_name(name), m_age(age) {}
// ログ出力メソッドの実装
virtual void log(el::base::type::ostream_t& os) const {
os << "User{id=" << m_id << ", name='" << m_name
<< "', age=" << m_age << "}";
}
private:
int m_id;
std::string m_name;
int m_age;
};
// MAKE_LOGGABLEマクロを使用したクラス
class Product {
public:
Product(int id, const std::string& name, double price)
: m_id(id), m_name(name), m_price(price) {}
int getId() const { return m_id; }
const std::string& getName() const { return m_name; }
double getPrice() const { return m_price; }
private:
int m_id;
std::string m_name;
double m_price;
};
// カスタムフォーマッタの実装
inline MAKE_LOGGABLE(Product, product, os) {
os << "Product{id=" << product.getId()
<< ", name='" << product.getName()
<< "', price=" << product.getPrice() << "}";
return os;
}
// 時間型のカスタムフォーマッタ
inline MAKE_LOGGABLE(std::chrono::system_clock::time_point, when, os) {
time_t t = std::chrono::system_clock::to_time_t(when);
auto tm = std::localtime(&t);
char buf[1024];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S (%Z)", tm);
os << buf;
return os;
}
// カスタムフォーマット指定子
const char* getClientIP(const el::LogMessage*) {
return "192.168.1.100"; // 実際はリクエストから取得
}
int main(void) {
// カスタムフォーマット指定子のインストール
el::Helpers::installCustomFormatSpecifier(
el::CustomFormatSpecifier("%client_ip", getClientIP));
// フォーマットの再設定
el::Loggers::reconfigureAllLoggers(
el::ConfigurationType::Format,
"%datetime %level %client_ip : %msg");
// カスタムクラスのログ出力
User user(1, "田中太郎", 30);
LOG(INFO) << "ユーザー情報: " << user;
Product product(101, "ノートPC", 89800.0);
LOG(INFO) << "商品情報: " << product;
// 時間型のログ出力
auto now = std::chrono::system_clock::now();
LOG(INFO) << "現在時刻: " << now;
// STLコンテナのネストしたログ
std::vector<Product> products = {
Product(101, "ノートPC", 89800.0),
Product(102, "マウス", 2980.0),
Product(103, "キーボード", 5480.0)
};
LOG(INFO) << "商品リスト: " << products;
return 0;
}
エラーハンドリングとクラッシュ対応
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
// カスタムクラッシュハンドラ
void myCrashHandler(int sig) {
LOG(FATAL) << "アプリケーションクラッシュ! シグナル: " << sig;
LOG(FATAL) << "スタックトレース情報をログに記録";
// 必須: アプリケーション終了
el::Helpers::crashAbort(sig);
}
// カスタム設定バリデーション
bool validateLogConfiguration(const el::Configurations& conf) {
// ログファイルパスの検証
std::string filename = conf.get(el::Level::Info, el::ConfigurationType::Filename)->value();
if (filename.empty()) {
LOG(ERROR) << "ログファイル名が設定されていません";
return false;
}
// ディレクトリの存在確認
std::string logDir = filename.substr(0, filename.find_last_of("/"));
struct stat info;
if (stat(logDir.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) {
LOG(ERROR) << "ログディレクトリが存在しません: " << logDir;
return false;
}
return true;
}
class SafeLogger {
public:
SafeLogger() {
try {
// 安全な設定読み込み
el::Configurations conf;
conf.setToDefault();
// 基本設定
conf.setGlobally(el::ConfigurationType::Format,
"%datetime{%Y-%M-%d %H:%m:%s.%g} [%level] %msg");
conf.setGlobally(el::ConfigurationType::ToFile, "true");
conf.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
conf.setGlobally(el::ConfigurationType::MaxLogFileSize, "5242880"); // 5MB
if (validateLogConfiguration(conf)) {
el::Loggers::reconfigureAllLoggers(conf);
LOG(INFO) << "ロガー設定完了";
} else {
// フォールバック設定
el::Configurations fallback;
fallback.setToDefault();
fallback.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
fallback.setGlobally(el::ConfigurationType::ToFile, "false");
el::Loggers::reconfigureAllLoggers(fallback);
LOG(WARNING) << "フォールバック設定を使用";
}
} catch (const std::exception& e) {
std::cerr << "ロガー初期化エラー: " << e.what() << std::endl;
}
}
template<typename T>
void safeLog(el::Level level, const T& message) {
try {
switch (level) {
case el::Level::Info:
LOG(INFO) << message;
break;
case el::Level::Debug:
LOG(DEBUG) << message;
break;
case el::Level::Warning:
LOG(WARNING) << message;
break;
case el::Level::Error:
LOG(ERROR) << message;
break;
case el::Level::Fatal:
LOG(FATAL) << message;
break;
default:
LOG(INFO) << message;
}
} catch (...) {
std::cerr << "ログ出力エラー" << std::endl;
}
}
};
int main(void) {
// クラッシュハンドラの設定
el::Helpers::setCrashHandler(myCrashHandler);
SafeLogger logger;
logger.safeLog(el::Level::Info, "安全なロガーのテスト開始");
try {
// 例外が発生する可能性のある処理
throw std::runtime_error("テスト例外");
} catch (const std::exception& e) {
logger.safeLog(el::Level::Error, std::string("例外キャッチ: ") + e.what());
}
// 意図的なクラッシュのテスト(実際の使用では削除)
// int* p = nullptr;
// *p = 42; // セグメンテーション違反
logger.safeLog(el::Level::Info, "プログラム正常終了");
return 0;
}
マルチスレッド環境での使用
#include "easylogging++.h"
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
INITIALIZE_EASYLOGGINGPP
class ThreadSafeCounter {
private:
int count = 0;
std::mutex mtx;
public:
void increment(int threadId) {
std::lock_guard<std::mutex> lock(mtx);
count++;
// スレッドセーフなログ出力
LOG(INFO) << "スレッド " << threadId << ": カウント = " << count;
}
int getCount() const {
std::lock_guard<std::mutex> lock(mtx);
return count;
}
};
void workerFunction(int threadId, ThreadSafeCounter& counter) {
// スレッド固有のロガー
el::Logger* threadLogger = el::Loggers::getLogger("thread_" + std::to_string(threadId));
CLOG(INFO, ("thread_" + std::to_string(threadId)).c_str())
<< "ワーカースレッド開始: ID=" << threadId;
for (int i = 0; i < 10; ++i) {
counter.increment(threadId);
// 処理時間のシミュレーション
std::this_thread::sleep_for(std::chrono::milliseconds(100));
CLOG(DEBUG, ("thread_" + std::to_string(threadId)).c_str())
<< "処理 " << i + 1 << "/10 完了";
}
CLOG(INFO, ("thread_" + std::to_string(threadId)).c_str())
<< "ワーカースレッド終了: ID=" << threadId;
}
int main() {
// スレッドセーフフラグを有効化
el::Loggers::addFlag(el::LoggingFlag::MultiLoggerSupport);
// 各スレッド用ロガーの設定
for (int i = 0; i < 5; ++i) {
std::string loggerId = "thread_" + std::to_string(i);
el::Logger* logger = el::Loggers::getLogger(loggerId);
el::Configurations conf;
conf.setToDefault();
conf.set(el::Level::Global, el::ConfigurationType::Format,
"%datetime [THREAD-" + std::to_string(i) + "] %level %msg");
conf.set(el::Level::Global, el::ConfigurationType::Filename,
"./logs/thread_" + std::to_string(i) + ".log");
el::Loggers::reconfigureLogger(loggerId, conf);
}
LOG(INFO) << "マルチスレッドテスト開始";
ThreadSafeCounter counter;
std::vector<std::thread> threads;
// ワーカースレッド作成
for (int i = 0; i < 5; ++i) {
threads.emplace_back(workerFunction, i, std::ref(counter));
}
// 全スレッドの完了を待機
for (auto& thread : threads) {
thread.join();
}
LOG(INFO) << "全スレッド完了. 最終カウント: " << counter.getCount();
return 0;
}
// コンパイル例(スレッドセーフ有効)
// g++ main.cpp easylogging++.cc -o app -std=c++11 -pthread -DELPP_THREAD_SAFE