Boost.Log

repository·develop·Indexed 42 days ago

https://github.com/boostorg/log

A C++ library providing robust logging capabilities for applications and libraries. It features a flexible architecture with sinks (composed of frontends and backends), support for synchronous and asynchronous logging with record ordering, and specialized backends such as the Windows event log. The library includes tools for managing log messages, formatting, filtering, and attaching metadata via global, thread-local, or scoped attributes using macros like BOOST_LOG_SCOPED_THREAD_TAG.

Tokens
13.9K
Snippets
39
Records
43
Agent score
52%

What's inside Boost.Log

  1. Overview of Boost.Log

    develop
    Boost.Log is a part of the Boost C++ Libraries collection. It provides a comprehensive set of tools for adding logging capabilities to both libraries and standalone applications. It is designed to handle various logging requirements, from simple console output to complex, structured logging systems.
  2. How sinks, frontends, and backends work together

    develop

    In Boost.Log, a sink is the destination for log records. It is composed of two parts:

    1. Frontend: Manages general sink behavior, such as threading models (e.g., synchronous_sink) and filtering. It handles thread synchronization so that the backend does not have to.
    2. Backend: Implements the actual logic for formatting and storing log records (e.g., text_ostream_backend).

    To use a sink, you typically instantiate a frontend with a specific backend, configure the backend (like adding output streams), and then register the sink with the logging core.

    // Create a synchronous sink frontend with a text stream backend
    typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
    shared_ptr< text_sink > pSink(new text_sink);
    
    // Access the backend safely using a locked pointer
    text_sink::locked_backend_ptr pBackend = pSink->locked_backend();
    
    // Add a stream to the backend
    shared_ptr< std::ostream > pStream(&std::clog, boost::null_deleter());
    pBackend->add_stream(pStream);
    
    // Register the sink with the core
    logging::core::get()->add_sink(pSink);
  3. Initialize logging with file and console sinks

    develop

    You can initialize logging using utility functions like logging::add_file_log. This allows you to specify a filename and a format for the log records. The format can be constructed using boost::log::expressions (e.g., expr::stream, expr::format_date_time, expr::message).

    To support specific character encodings (like UTF-8) for wide character logging, you can retrieve the sink object and call imbue() with a std::locale (for example, using boost::locale::generator).

    Common attributes like timestamps and record counters can be added globally using logging::add_common_attributes().

    // Declare attribute keywords for use in formatting
    BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
    BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
    
    void init_logging()
    {
        // Create a file sink with a specific format
        boost::shared_ptr< sinks::synchronous_sink< sinks::text_file_backend > > sink = logging::add_file_log
        (
            "sample.log",
            keywords::format = expr::stream
                << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f")
                << " <" << severity.or_default(normal)
                << "> " << expr::message
        );
    
        // Set locale for character code conversion (e.g., UTF-8)
        std::locale loc = boost::locale::generator()("en_US.UTF-8");
        sink->imbue(loc);
    
        // Add common attributes like timestamp
        logging::add_common_attributes();
    }
  4. Initialize logging from a settings file with custom factories

    develop

    You can configure Boost.Log dynamically using a settings file (via logging::init_from_stream) by registering custom factories for filters and formatters. This allows the settings file to reference custom attribute types or complex formatting logic by name.

    To use custom factories:

    1. Implement a formatter_factory: Create a class inheriting from logging::formatter_factory<T> and implement create_formatter to return your custom formatter.
    2. Register the factory: Use logging::register_formatter_factory(name, factory_ptr) to map a string name (used in the settings file) to your factory.
    3. Register simple factories: For types that support operator<< and operator>>, use logging::register_simple_filter_factory<T>(name) and logging::register_simple_formatter_factory<T, CharType>(name).
    4. Load settings: Call logging::init_from_stream(stream) to parse the configuration.
    // 1. Define a custom factory
    class my_scopes_formatter_factory : public logging::formatter_factory<char> {
    public:
        formatter_type create_formatter(logging::attribute_name const& attr_name, args_map const& args) {
            return formatter_type(scope_list_formatter(attr_name));
        }
    };
    
    // 2. Register and initialize
    void init_logging() {
        // Register custom formatter factory
        logging::register_formatter_factory("MyScopes", boost::make_shared<my_scopes_formatter_factory>());
    
        // Register simple factories for custom enums (requires operator<< and operator>>)
        logging::register_simple_filter_factory<severity_level>("Severity");
        logging::register_simple_formatter_factory<severity_level, char>("Severity");
    
        // Load from file
        std::ifstream settings("settings.txt");
        logging::init_from_stream(settings);
    
        logging::add_common_attributes();
    }
  5. Configure log record formatters

    develop

    Formatters define how log attributes and the message body are rendered in the output. You can set a formatter on a per-sink basis using set_formatter().

    Commonly used components in formatters include:

    • `expr::attr<T>(
  6. Log to multiple files based on attributes

    develop

    You can use boost::log::sinks::text_multifile_backend wrapped in a synchronous_sink to direct log records to different files simultaneously based on attribute values.

    To implement this:

    1. Define a text_multifile_backend inside a synchronous_sink.
    2. Use set_file_name_composer with sinks::file::as_file_name_composer to define a naming pattern that includes an attribute (e.g., a thread ID).
    3. Use BOOST_LOG_SCOPED_THREAD_TAG or similar mechanisms to attach the relevant attribute to the log records in different threads.
    4. Register the sink with boost::log::core::get()->add_sink(sink).

    This allows the sink to automatically separate log records into different files (e.g., logs/thread_id.log) depending on the value of the specified attribute.

    // Create a text file sink using a multifile backend
    typedef sinks::synchronous_sink< sinks::text_multifile_backend > file_sink;
    shared_ptr< file_sink > sink(new file_sink);
    
    // Set up how the file names will be generated using an attribute (e.g., "ThreadID")
    sink->locked_backend()->set_file_name_composer(
        sinks::file::as_file_name_composer(
            expr::stream << "logs/" << expr::attr< std::thread::id >("ThreadID") << ".log"
        )
    );
    
    // Set the log record formatter
    sink->set_formatter(
        expr::format("%1%: [%2%] - %3%")
            % expr::attr< unsigned int >("RecordID")
            % expr::attr< boost::posix_time::ptime >("TimeStamp")
            % expr::smessage
    );
    
    // Add it to the core
    logging::core::get()->add_sink(sink);
    
    // Add global attributes to the core
    logging::core::get()->add_global_attribute("TimeStamp", attrs::local_clock());
    logging::core::get()->add_global_attribute("RecordID", attrs::counter< unsigned int >());
    
    // In a thread, tag the scope with the attribute used for file naming
    void thread_foo()
    {
        BOOST_LOG_SCOPED_THREAD_TAG("ThreadID", std::this_thread::get_id());
        BOOST_LOG(my_logger::get()) << "Log record";
    }
  7. Configure log formatting and attributes

    develop

    Boost.Log allows you to define how log records are formatted using set_formatter and expr::format. You can include attributes like timestamps or custom counters in your log lines.

    To use attributes in a formatter, ensure they are added to the logging core as global attributes using logging::core::get()->add_global_attribute(...).

    // Set up a formatter using expressions
    sink->set_formatter
        (
            expr::format("%1%: [%2%] - %3%")
                % expr::attr< unsigned int >("RecordID")
                % expr::attr< boost::posix_time::ptime >("TimeStamp")
                % expr::smessage
        );
    
    // Add global attributes to the core
    logging::core::get()->add_global_attribute("TimeStamp", attrs::local_clock());
    logging::core::get()->add_global_attribute("RecordID", attrs::counter< unsigned int >());
  8. Use trivial logging macros for quick logging

    develop

    For simple logging requirements, you can use the BOOST_LOG_TRIVIAL macro. This macro accepts a severity level as an argument and supports standard stream insertion (<<) to format log messages.

    Available severity levels include:

    • trace
    • debug
    • info
    • warning
    • error
    • fatal
    #include <boost/log/trivial.hpp>
    
    BOOST_LOG_TRIVIAL(trace) << "A trace severity message";
    BOOST_LOG_TRIVIAL(debug) << "A debug severity message";
    BOOST_LOG_TRIVIAL(info) << "An informational severity message";
    BOOST_LOG_TRIVIAL(warning) << "A warning severity message";
    BOOST_LOG_TRIVIAL(error) << "An error severity message";
    BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message";
  9. Filter trivial log records by severity

    develop

    You can control which log messages are processed by applying a filter to the global logging core. Using boost::log::core::get()->set_filter(...), you can define criteria based on the trivial::severity level.

    For example, setting a filter to trivial::severity >= trivial::info will allow info, warning, error, and fatal messages to pass, while suppressing trace and debug messages.

    #include <boost/log/trivial.hpp>
    #include <boost/log/core.hpp>
    #include <boost/log/expressions.hpp>
    
    using namespace boost::log;
    
    // Apply a filter so only messages with severity 'info' or higher are logged
    core::get()->set_filter(
        trivial::severity >= trivial::info
    );
    
    // This will be suppressed
    BOOST_LOG_TRIVIAL(debug) << "This message will not appear";
    
    // This will be logged
    BOOST_LOG_TRIVIAL(info) << "This message will appear";
  10. Implement bounded asynchronous logging with record ordering

    develop

    To implement asynchronous logging that limits memory usage and ensures log records are written in a specific order (even when produced by multiple threads), use sinks::asynchronous_sink combined with a sinks::bounded_ordering_queue.

    Key components:

    • sinks::asynchronous_sink<Backend, Queue>: Wraps a backend to process logs in a separate thread.
    • sinks::bounded_ordering_queue<Ordering, Capacity, OverflowStrategy>:
      • Ordering: Defines how records are sorted (e.g., logging::attribute_value_ordering).
      • Capacity: The maximum number of records allowed in the queue.
      • OverflowStrategy: Defines behavior when the queue is full (e.g., sinks::block_on_overflow to wait for space).
    • logging::make_attr_ordering<T>(AttributeName, Comparator): Used during sink construction to tell the queue which attribute to use for sorting records.

    To ensure all logs are written before the application exits, call sink->stop() and sink->flush().

    // Define the backend (e.g., text file stream)
    typedef sinks::text_ostream_backend backend_t;
    
    // Define the asynchronous sink with a bounded, ordering queue
    typedef sinks::asynchronous_sink<
        backend_t,
        sinks::bounded_ordering_queue<
            logging::attribute_value_ordering< unsigned int, std::less< unsigned int > ability, 
            128,                        // Max 128 records in queue
            sinks::block_on_overflow    // Block the producer if queue is full
        >
    > sink_t;
    
    // Initialize the sink with ordering based on a "RecordID" attribute
    shared_ptr< sink_t > sink(new sink_t(
        boost::make_shared< backend_t >(),
        keywords::order = logging::make_attr_ordering< unsigned int >("RecordID", std::less< unsigned int >())
    ));
    
    // Add to core and set attributes for ordering
    logging::core::get()->add_sink(sink);
    logging::core::get()->add_global_attribute("RecordID", attrs::counter< unsigned int >());
    
    // ... logging operations ...
    
    // Cleanup
    sink->stop();
    sink->flush();
  11. Log to a native syslog server

    develop

    To log messages to a native syslog server (like syslogd), use the boost::log::sinks::syslog_backend within a synchronous sink. You must define the implementation type and the syslog facility using keywords during sink construction.

    Key steps:

    1. Define a sink using sinks::synchronous_sink<sinks::syslog_backend>.
    2. Use keywords::use_impl = sinks::syslog::native to specify the native API.
    3. Specify the syslog facility (e.g., sinks::syslog::local7) using keywords::facility.
    4. Map application-specific severity levels to syslog levels using sinks::syslog::custom_severity_mapping and register it with the backend via set_severity_mapper.
    5. Register the sink with the logging core using logging::core::get()->add_sink(sink).
    // Create a syslog sink using the native implementation
    shared_ptr< sinks::synchronous_sink< sinks::syslog_backend > > sink(
        new sinks::synchronous_sink< sinks::syslog_backend>(
            keywords::use_impl = sinks::syslog::native,
            keywords::facility = sinks::syslog::local7
        )
    );
    
    // Map custom application severity levels to syslog levels
    sinks::syslog::custom_severity_mapping< severity_levels > mapping("Severity");
    mapping[normal] = sinks::syslog::info;
    mapping[warning] = sinks::syslog::warning;
    mapping[error] = sinks::syslog::critical;
    
    // Apply the mapping to the backend
    sink->locked_backend()->set_severity_mapper(mapping);
    
    // Register the sink with the core
    logging::core::get()->add_sink(sink);
  12. Emit Windows events using scoped thread tags

    develop

    When using the Windows event log backend, you can attach metadata to a specific log record by using BOOST_LOG_SCOPED_THREAD_TAG. These tags (like EventID, Drive, or Size) are then picked up by the event_composer to populate the Windows event message placeholders.

    void announce_low_disk_space(std::string const& drive, boost::uintmax_t size)
    {
        // Attach attributes that the event_composer expects
        BOOST_LOG_SCOPED_THREAD_TAG("EventID", (int)LOW_DISK_SPACE_MSG);
        BOOST_LOG_SCOPED_THREAD_TAG("Drive", drive);
        BOOST_LOG_SCOPED_THREAD_TAG("Size", size);
    
        // Emit the log record
        BOOST_LOG_SEV(event_logger::get(), warning) << "Low disk " << drive << " space";
    }