If, while reading the previous sections, you wondered where these "LineID" and "Severity" things came from, the answer is here.
Each log record can have a number of attributes attached. Attributes can contain any essental information about the conditions in which the log record occurred, such as position in the 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 can 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 can find the 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 where the attribute was registered makes no difference for them. Any attribute can be registered in any scope. When registered, 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, "LineID" and "Severity" are attribute names. These attributes are registered by certain parts of the library, such as loggers and trivial logging setup code. Below is the description of the attribute registration process.
There are attributes that are likely to be used in nearly any application. For instance, log record counter and a time stamp. They can be added with a single function call:
logging::add_common_attributes();
With this call attributes "LineID", "TimeStamp", "ProcessID" and "ThreadID" are registered globally. The "LineID" attribute is a counter that increments for each record being made, the first record gets identifier 1. 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). The last two attributes identify the process and the thread in which every log record is emitted.
Important | |
---|---|
In single-threaded builds the "ThreadID" attribute is not registered. |
Tip | |
---|---|
The trivial logging support code calls |
This function is one of the several convenience functions described here.
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!";
Tip | |
---|---|
You can define your own formatting rules for the severity level by defining
|
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 BOOST_LOG_SEV
macro can be replaced with this equivalent:
logging::record rec = lg.open_record(keywords::severity = normal); if (rec) { rec.message() = "A regular message"; lg.push_record(rec); }
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.
Tip | |
---|---|
If you wonder if it is possible to define formatting rules for your custom
severity level type (or any other type for that matter), then yes, it is.
The simplest way to do it is to define |
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:
void add_common_attributes() { 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 >()); // other attributes skipped for brevity }
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 acquisition. 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.
Logger-specific attributes are no less useful than global ones. Severity levels and channel names are the most obvious candidates to be implemented on the source level. Nothing prevents you from adding more attributes to your loggers, like this:
lg.add_attribute("Tag", boost::make_shared< attrs::constant< std::string > >("My tag value"));
Now all log records made through this logger will be tagged with the specific attribute. This attribute value may be used later in filtering and formatting.
Another good use of attributes is the ability to mark log records made by different parts of application in order to highlight activity related to a single process. One may even implement a rough profiling tool to detect performance bottlenecks. For example:
void foo() { BOOST_LOG_SCOPED_THREAD_ATTR("Timeline", attrs::timer); bar(); }
Now every log record made from the bar
function, or any other function it calls, will contain the "Timeline"
attribute with a high precision time duration passed since the attribute
was registered. Based on these readings, one may detect which parts of the
code require more or less time to execute. The "Timeline" attribute
will be unregistered upon leaving the scope of function foo
.
See the Attributes section for detailed description of attributes provided by the library.