![]() |
Hey, all the fuss is just to have a string in the file, you say? That's right, you don't need a logging library to have something written into a file. But the library is capable of doing more: formatting and filtering are yet to come and these features are tightly coupled with the concept of attributes.
Each log record may have a number of attributes attached. Attributes may contain any essental information about conditions in which the log record occurred, such as position in code, executable module name, current date and time, or any piece of data relevant to your particular application and execution environment. An attribute may behave as a value generator, in which case it would return a different value for each log record it's involved in. As soon as the attribute generates the value, the latter becomes independent from the creator and may be used by different filters, formatters and sinks. But in order to do so one has to know the type of the value, or at least what types it may have. There are a number of commonly used attributes implemented in the library, you may find types of their values in the documentation.
Aside from that, as noted in the Design overview section, there are three possible scopes of attributes: source-specific, thread-specific and global. When a log record is made attribute values from these three sets are accumulated into a single view and passed to sinks, so there is no difference for them where the attribute was registered. Any attribute may be registered in any scope. Upon registering an attribute is given a unique name in order to make it possible to search for it. If it happens that the same named attribute is found in several scopes, the attribute from the most specific scope is taken into consideration in any further processing, including filtering and formatting. Such behavior makes it possible to override global or thread-scoped attributes with the ones registered in your local logger, thus reducing thread interference.
Getting back to our tutorial, let's add some attributes to our application.
There are attributes that are most likely to be used in almost any application. These are log record counter and a timestamp. They can be added with a single function call:
logging::add_common_attributes();
With this call attributes "LineID" and "TimeStamp" are
registered globally. The "LineID" attribute is a counter that increments
for each record being made, the first record gets identifier 1. The value
type of the attribute is unsigned
int. The "TimeStamp" attribute
always yelds the current time (i.e. the time when the log record is created,
not the time it was written to a sink) and its value type is boost::posix_time::ptime (see Boost.DateTime
documentation).
Some attrubutes are registered automatically on loggers construction. For
example, severity_logger
registers a source-specific attribute "Severity" which can be used
to add a level of emphasis for different log records. For example:
// We define our own severity levels enum severity_level { normal, notification, warning, error, critical }; // The logger implicitly adds a source-specific attribute 'Severity' of type 'severity_level' on construction src::severity_logger< severity_level > slg; BOOST_LOG_SEV(slg, normal) << "A regular message"; BOOST_LOG_SEV(slg, warning) << "Something bad is going on but I can handle it"; BOOST_LOG_SEV(slg, critical) << "Everything crumbles, shoot me now!";
The BOOST_LOG_SEV macro acts
pretty much like BOOST_LOG
except that it takes an additional argument for the open_record
method of the logger. The expanded BOOST_LOG_SEV
macro would look something like this:
if (lg.open_record(keywords::severity = normal)) lg.strm() << "A regular message";
You can see here that the open_record
can take named arguments. Some logger types provided by the library have
support for such additional parameters and this approach can certainly be
used by users when writing their own loggers.
Let's see what's under the hood of that add_common_attributes
function we used in the simple form section. It might look something like
this:
boost::shared_ptr< logging::core > pCore = logging::core::get(); pCore->add_global_attribute( "LineID", boost::make_shared< attrs::counter< unsigned int > >(1)); pCore->add_global_attribute( "TimeStamp", boost::make_shared< attrs::local_clock >());
Here the counter and local_clock components are attribute classes,
they derive from the common attribute interface attribute.
The library provides a number of other attribute classes, including the
functor attribute that calls
some functional object on value acquision. For example, we can in a similar
way register a named_scope
attrubute:
pCore->add_global_attribute( "Scope", boost::make_shared< attrs::named_scope >());
This will give the ability to store scope names in log for every log record the application makes. See the Attributes section for detailed description of attributes provided by the library.