...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
#include <boost/log/core/record.hpp
>
All the information that the logging library processes is packed into a
single object of type record
.
All attached data, including the message text, is represented as named
attribute values that can be fetched and processed by filters, formatters
and sinks. Particular attribute values can be accessed in different ways,
here are a few quick examples:
enum severity_level { ... }; std::ostream& operator<< (std::ostream& strm, severity_level level); struct print_visitor { typedef void result_type; result_type operator() (severity_level level) const { std::cout << level << std::endl; }; }; // Prints severity level through visitation API void print_severity_visitation(logging::record const& rec) { logging::visit< severity_level >("Severity", rec, print_visitor()); } // Prints severity level through extraction API void print_severity_extraction(logging::record const& rec) { logging::value_ref< severity_level > level = logging::extract< severity_level >("Severity", rec); std::cout << level << std::endl; }
attribute_values
method of the record.
// Prints severity level by searching the attribute values void print_severity_lookup(logging::record const& rec) { logging::attribute_value_set const& values = rec.attribute_values(); logging::attribute_value_set::const_iterator it = values.find("Severity"); if (it != values.end()) { logging::attribute_value const& value = it->second; // A single attribute value can also be visited or extracted std::cout << value.extract< severity_level >() << std::endl; } }
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level) // Prints severity level by using the subscript operator void print_severity_subscript(logging::record const& rec) { // Use the attribute keyword to communicate the name and type of the value logging::value_ref< severity_level, tag::severity > level = rec[severity]; std::cout << level << std::endl; }
Log records cannot be copied, only moved. A record can be default-constructed in which case it is in an empty state; such records are mostly unusable and should not be passed to the library for processing. Non-empty log records can only be created by the logging core as a result of successful filtering. The non-empty record contains attribute values acquired from attributes. More attribute values can be added to the non-empty record after filtering. The added values will not affect filtering results but can still be used by formatters and sinks.
In multithreaded environments, after being constructed a non-empty log record is considered to be tied to the current thread as it may refer to some thread-specific resources. For example, the record may contain an attribute value which refers to the named scope list which is stored on the stack. For this reason log records must not be passed between different threads.
#include <boost/log/core/record_view.hpp
>
While records are used for filling the information, the library uses another type to actually process it. Record views provide a similar interface to records with a few notable distinctions:
The library will automatically create a record view from the record by
calling the lock
method.
The call will also make sure the resulting view is not attached to the
current thread if a sink is asynchronous. The lock
call is a one time operation; the record is left in the empty state afterwards.
All APIs for interacting with attribute values described for log records
are also applicable to record views and can be used in custom formatters
and sinks.
#include <boost/log/core/core.hpp
>
The logging core is a central hub that provides the following facilities:
flush
method that can be used to enforce the synchronized state for all log
sinks.
The logging core is an application-wide singleton, thus every logging source
has access to it. The core instance is accessible with the static method
get
.
void foo() { boost::shared_ptr< logging::core > core = logging::core::get(); // ... }
In order to add or remove global or thread-specific attributes to the
core there are corresponding methods: add_global_attribute
,
remove_global_attribute
,
add_thread_attribute
and remove_thread_attribute
.
Attribute sets provide interface similar to std::map
,
so the add_*
methods accept an attribute name string (key) and a pointer to the attribute
(mapped value) and return a pair of iterator and boolean value, like
std::map< ... >::insert
does. The remove_*
methods accept an iterator to a previously
added attribute.
void foo() { boost::shared_ptr< logging::core > core = logging::core::get(); // Add a global attribute std::pair< logging::attribute_set::iterator, bool > res = core->add_global_attribute("LineID", attrs::counter< unsigned int >()); // ... // Remove the added attribute core->remove_global_attribute(res.first); }
Tip | |
---|---|
It must be said that all methods of logging core are thread-safe in multithreaded environments. However, that may not be true for other components, such as iterators or attribute sets. |
It is possible to acquire a copy of the whole attribute set (global or
thread-specific) or install it into the core. Methods get_global_attributes
,
set_global_attributes
,
get_thread_attributes
and set_thread_attributes
serve this purpose.
Warning | |
---|---|
After installing a whole attribute set into the core, all iterators
that were previously returned by the corresponding |
Global filtering is handled by the filter function object, which can
be provided with the set_filter
method. More on creating filters appears in this
section. Here it will suffice to say that the filter accepts a
set of attribute values and returns a boolean value that tells whether
a log record with these attribute values passed filtering or not. The
global filter is applied to every log record made throughout the application,
so it can be used to wipe out excessive log records quickly.
The global filter can be removed by the reset_filter
method. When there is no filter set in the core it is assumed that no
records are filtered away. This is the default after initial construction
of the logging core.
enum severity_level { normal, warning, error, critical }; void foo() { boost::shared_ptr< logging::core > core = logging::core::get(); // Set a global filter so that only error messages are logged core->set_filter(expr::attr< severity_level >("Severity") >= error); // ... }
The core also provides another way to disable logging. By calling the
set_logging_enabled
with
a boolean argument one may completely disable or re-enable logging, including
applying filtering. Disabling logging with this method may be more beneficial
in terms of application performance than setting a global filter that
always fails.
After global filtering is applied, log sinks step into action. In order
to add and remove sinks the core provides add_sink
and remove_sink
methods.
Both these methods accept a pointer to the sink. The add_sink
will add the sink to the core if it's not added already. The remove_sink
method will seek for the
provided sink in an internal list of previously added sinks and remove
the sink if it finds it. The order in which the core processes sinks
internally is unspecified.
void foo() { boost::shared_ptr< logging::core > core = logging::core::get(); // Set a sink that will write log records to the console boost::shared_ptr< sinks::text_ostream_backend > backend = boost::make_shared< sinks::text_ostream_backend >(); backend->add_stream( boost::shared_ptr< std::ostream >(&std::clog, boost::null_deleter())); typedef sinks::unlocked_sink< sinks::text_ostream_backend > sink_t; boost::shared_ptr< sink_t > sink = boost::make_shared< sink_t >(backend); core->add_sink(sink); // ... // Remove the sink core->remove_sink(sink); }
You can read more on the design of sinks in the following sections: Sink Frontends and Sink Backends.
The core provides a way to set up centralized exception handling. If
an exception takes place during filtering or processing in one of the
added sinks, the core will invoke an exception handler if one was installed
with the set_exception_handler
method. An exception handler is a nullary function object that is invoked
from within a catch
clause.
The library provides tools
to simplify exception handlers construction.
Tip | |
---|---|
The exception handler in the logging core is global and thus is intended to perform some common actions on errors. Logging sinks and sources also provide exception handling facilities (see here and here), which can be used to do a finer grained error processing. |
struct my_handler { typedef void result_type; void operator() (std::runtime_error const& e) const { std::cout << "std::runtime_error: " << e.what() << std::endl; } void operator() (std::logic_error const& e) const { std::cout << "std::logic_error: " << e.what() << std::endl; throw; } }; void init_exception_handler() { // Setup a global exception handler that will call my_handler::operator() // for the specified exception types logging::core::get()->set_exception_handler(logging::make_exception_handler< std::runtime_error, std::logic_error >(my_handler())); }
One of the most important functions of the logging core is providing
an entry point for all logging sources to feed log records into. This
is done with the open_record
and push_record
methods.
The first method is used to initiate the record logging process. It accepts the source-specific set of attributes. The method constructs a common set of attribute values of the three sets of attributes (global, thread-specific and source-specific) and applies filtering. If the filtering succeeded, i.e. at least one sink accepts a record with these attribute values, the method returns a non-empty record object, which can be used to fill in the log record message. If the filtering failed, an empty record object is returned.
When the log source is ready to complete the logging procedure, it has
to call the push_record
method with the record returned by the open_record
method. Note that one should not call push_record
with an empty record. The record should be passed as rvalue reference.
During the call the record view will be constructed from the record.
The view will then be passed on to the sinks that accepted it during
filtering. This may involve record formatting and further processing,
like storing it into a file or sending it over the network. After that
the record object can be destroyed.
void logging_function(logging::attribute_set const& attrs) { boost::shared_ptr< logging::core > core = logging::core::get(); // Attempt to open a log record logging::record rec = core->open_record(attrs); if (rec) { // Ok, the record is accepted. Compose the message now. logging::record_ostream strm(rec); strm << "Hello, World!"; strm.flush(); // Deliver the record to the sinks. core->push_record(boost::move(rec)); } }
All this logic is usually hidden in the loggers and macros provided by the library. However, this may be useful for those developing new log sources.