G3log

Asynchronous and crash-safe C++ logging library. Design emphasizing high performance and system stability. Characterized by functionality preventing log loss during crashes and minimizing impact on applications through asynchronous processing.

loggingC++asynchronouscrash-safethread-safe

Library

G3log

Overview

G3log is an asynchronous, crash-safe logger designed for C++ applications. Implemented in pure C++14 (with C++11 support up to release 1.3.2), it has no external dependencies and runs on OSX, Windows, and multiple Linux distributions. In addition to providing default logging sinks, G3log allows easy addition of custom sinks and ensures log safety even when programs crash, making it ideal for enterprise-level applications requiring robust logging capabilities.

Details

G3log is a performance-focused and safety-oriented C++ logging library that continues active development as of 2025. Its key features include true asynchronous processing for high performance and crash-safe functionality that ensures logs are written even during application crashes. It provides comprehensive modern C++ development features including LogWorker-based dedicated thread log processing, flexible output destination control via custom sinks, support for both streaming and printf syntax, CHECK functionality for design-by-contract, and automatic stack dumps during fatal signals.

Key Features

  • Asynchronous Log Processing: High-performance log processing via LogWorker dedicated threads
  • Crash-Safe: Ensures log data is reliably written even during abnormal application termination
  • Custom Sink Support: Flexible output control to files, console, network, and other destinations
  • Multiple Syntax Support: Support for both streaming (LOG) and printf-style (LOGF) syntax
  • Conditional Logging: Efficient conditional logging with LOG_IF, LOG_EVERY_N, and other macros
  • Design by Contract: Assertion functionality with CHECK, CHECK_F, and related macros

Advantages and Disadvantages

Advantages

  • True asynchronous processing minimizes performance impact on main threads
  • Crash-safe functionality reliably preserves critical debugging information
  • Easy integration with no external dependencies and standard CMake build system
  • Flexible output destination control and extensibility through custom sinks
  • Automatic stack dumps during fatal errors improve debugging efficiency
  • Rich conditional logging features enable efficient log control

Disadvantages

  • Requires C++14 or later, incompatible with older compiler environments
  • Asynchronous processing may cause log output timing to differ from execution timing
  • Learning curve exists, especially when implementing custom sinks
  • Integration with other language ecosystems (Java, Python) requires additional work
  • Log format customization is relatively limited
  • Immediate log verification may require attention due to asynchronous processing

Reference Pages

Code Examples

Basic Setup

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

int main(int argc, char* argv[]) {
    // Create LogWorker (manages asynchronous log processing)
    auto worker = g3::LogWorker::createLogWorker();
    
    // Add default file logger
    const std::string directory = "./logs/";
    const std::string name = "app_log";
    auto handle = worker->addDefaultLogger(name, directory);
    
    // Initialize logging system
    g3::initializeLogging(worker.get());
    
    LOG(INFO) << "G3log initialized successfully";
    
    // Log flushing is automatically handled by worker destructor
    // which calls g3::internal::shutDownLogging()
    
    return 0;
}

Basic Log Output

#include <g3log/g3log.hpp>

void basicLogging() {
    // Streaming syntax for log output
    LOG(INFO) << "Application started successfully";
    LOG(WARNING) << "Warning message: config value = " << config_value;
    LOG(FATAL) << "Fatal error occurred"; // Process termination
    
    // Printf-style log output
    LOGF(INFO, "User %s logged in (ID: %d)", username.c_str(), user_id);
    LOGF(WARNING, "Memory usage: %.2f MB", memory_usage_mb);
    LOGF(DEBUG, "Debug info: file=%s, line=%d", __FILE__, __LINE__);
    
    // Conditional logging
    LOG_IF(INFO, user_count > 100) << "Many users connected: " << user_count;
    LOGF_IF(WARNING, memory_usage > 0.8, "High memory usage: %.1f%%", memory_usage * 100);
    
    // Periodic logging (every N times)
    LOG_EVERY_N(INFO, 10) << "Every 10th log message: " << google::COUNTER << " times";
    
    // First N times only
    LOG_FIRST_N(INFO, 5) << "First 5 times only: " << google::COUNTER << " times";
}

Advanced Configuration (Custom Sinks and Formatting)

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

// Custom sink implementation example (console output)
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;
    }
};

// Custom file output sink
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()) {
            // Use custom formatting function
            file_ << logEntry.get().toString(&CustomFormatting) << std::endl;
            file_.flush(); // Immediate write
        }
    }
    
    // Custom formatting function
    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();
    
    // Default file logger
    auto fileHandle = worker->addDefaultLogger("app", "./logs/");
    
    // Add custom console sink
    auto consoleHandle = worker->addSink(
        std::make_unique<ColorConsoleSink>(),
        &ColorConsoleSink::ReceiveLogMessage
    );
    
    // Add custom file sink
    auto customFileHandle = worker->addSink(
        std::make_unique<CustomFileSink>("./logs/custom.log"),
        &CustomFileSink::ReceiveLogMessage
    );
    
    g3::initializeLogging(worker.get());
    
    // Test output by log level
    LOG(INFO) << "Information message";
    LOG(WARNING) << "Warning message";
    LOG(DEBUG) << "Debug message";
    
    // Asynchronously call sink methods
    std::future<void> future = customFileHandle->call(
        &CustomFileSink::SomeCustomMethod, param1, param2
    );
    
    return 0;
}

Error Handling and Design by Contract

#include <g3log/g3log.hpp>

void errorHandlingExamples() {
    // CHECK macros - terminate program if condition is false
    int* ptr = getPointer();
    CHECK(ptr != nullptr) << "Pointer is null";
    CHECK_NOTNULL(ptr); // Returns ptr (if not null)
    
    // Comparison CHECKs
    int expected = 42;
    int actual = calculateValue();
    CHECK_EQ(expected, actual) << "Expected and actual values differ";
    CHECK_NE(status, ERROR_STATE) << "System is in error state";
    CHECK_LT(memory_usage, MAX_MEMORY) << "Memory usage exceeds limit";
    
    // String comparison CHECK
    const char* config_path = getConfigPath();
    CHECK_STREQ(config_path, "/etc/app.conf") << "Invalid config file path";
    
    // Non-fatal CHECK (log only, continue program)
    CHECK(validate_data(data)); // Program continues even if false
    
    // Fatal CHECK (terminate program)
    CHECK_F(initialize_system(), "System initialization failed");
}

// More practical error handling example
class DatabaseConnection {
private:
    void* connection_;
    
public:
    DatabaseConnection(const std::string& conn_string) {
        connection_ = connect(conn_string);
        CHECK_NOTNULL(connection_) << "Database connection failed: " << conn_string;
        
        LOG(INFO) << "Connected to database: " << conn_string;
    }
    
    bool executeQuery(const std::string& query) {
        CHECK(!query.empty()) << "Query is empty";
        
        bool result = internal_execute(query);
        LOG_IF(WARNING, !result) << "Query execution failed: " << query;
        
        return result;
    }
    
    ~DatabaseConnection() {
        if (connection_) {
            disconnect(connection_);
            LOG(INFO) << "Database connection closed";
        }
    }
};

Practical Example (Application Integration)

#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>
#include <g3sinks/LogRotate.h> // External sink (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 log file
        default_handle_ = worker_->addDefaultLogger(app_name, log_dir);
        
        // Log rotation sink
        rotate_handle_ = worker_->addSink(
            std::make_unique<LogRotate>(app_name, log_dir),
            &LogRotate::save
        );
        
        // Configure log rotation (10MB per file)
        const int k10MB = 10 * 1024 * 1024;
        rotate_handle_->call(&LogRotate::setMaxLogSize, k10MB);
        
        // Custom header configuration
        std::string header = "=== " + app_name + " Log Started ===\n";
        default_handle_->call(&g3::FileSink::overrideLogHeader, header);
        
        // Apply custom formatting
        default_handle_->call(&g3::FileSink::overrideLogDetails, 
                             &g3::LogMessage::FullLogDetailsToString);
        
        g3::initializeLogging(worker_.get());
        
        LOG(INFO) << "Logging system initialized";
    }
    
    void shutdown() {
        if (worker_) {
            LOG(INFO) << "Shutting down logging system";
            // Automatic shutdown via worker destructor
            worker_.reset();
        }
    }
    
    // Application-specific log methods
    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;
    }
};

// Usage example
int main(int argc, char* argv[]) {
    ApplicationLogger logger;
    
    try {
        logger.initialize("./logs", "MyApplication");
        
        // Application processing
        logger.logUserAction("user123", "login");
        logger.logPerformanceMetric("response_time", 0.125);
        
        // Some processing...
        
    } catch (const std::exception& e) {
        LOG(FATAL) << "Unhandled exception: " << e.what();
    }
    
    logger.shutdown();
    return 0;
}