Sometimes trivial logging doesn't provide enough flexibility. For example, one may want a more sophisticated logic of log processing, rather than simply storing it into a file. In order to customize this, you have to construct logging sinks and register them with the logging core. This should normally be done only once somewhere in the startup code of your application.
As a starting point, here is how you would initialize logginig into a file, similarly to the trivial logging:
void init() { logging::init_log_to_file("sample.log"); logging::core::get()->set_filter ( flt::attr< logging::trivial::severity_level >("Severity") >= logging::trivial::info ); } // We'll skip the "main" function for now
The added piece is a call to the init_log_to_file
function. As the name implies, the function initializes a logging sink that
stores log records into a text file. The function also accepts a number of
customization options, such as the rotation interval and size limits. For
instance:
void init() { logging::init_log_to_file ( keywords::file_name = "sample_%N.log", // file name pattern keywords::rotation_size = 10 * 1024 * 1024, // rotate files every 10 MiB... // ...or at midnight keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0), keywords::format = "[%TimeStamp%]: %_%" // log record format ); logging::core::get()->set_filter ( flt::attr< logging::trivial::severity_level >("Severity") >= logging::trivial::info ); }
You can see that the options are passed to the function in the named form. This approach is taken in many places of the library. You'll get used to it. The meaning of the parameters is mostly self-explaining, and is documented in this manual (see here for what regards the text file sink). This and other convenience initialization functions are described in this section.
Note | |
---|---|
You can register more than one sink. Each sink will process log records independently from others. In particular this means that you can use trivial logging and additionally register one or more sinks as shown above. All sinks will receive log records as you emit them. |
If you don't want to go into details, you can skip this section and continue from the next one. Otherwise, if you need more comprehensive control over sink configuration, or want to use different sinks, you may consider registering them manually.
In the simplest form, the call to the init_log_to_file
function in the section above is equivalent to this:
void init() { // Construct the sink typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink; boost::shared_ptr< text_sink > pSink = boost::make_shared< text_sink >(); // Add a stream to write log to pSink->locked_backend()->add_stream( boost::make_shared< std::ofstream >("sample.log")); // Register the sink in the logging core logging::core::get()->add_sink(pSink); }
Ok, the first thing you may have noticed about sinks is that they are composed
of two classes: the frontend and the backend. The frontend (which is the
synchronous_sink
class template
in the snippet above) is responsible for various common tasks for all sinks,
such as thread synchronization model and filtering. The backend (the text_ostream_backend
class above) implements
everything specific to the sink, such as text formatting and writing to a
file in this case. The library provides a number of frontends and backends
that can be used with each other out of the box.
The synchronous_sink
class
template above indicates that the sink is synchronous, that is, it allows
several threads to log simultaneously and will block in case of contention.
This means that the backend text_ostream_backend
need not worry about multithreading at all. There are other sink frontends
available, you can read more about them here.
The text_ostream_backend
class writes formatted log records into STL-compatible streams. We have used
a file stream above but we could have used any type of stream. For example,
adding output to console could look as follows:
#include <boost/log/utility/empty_deleter.hpp> // We have to provide an empty deleter to avoid destroying the global stream object boost::shared_ptr< std::ostream > pStream(&std::clog, logging::empty_deleter()); pSink->locked_backend()->add_stream(pStream);
The text_ostream_backend
supports adding several streams. In that case its output will be duplicated
to all added streams. This may be useful to duplicate output to console and
file, since all the filtering, formatting and other overhead of the library
happen only once per record for the sink.
Note | |
---|---|
Please note the difference between registering several distinct sinks and registering one sink with several target streams. While the former allows independently customizing output to each sink, the latter would work considerably faster if such customization is not needed. This feature is specific to this particular backend. |
The library provides a number of backends that provide different log processing logic. For instance, by specifying a syslog backend you can send log records over the network to the syslog server, or by setting up a Windows NT event log backend you can monitor your application run time by the standard Windows tools.
The last thing worth noting here is the locked_backend
member function call to access the sink backend. It is used to get thread-safe
access to the backend and is provided by all sink frontends. This function
returns a smart-pointer to the backend and as long as it exists the backend
is locked (which means even if another thread tries to log and the log record
is passed to the sink, it will not be logged until you release the backend).
The only exception is the unlocked_sink
frontend which does not synchronize at all and simply returns an unlocked
pointer to the backend.