g3log

repository·master·Indexed 21 days ago

https://github.com/kjellkod/g3log

An asynchronous C++ logger designed for high performance and reliability. It features streaming and printf-style logging syntaxes, a design-by-contract framework with CHECK macros, and robust fatal crash handling that flushes logs and provides stack traces (including Relative Virtual Addresses on Windows) when system signals like SIGSEGV or SIGABRT occur.

Tokens
11.4K
Snippets
48
Records
56
Agent score
74%

What's inside g3log

  1. Understand Log Flushing and Crash Safety

    master

    Flushing Behavior

    • Default FileSink: Flushes at set intervals (default buffer size is 100 entries). This can be adjusted in FileSink.
    • Shutdown: All enqueued logs are flushed when the program shuts down normally.
    • Fatal Events: All enqueued logs are flushed when a fatal signal (e.g., SIGSEGV, SIGABRT) is caught.

    Limitations

    • exit(0) or other abrupt process exits that do not trigger a fatal signal will not flush enqueued logs.
    • g3log is designed to be crash-safe by catching fatal signals and ensuring the LogWorker flushes pending messages before the process exits.
  2. How g3log handles fatal events and crashes

    master

    g3log is designed to prevent log loss during crashes. When a fatal event occurs (via LOG(FATAL), CHECK failures, or system signals like SIGSEGV, SIGABRT, SIGILL, SIGFPE, or SIGTERM), g3log:

    1. Captures the event.
    2. Flushes all pending log activity to the configured sinks.
    3. Attempts to push a stacktrace to the logging sink (if object symbols are available).
    4. Only allows the process to exit after all logs up to the point of the crash have been written.

    You can override fatal error handling or add custom execution hooks via the API.

  3. Extend logging with custom sinks

    master

    While the default sink has no external dependencies, you can use or create custom sinks for various purposes. Common sink types include:

    • Log rotation
    • Syslog
    • Colored terminal output
    • Log rotation with filtering

    For additional sinks, see the g3sinks repository. You can implement your own sink by following the patterns defined in the API.md documentation.

  4. Use Design-by-Contract with CHECK

    master

    The CHECK macro provides a way to enforce invariants in your code.

    • If the condition inside CHECK evaluates to false, it triggers a FATAL message.
    • A FATAL message is logged, and the application will immediately exit.
    • You can append a message to the fatal log using the << operator.
    CHECK(less != more); // If this is false, it triggers a FATAL message and exits
    CHECK(less > more) << "CHECK(false) triggers a FATAL message";
  5. Configure logging levels and filtering

    master

    g3log supports:

    • Custom Logging Levels: You can define your own levels or replace the defaults (DEBUG, INFO, WARNING, FATAL).
    • Log Filtering: If dynamic logging levels are enabled in the configuration, g3log can filter logs. Filtering can also be implemented at the sink level (e.g., using g3sinks).
  6. Handle fatal signals and crashes

    master

    g3log catches fatal events (like SIGSEGV or SIGILL), generates a stack dump, flushes all log entries to the sinks, and then re-emits the signal.

    Customizing Fatal Signals (Linux/Unix)

    By default, g3log handles SIGABRT, SIGFPE, SIGILL, SIGSEGV, and SIGTERM. You can override this list using g3::overrideSetupSignals to add or remove signals (for example, if SIGTERM is used by another library like ZMQ and should be skipped).

    Pre-fatal Hook

    You can define a callback function that executes immediately before the fatal signal handling occurs. This is useful for performing critical cleanup tasks during a crash.

    Disabling Fatal Handling

    Fatal signal handling can be disabled entirely at build time using the CMake option ENABLE_FATAL_SIGNALHANDLING.

    // Example when SIGTERM is skipped due to ZMQ usage
    g3::overrideSetupSignals({ {SIGABRT, "SIGABRT"}, 
                              {SIGFPE, "SIGFPE"},
                              {SIGILL, "SIGILL"},
                              {SIGSEGV, "SIGSEGV"}});
    
    // Example of how to enforce important shutdown cleanup even in the event of a fatal crash:
     g3::setFatalPreLoggingHook([]{ cleanup(); });
  7. Quickstart: Using g3log in your files

    master

    To use g3log in your C++ project, include the main header. This provides access to the logger without requiring complex dependency injection.

    #include <g3log/g3log.hpp>
    #include <g3log/g3log.hpp>
  8. Build g3log with CMake in a Debian environment

    master

    To build g3log with unit tests and performance benchmarks enabled, use the following CMake configuration steps within your terminal (e.g., inside a Dev Container or Codespace):

    1. Create a build directory.
    2. Configure with ADD_G3LOG_UNIT_TEST=ON and ADD_G3LOG_BENCH_PERFORMANCE=ON.
    3. Build using make.
    mkdir debianbuild
    cd debianbuild
    cmake -DADD_G3LOG_UNIT_TEST=ON -DADD_G3LOG_BENCH_PERFORMANCE=ON ..
    make -j
  9. Initialize and shut down g3log

    master

    To use g3log, you must create a LogWorker and initialize it.

    Setup Steps

    1. Create a LogWorker using LogWorker::createLogWorker().
    2. Add your sinks (default or custom).
    3. Call g3::initializeLogging(logworker.get()) to start the background logging thread.

    Shutdown

    If you use a std::unique_ptr<LogWorker>, the logger will shut down automatically when the pointer goes out of scope (RAII). Alternatively, you can call g3::internal::shutDownLogging() manually to ensure all buffered logs are flushed to the sinks before the application exits.

    #include <g3log/g3log.hpp> 
    #include <g3log/logworker.hpp>
    
    int main(int argc, char** argv) {
       using namespace g3;
       
       // 1. Create worker
       std::unique_ptr<LogWorker> logworker{ LogWorker::createLogWorker() };
    
       // 2. Add sinks
       auto sinkHandle = logworker->addSink(std::make_unique<CustomSink>(), &CustomSink::ReceiveLogMessage);
    
       // 3. Initialize
       initializeLogging(logworker.get());
    
       LOG(INFO) << "Logger is ready";
    
       // 4. Shutdown (Automatic via RAII or manual)
       g3::internal::shutDownLogging();
       return 0;
    }
  10. Setup the default FileSink

    master

    The default sink is a simple file sink. You can initialize it using addDefaultLogger on a LogWorker instance. This will create log files with a timestamped suffix.

    const std::string directory = "./";
    const std::string name = "(ReplaceLogFile)";
    auto worker = g3::LogWorker::createLogWorker();
    auto handle = worker->addDefaultLogger(name, directory);
    
    // Resulting filename format: ./(ReplaceLogFile).g3log.YYYYMMDD-HHMMSS.log
  11. Run g3log unit tests

    master

    To run the tests after the build process is complete, use one of the following:

    • Cross-platform: ctest -C Release
    • Linux: make test
    • Detailed gtest output: ../scripts/runAllTests.sh (run from the build directory).
    ctest -C Release
  12. Override log formatting for a custom sink

    master

    When implementing a custom sink, you can override the default log formatting by passing a custom formatting function to LogMessage::toString(). This function will be used to convert the log message into a string before it is processed by your sink's receive method.

    namespace {
      std::string MyCustomFormatting(const LogMessage& msg) {
        // Custom formatting logic here
        return msg.toString(); 
      }
    }
    
    void MyCustomSink::ReceiveLogEntry(LogMessageMover message) {
      // Pass the custom formatting function to toString()
      std::string formatted = message.get().toString(&MyCustomFormatting) << std::flush;
    }
    
    // Setup
    auto worker = g3::LogWorker::createLogWorker();
    auto sinkHandle = worker->addSink(std::make_unique<MyCustomSink>(),
                                     &MyCustomSink::ReceiveLogMessage);