G3log

非同期でクラッシュセーフなC++ロギングライブラリ。高いパフォーマンスとシステム安定性を重視した設計。クラッシュ時でもログの損失を防ぐ機能と、非同期処理によるアプリケーションへの影響最小化を特徴とする。

ロギングC++非同期クラッシュセーフパフォーマンス

ライブラリ

g3log

概要

g3logは「非同期でクラッシュセーフなロガー」として設計されたC++ログライブラリです。C++14(C++11サポートは1.3.2リリースまで)で実装され、外部ライブラリに依存せず、OSX、Windows、Linux等の複数プラットフォームで動作します。デフォルトのログシンクに加えてカスタムシンクの追加が可能で、高性能な非同期ログ処理とクラッシュ時のスタックダンプ機能を提供し、企業レベルのアプリケーションでの使用に適した堅牢性を備えています。

詳細

g3logは高性能かつ安全性を重視したC++ログライブラリとして、2025年現在でも活発に開発が続けられています。最大の特徴は真の非同期処理による高いパフォーマンスと、アプリケーションクラッシュ時でもログが確実に書き込まれるクラッシュセーフ機能です。LogWorkerによる専用スレッドでのログ処理、カスタムシンクによる柔軟な出力先制御、ストリーミング記法とprintf記法の両対応、デザインバイコントラクトのCHECK機能、致命的シグナル時の自動スタックダンプなど、現代的なC++開発に必要な機能を包括的に提供します。

主な特徴

  • 非同期ログ処理: LogWorkerによる専用スレッドでの高性能ログ処理
  • クラッシュセーフ: アプリケーション異常終了時でもログデータが確実に書き込まれる
  • カスタムシンク対応: ファイル、コンソール、ネットワーク等への柔軟な出力制御
  • 複数記法サポート: ストリーミング(LOG)とprintf形式(LOGF)の両対応
  • 条件ログ: LOG_IF、LOG_EVERY_N等の効率的な条件付きログ機能
  • デザインバイコントラクト: CHECK、CHECK_F等のアサーション機能

メリット・デメリット

メリット

  • 真の非同期処理によりメインスレッドへの性能影響を最小化
  • クラッシュセーフ機能により重要なデバッグ情報を確実に保持
  • 外部依存なしで導入が容易、CMakeによる標準的なビルドシステム
  • カスタムシンクによる柔軟な出力先制御と拡張性
  • 致命的エラー時の自動スタックダンプでデバッグ効率向上
  • 豊富な条件ログ機能による効率的なログ制御

デメリット

  • C++14以降が必要で古いコンパイラ環境では使用不可
  • 非同期処理によりログ出力タイミングが実行タイミングと異なる場合がある
  • 学習コストがあり、特にカスタムシンク実装時は理解が必要
  • Java、Python等の他言語エコシステムとの統合は別途対応が必要
  • ログフォーマットのカスタマイズは比較的限定的
  • 非同期処理のため、即座にログを確認したい場合は注意が必要

参考ページ

書き方の例

基本セットアップ

#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>
#include <memory>

int main(int argc, char* argv[]) {
    // LogWorkerを作成(非同期ログ処理の管理)
    auto worker = g3::LogWorker::createLogWorker();
    
    // デフォルトファイルロガーを追加
    const std::string directory = "./logs/";
    const std::string name = "app_log";
    auto handle = worker->addDefaultLogger(name, directory);
    
    // ログシステムを初期化
    g3::initializeLogging(worker.get());
    
    // プログラム終了時にログを確実にフラッシュ
    // worker のデストラクタで自動的に g3::internal::shutDownLogging() が呼ばれる
    
    return 0;
}

基本的なログ出力

#include <g3log/g3log.hpp>

void basicLogging() {
    // ストリーミング記法でのログ出力
    LOG(INFO) << "アプリケーションが開始されました";
    LOG(WARNING) << "警告メッセージ: 設定値 = " << config_value;
    LOG(FATAL) << "致命的エラーが発生しました"; // プロセス終了
    
    // printf形式でのログ出力
    LOGF(INFO, "ユーザー %s がログインしました(ID: %d)", username.c_str(), user_id);
    LOGF(WARNING, "メモリ使用量: %.2f MB", memory_usage_mb);
    LOGF(DEBUG, "デバッグ情報: ファイル=%s, 行=%d", __FILE__, __LINE__);
    
    // 条件付きログ
    LOG_IF(INFO, user_count > 100) << "多数のユーザーが接続中: " << user_count;
    LOGF_IF(WARNING, memory_usage > 0.8, "メモリ使用率が高くなっています: %.1f%%", memory_usage * 100);
    
    // 一定回数ごとのログ
    LOG_EVERY_N(INFO, 10) << "10回に1回のログメッセージ: " << google::COUNTER << "回目";
    
    // 最初のN回だけのログ
    LOG_FIRST_N(INFO, 5) << "最初の5回だけのログ: " << google::COUNTER << "回目";
}

高度な設定(カスタムシンクとログフォーマット)

#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>
#include <iostream>
#include <fstream>

// カスタムシンクの実装例(コンソール出力)
struct ColorConsoleSink {
    enum Color { RED = 31, GREEN = 32, YELLOW = 33, WHITE = 97 };
    
    Color getColor(const LEVELS& level) const {
        if (level.value == WARNING.value) return YELLOW;
        if (level.value == DEBUG.value) return GREEN;
        if (g3::internal::wasFatal(level)) return RED;
        return WHITE;
    }
    
    void ReceiveLogMessage(g3::LogMessageMover logEntry) {
        auto level = logEntry.get()._level;
        auto color = getColor(level);
        
        std::cout << "\033[" << color << "m"
                  << logEntry.get().toString() 
                  << "\033[0m" << std::endl;
    }
};

// ファイル出力用カスタムシンク
class CustomFileSink {
private:
    std::ofstream file_;
    
public:
    CustomFileSink(const std::string& filename) : file_(filename, std::ios::app) {}
    
    void ReceiveLogMessage(g3::LogMessageMover logEntry) {
        if (file_.is_open()) {
            // カスタムフォーマット関数を使用
            file_ << logEntry.get().toString(&CustomFormatting) << std::endl;
            file_.flush(); // 即座に書き込み
        }
    }
    
    // カスタムフォーマット関数
    static std::string CustomFormatting(const g3::LogMessage& msg) {
        std::ostringstream oss;
        oss << "[" << msg.timestamp("%Y-%m-%d %H:%M:%S") << "] "
            << "[" << msg.level() << "] "
            << "[Thread:" << msg.threadID() << "] "
            << msg.file() << ":" << msg.line() << " - "
            << msg.message();
        return oss.str();
    }
};

int main() {
    auto worker = g3::LogWorker::createLogWorker();
    
    // デフォルトファイルロガー
    auto fileHandle = worker->addDefaultLogger("app", "./logs/");
    
    // カスタムコンソールシンク追加
    auto consoleHandle = worker->addSink(
        std::make_unique<ColorConsoleSink>(),
        &ColorConsoleSink::ReceiveLogMessage
    );
    
    // カスタムファイルシンク追加
    auto customFileHandle = worker->addSink(
        std::make_unique<CustomFileSink>("./logs/custom.log"),
        &CustomFileSink::ReceiveLogMessage
    );
    
    g3::initializeLogging(worker.get());
    
    // ログレベル別の出力テスト
    LOG(INFO) << "情報メッセージ";
    LOG(WARNING) << "警告メッセージ";
    LOG(DEBUG) << "デバッグメッセージ";
    
    // 非同期でシンクのメソッドを呼び出し
    std::future<void> future = customFileHandle->call(
        &CustomFileSink::SomeCustomMethod, param1, param2
    );
    
    return 0;
}

エラーハンドリングとデザインバイコントラクト

#include <g3log/g3log.hpp>

void errorHandlingExamples() {
    // CHECK系マクロ - 条件が偽の場合プログラム終了
    int* ptr = getPointer();
    CHECK(ptr != nullptr) << "ポインタがnullです";
    CHECK_NOTNULL(ptr); // ptr を返す(nullでない場合)
    
    // 比較CHECK
    int expected = 42;
    int actual = calculateValue();
    CHECK_EQ(expected, actual) << "期待値と実際の値が異なります";
    CHECK_NE(status, ERROR_STATE) << "エラー状態になっています";
    CHECK_LT(memory_usage, MAX_MEMORY) << "メモリ使用量が上限を超えています";
    
    // 文字列比較CHECK
    const char* config_path = getConfigPath();
    CHECK_STREQ(config_path, "/etc/app.conf") << "設定ファイルパスが不正です";
    
    // 非致命的なCHECK(ログのみ、プログラム継続)
    CHECK(validate_data(data)); // falseでもプログラムは継続
    
    // 致命的CHECK(プログラム終了)
    CHECK_F(initialize_system(), "システム初期化に失敗しました");
}

// より実践的なエラーハンドリング例
class DatabaseConnection {
private:
    void* connection_;
    
public:
    DatabaseConnection(const std::string& conn_string) {
        connection_ = connect(conn_string);
        CHECK_NOTNULL(connection_) << "データベース接続に失敗: " << conn_string;
        
        LOG(INFO) << "データベースに接続しました: " << conn_string;
    }
    
    bool executeQuery(const std::string& query) {
        CHECK(!query.empty()) << "クエリが空です";
        
        bool result = internal_execute(query);
        LOG_IF(WARNING, !result) << "クエリ実行に失敗: " << query;
        
        return result;
    }
    
    ~DatabaseConnection() {
        if (connection_) {
            disconnect(connection_);
            LOG(INFO) << "データベース接続を切断しました";
        }
    }
};

実用例(アプリケーション統合)

#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>
#include <g3sinks/LogRotate.h> // 外部シンク(log rotation)

class ApplicationLogger {
private:
    std::unique_ptr<g3::LogWorker> worker_;
    std::unique_ptr<g3::SinkHandle<g3::FileSink>> default_handle_;
    std::unique_ptr<g3::SinkHandle<LogRotate>> rotate_handle_;
    
public:
    void initialize(const std::string& log_dir, const std::string& app_name) {
        worker_ = g3::LogWorker::createLogWorker();
        
        // デフォルトログファイル
        default_handle_ = worker_->addDefaultLogger(app_name, log_dir);
        
        // ログローテーション機能付きシンク
        rotate_handle_ = worker_->addSink(
            std::make_unique<LogRotate>(app_name, log_dir),
            &LogRotate::save
        );
        
        // ログローテーション設定(10MBごと)
        const int k10MB = 10 * 1024 * 1024;
        rotate_handle_->call(&LogRotate::setMaxLogSize, k10MB);
        
        // カスタムヘッダー設定
        std::string header = "=== " + app_name + " Log Started ===\n";
        default_handle_->call(&g3::FileSink::overrideLogHeader, header);
        
        // カスタムフォーマット適用
        default_handle_->call(&g3::FileSink::overrideLogDetails, 
                             &g3::LogMessage::FullLogDetailsToString);
        
        g3::initializeLogging(worker_.get());
        
        LOG(INFO) << "ログシステムが初期化されました";
    }
    
    void shutdown() {
        if (worker_) {
            LOG(INFO) << "ログシステムをシャットダウンします";
            // workerのデストラクタで自動的にシャットダウン
            worker_.reset();
        }
    }
    
    // アプリケーション固有のログメソッド
    void logUserAction(const std::string& user, const std::string& action) {
        LOG(INFO) << "USER_ACTION: " << user << " -> " << action;
    }
    
    void logPerformanceMetric(const std::string& metric, double value) {
        LOGF(INFO, "METRIC: %s = %.3f", metric.c_str(), value);
    }
    
    void logError(const std::string& component, const std::string& error) {
        LOG(FATAL) << "ERROR in " << component << ": " << error;
    }
};

// 使用例
int main(int argc, char* argv[]) {
    ApplicationLogger logger;
    
    try {
        logger.initialize("./logs", "MyApplication");
        
        // アプリケーション処理
        logger.logUserAction("user123", "login");
        logger.logPerformanceMetric("response_time", 0.125);
        
        // 何らかの処理...
        
    } catch (const std::exception& e) {
        LOG(FATAL) << "未処理例外: " << e.what();
    }
    
    logger.shutdown();
    return 0;
}