Boost.Log
repository·develop·Indexed 42 days ago
https://github.com/boostorg/logA 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.
What's inside Boost.Log
- 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.
How sinks, frontends, and backends work together
developIn Boost.Log, a sink is the destination for log records. It is composed of two parts:
- 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. - 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);- Frontend: Manages general sink behavior, such as threading models (e.g.,
Initialize logging with file and console sinks
developYou 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 usingboost::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 astd::locale(for example, usingboost::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(); }Initialize logging from a settings file with custom factories
developYou 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:
- Implement a
formatter_factory: Create a class inheriting fromlogging::formatter_factory<T>and implementcreate_formatterto return your custom formatter. - Register the factory: Use
logging::register_formatter_factory(name, factory_ptr)to map a string name (used in the settings file) to your factory. - Register simple factories: For types that support
operator<<andoperator>>, uselogging::register_simple_filter_factory<T>(name)andlogging::register_simple_formatter_factory<T, CharType>(name). - 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(); }- Implement a
Configure log record formatters
developFormatters 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>(
Log to multiple files based on attributes
developYou can use
boost::log::sinks::text_multifile_backendwrapped in asynchronous_sinkto direct log records to different files simultaneously based on attribute values.To implement this:
- Define a
text_multifile_backendinside asynchronous_sink. - Use
set_file_name_composerwithsinks::file::as_file_name_composerto define a naming pattern that includes an attribute (e.g., a thread ID). - Use
BOOST_LOG_SCOPED_THREAD_TAGor similar mechanisms to attach the relevant attribute to the log records in different threads. - 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"; }- Define a
Configure log formatting and attributes
developBoost.Log allows you to define how log records are formatted using
set_formatterandexpr::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 >());Use trivial logging macros for quick logging
developFor simple logging requirements, you can use the
BOOST_LOG_TRIVIALmacro. This macro accepts a severity level as an argument and supports standard stream insertion (<<) to format log messages.Available severity levels include:
tracedebuginfowarningerrorfatal
#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";Filter trivial log records by severity
developYou 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 thetrivial::severitylevel.For example, setting a filter to
trivial::severity >= trivial::infowill allowinfo,warning,error, andfatalmessages to pass, while suppressingtraceanddebugmessages.#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";Implement bounded asynchronous logging with record ordering
developTo 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_sinkcombined with asinks::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_overflowto 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()andsink->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();Log to a native syslog server
developTo log messages to a native syslog server (like
syslogd), use theboost::log::sinks::syslog_backendwithin a synchronous sink. You must define the implementation type and the syslog facility using keywords during sink construction.Key steps:
- Define a sink using
sinks::synchronous_sink<sinks::syslog_backend>. - Use
keywords::use_impl = sinks::syslog::nativeto specify the native API. - Specify the syslog facility (e.g.,
sinks::syslog::local7) usingkeywords::facility. - Map application-specific severity levels to syslog levels using
sinks::syslog::custom_severity_mappingand register it with the backend viaset_severity_mapper. - 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);- Define a sink using
Emit Windows events using scoped thread tags
developWhen using the Windows event log backend, you can attach metadata to a specific log record by using
BOOST_LOG_SCOPED_THREAD_TAG. These tags (likeEventID,Drive, orSize) are then picked up by theevent_composerto 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"; }