#include <boost/log/utility/string_literal.hpp
>
String literals are used in several places throughout the library. However, this component can be successfully used outside of the library in users' code. It is header-only and does not require linking with the library binary. String literals can improve performance significantly if there is no need to modify stored strings. What is also important, since string literals do not dynamically allocate memory, it is easier to maintain exception safety when using string literals instead of regular strings.
The functionality is implemented in the basic_string_literal
class template, which is parametrized with the character and character
traits, similar to std::basic_string
.
There are also two convenience typedefs provided: string_literal
and wstring_literal
, for
narrow and wide character types, respectively. In order to ease string
literal construction in generic code there is also a str_literal
function template that accepts a string literal and returns a basic_string_literal
instance for the appropriate character type.
String literals support interface similar to STL strings, except for string modification functions. However, it is possible to assign to or clear string literals, as long as only string literals involved. Relational and stream output operators are also supported.
#include <boost/log/utility/type_dispatch/type_dispatcher.hpp
>
Type dispatchers are used throughout the library in order to work with attribute values. Dispatchers allow acquiring the stored attribute value using the Visitor concept. The most notable places where the functionality is used are filters and formatters. However, this mechanism is orthogonal to attributes and can be used for other purposes as well. Most of the time users won't need to dig into the details of type dispatchers, but this information may be useful for those who intend to extend the library and wants to understand what's under the hood.
Every type dispatcher supports the type_dispatcher
interface. When an attribute value needs to be extracted, this interface
is passed to the attribute value object, which then tries to acquire the
callback for the actual type of the value. All callbacks are objects of
the [class_type_dispatcher_callback] class template, instantiated on the
actual type of the value. If the dispatcher is able to consume the value
of the requested type, it must return a non-empty callback object. When
(and if) the corresponding callback is acquired, the attribute value object
only has to pass the contained value to its operator
()
.
Happily, there is no need to write type dispatchers from scratch. The library
provides two kinds of type dispatchers that implement the type_dispatcher
and [class_type_dispatcher_callback] interfaces and encapsulate the callback
lookup.
#include <boost/log/utility/type_dispatch/static_type_dispatcher.hpp
>
Static type dispatchers are used when the set of types that needs to be
supported for extraction is known at compile time. The static_type_dispatcher
class template is parametrized with an MPL type sequence of types that
need to be supported. The dispatcher inherits from the type_dispatcher
interface which provides the get_callback
method for acquiring the function object to invoke on the stored value.
All you need to do is provide a visitor function object to the dispatcher
at construction point and invoke the callback when dispatching the stored
value:
// Base interface for the custom opaque value struct my_value_base { virtual ~my_value_base() {} virtual bool dispatch(logging::type_dispatcher& dispatcher) const = 0; }; // A simple attribute value template< typename T > struct my_value : public my_value_base { T m_value; explicit my_value(T const& value) : m_value(value) {} // The function passes the contained type into the dispatcher bool dispatch(logging::type_dispatcher& dispatcher) const { logging::type_dispatcher::callback< T > cb = dispatcher.get_callback< T >(); if (cb) { cb(m_value); return true; } else return false; } }; // Value visitor for the supported types struct print_visitor { typedef void result_type; // Implement visitation logic for all supported types void operator() (int const& value) const { std::cout << "Received int value = " << value << std::endl; } void operator() (double const& value) const { std::cout << "Received double value = " << value << std::endl; } void operator() (std::string const& value) const { std::cout << "Received string value = " << value << std::endl; } }; // Prints the supplied value bool print(my_value_base const& val) { typedef boost::mpl::vector< int, double, std::string > types; print_visitor visitor; logging::static_type_dispatcher< types > disp(visitor); return val.dispatch(disp); }
#include <boost/log/utility/type_dispatch/dynamic_type_dispatcher.hpp
>
If the set of types that have to be supported is not available at compile
time, the dynamic_type_dispatcher
class is there to help. One can use its register_type
method to add support for a particular type. The user has to pass a function
object along with the type, this functor will be called when a visitor
for the specified type is invoked. Considering the my_value
from the code sample for static type dispatcher is intact, the code can
be rewritten as follows:
// Visitor functions for the supported types void on_int(int const& value) { std::cout << "Received int value = " << value << std::endl; } void on_double(double const& value) { std::cout << "Received double value = " << value << std::endl; } void on_string(std::string const& value) { std::cout << "Received string value = " << value << std::endl; } logging::dynamic_type_dispatcher disp; // The function initializes the dispatcher object void init_disp() { // Register type visitors disp.register_type< int >(&on_int); disp.register_type< double >(&on_double); disp.register_type< std::string >(&on_string); } // Prints the supplied value bool print(my_value_base const& val) { return val.dispatch(disp); }
Of course, complex function objects, like those provided by Boost.Bind, are also supported.
#include <boost/log/utility/type_dispatch/standard_types.hpp
> #include <boost/log/utility/type_dispatch/date_time_types.hpp
>
One may notice that when using type dispatchers and defining filters and formatters it may be convenient to have some predefined type sequences to designate frequently used sets of types. The library provides several such sets.
Table 1.6. Standard types (standard_types.hpp)
Type sequence |
Meaning |
---|---|
|
All integral types, including |
|
Floating point types |
|
Includes |
|
Narrow and wide string types. Currently only includes STL string types and string literals. |
There are also a number of time-related type sequences available:
Table 1.7. Time-related types (date_time_types.hpp)
Type sequence |
Meaning |
---|---|
|
All types defined in C/C++ standard that have both date and time portions |
|
All types defined in Boost.DateTime that have both date and time portions |
|
Includes |
|
All types defined in C/C++ standard that have date portion. Currently
equivalent to |
|
All types defined in Boost.DateTime that have date portion |
|
Includes |
|
All types defined in C/C++ standard that have time portion. Currently
equivalent to |
|
All types defined in Boost.DateTime
that have time portion. Currently equivalent to |
|
Includes |
|
All types defined in C/C++ standard that are used to represent
time duration. Currently only includes |
|
All time duration types defined in Boost.DateTime |
|
Includes |
|
All time period types defined in Boost.DateTime |
|
Currently equivalent to |
#include <boost/log/utility/value_ref.hpp
>
The value_ref
class
template is an optional reference wrapper which is used by the library
to refer to the stored attribute values. To a certain degree it shares
features of Boost.Optional
and Boost.Variant
components.
The template has two type parameters. The first is the referred type. It
can also be specified as a Boost.MPL
type sequence, in which case the value_ref
wrapper may refer to either type in the sequence. In this case, the which
method will return the index of
the referred type within the sequence. The second template parameter is
an optional tag type which can be used to customize formatting behavior.
This tag is forwarded to the to_log
manipulator when the wrapper
is put to a basic_formatting_ostream
stream, which is used by the library for record formatting. For an example
see how attribute value extraction is implemented:
void print_value_multiple_types(logging::attribute_value const& attr) { // Define the set of expected types of the stored value typedef boost::mpl::vector< int, std::string > types; // Extract a reference to the stored value logging::value_ref< types > val = logging::extract< types >(attr); // Check the result if (val) { std::cout << "Extraction succeeded" << std::endl; switch (val.which()) { case 0: std::cout << "int: " << val.get< int >() << std::endl; break; case 1: std::cout << "string: " << val.get< std::string >() << std::endl; break; } } else std::cout << "Extraction failed" << std::endl; }
The value_ref
wrapper
also supports applying a visitor function object to the referred object.
This can be done by calling one of the following methods:
apply_visitor
. This
method should only be used on a valid (non-empty) reference. The method
returns the visitor result.
apply_visitor_optional
.
The method checks if the reference is valid and applies the visitor
to the referred value if it is. The method returns the visitor result
wrapped into boost::optional
which will be filled only if the reference is valid.
apply_visitor_or_default
.
If the reference is valid, the method applies the visitor on the referred
value and returns its result. Otherwise the method returns a default
value passed as the second argument.
Note | |
---|---|
Regardless of the method used, the visitor function object must define the |
Here is an example of applying a visitor:
struct hash_visitor { typedef std::size_t result_type; result_type operator() (int val) const { std::size_t h = val; h = (h << 15) + h; h ^= (h >> 6) + (h << 7); return h; } result_type operator() (std::string const& val) const { std::size_t h = 0; for (std::string::const_iterator it = val.begin(), end = val.end(); it != end; ++it) h += *it; h = (h << 15) + h; h ^= (h >> 6) + (h << 7); return h; } }; void hash_value(logging::attribute_value const& attr) { // Define the set of expected types of the stored value typedef boost::mpl::vector< int, std::string > types; // Extract the stored value logging::value_ref< types > val = logging::extract< types >(attr); // Check the result if (val) std::cout << "Extraction succeeded, hash value: " << val.apply_visitor(hash_visitor()) << std::endl; else std::cout << "Extraction failed" << std::endl; }
#include <boost/log/utility/record_ordering.hpp
>
There are cases when log records need to be ordered. One possible use case is storing records in a container or a priority queue. The library provides two types of record ordering predicates out of the box:
The abstract_ordering
class allows application of a quick opaque ordering. The result of this
ordering is not stable between different runs of the application and in
general cannot be predicted before the predicate is applied, however it
provides the best performance. The abstract_ordering
class is a template that is specialized with an optional predicate function
that will be able to compare const
void*
pointers. By default an std::less
equivalent is used.
// A set of unique records std::set< logging::record_view, logging::abstract_ordering< > > m_Records;
This kind of ordering can be useful if the particular order of log records is not important but nevertheless some order is required.
This kind of ordering is implemented with the attribute_value_ordering
class and is based on the attribute values attached to the record. The
predicate will seek for an attribute value with the specified name in both
records being ordered and attempt to compare the attribute values.
// Ordering type definition typedef logging::attribute_value_ordering< int // attribute value type > ordering; // Records organized into a queue based on the "Severity" attribute value std::priority_queue< logging::record_view, std::vector< logging::record_view >, ordering > m_Records(ordering("Severity"));
Like the abstract_ordering
,
attribute_value_ordering
also accepts the second optional template parameter, which should be the
predicate to compare attribute values (int
s
in the example above). By default, an std::less
equivalent is used.
You can also use the make_attr_ordering
generator
function to automatically generate the attribute_value_ordering
instance based on the attribute value name and the ordering function. This
might be useful if the ordering function has a non-trivial type, like the
ones Boost.Bind
provides.
#include <boost/log/utility/exception_handler.hpp
>
The library provides exception handling hooks in different places. Tools, defined in this header, provide an easy way of implementing function objects suitable for such hooks.
An exception handler is a function object that accepts no arguments. The
result of the exception handler is ignored and thus should generally be
void
. Exception handlers are
called from within catch
sections
by the library, therefore in order to reacquire the exception object it
has to rethrow it. The header defines an exception_handler
template functor that does just that and then forwards the exception object
to a unary user-defined functional object. The make_exception_handler
function can be used to simplify the handler construction. All expected
exception types should be specified explicitly in the call, in the order
they would appear in the catch
sections (i.e. from most specific ones to the most general ones).
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())); }
As you can see, you can either suppress the exception by returning normally
from operator()
in the user-defined handler functor, or rethrow the exception, in which
case it will propagate further. If it appears that the exception handler
is invoked for an exception type that cannot be caught by any of the specified
types, the exception will be propagated without any processing. In order
to catch such situations, there exists the nothrow_exception_handler
class. It invokes the user-defined functor with no arguments if it cannot
determine the exception type.
struct my_handler_nothrow { 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 operator() () const { std::cout << "unknown exception" << std::endl; } }; void init_exception_handler_nothrow() { // Setup a global exception handler that will call my_handler::operator() // for the specified exception types. Note the std::nothrow argument that // specifies that all other exceptions should also be passed to the functor. logging::core::get()->set_exception_handler(logging::make_exception_handler< std::runtime_error, std::logic_error >(my_handler_nothrow(), std::nothrow)); }
It is sometimes convenient to completely suppress all exceptions at a certain
library level. The make_exception_suppressor
function creates an exception handler that simply does nothing upon exception
being caught. For example, this way we can disable all exceptions from
the logging library:
void init_logging() { boost::shared_ptr< logging::core > core = logging::core::get(); // Disable all exceptions core->set_exception_handler(logging::make_exception_suppressor()); }
The library provides a number of stream manipulators that may be useful in some contexts.
#include <boost/log/utility/manipulators/to_log.hpp
>
The to_log
function creates a stream manipulator that simply outputs the adopted
value to the stream. By default its behavior is equivalent to simply
putting the value to the stream. However, the user is able to overload
the operator<<
for the adopted value to override formatting behavior when values are
formatted for logging purposes. This is typically desired when the regular
operator<<
is employed for other tasks (such as serialization) and its behavior
is neither suitable for logging nor can be easily changed. For example:
std::ostream& operator<< ( std::ostream& strm, logging::to_log_manip< int > const& manip ) { strm << std::setw(4) << std::setfill('0') << std::hex << manip.get() << std::dec; return strm; } void test_manip() { std::cout << "Regular output: " << 1010 << std::endl; std::cout << "Log output: " << logging::to_log(1010) << std::endl; }
The second streaming statement in the test_manip
function will invoke our custom stream insertion operator which defines
special formatting rules.
It is also possible to define different formatting rules for different
value contexts as well. The library uses this feature to allow different
formatting ruled for different attribute values, even if the stored value
type is the same. To do so one has to specify an explicit template argument
for to_log
,
a tag type, which will be embedded into the manipulator type and thus
will allow to define different insertion operators:
struct tag_A; struct tag_B; std::ostream& operator<< ( std::ostream& strm, logging::to_log_manip< int, tag_A > const& manip ) { strm << "A[" << manip.get() << "]"; return strm; } std::ostream& operator<< ( std::ostream& strm, logging::to_log_manip< int, tag_B > const& manip ) { strm << "B[" << manip.get() << "]"; return strm; } void test_manip_with_tag() { std::cout << "Regular output: " << 1010 << std::endl; std::cout << "Log output A: " << logging::to_log< tag_A >(1010) << std::endl; std::cout << "Log output B: " << logging::to_log< tag_B >(1010) << std::endl; }
Note | |
---|---|
The library uses |
#include <boost/log/utility/manipulators/add_value.hpp
>
The add_value
function creates a manipulator that attaches an attribute value to a
log record. This manipulator can only be used in streaming expressions
with the basic_record_ostream
stream type (which is the case when log record message is formatted).
Since the message text is only formatted after filtering, attribute values
attached with this manipulator do not affect filtering and can only be
used in formatters and sinks themselves.
In addition to the value itself, the manipulator also requires the attribute name to be provided. For example:
// Creates a log record with attribute value "MyAttr" of type int attached BOOST_LOG(lg) << logging::add_value("MyAttr", 10) << "Hello world!";
#include <boost/log/utility/manipulators/dump.hpp
>
The dump
function creates a manipulator that outputs binary contents of a contiguous
memory region. This can be useful for logging some low level binary data,
such as encoded network packets or entries of a binary file. The use
is quite straightforward:
void on_receive(std::vector< unsigned char > const& packet) { // Outputs something like "Packet received: 00 01 02 0a 0b 0c" BOOST_LOG(lg) << "Packet received: " << logging::dump(packet.data(), packet.size()); }
The manipulator also allows to limit the amount of data to be output, in case if the input data can be too large. Just specify the maximum number of bytes of input to dump as the last argument:
void on_receive(std::vector< unsigned char > const& packet) { // Outputs something like "Packet received: 00 01 02 03 04 05 06 07 and 67 bytes more" BOOST_LOG(lg) << "Packet received: " << logging::dump(packet.data(), packet.size(), 8); }
There is another manipulator called dump_elements
for printing
binary representation of non-byte array elements. The special manipulator
for this case is necessary because the units of the size argument of
dump
can be confusing (is it in bytes or in elements?). Therefore dump
will not compile when used for non-byte input data. dump_elements
accepts
the same arguments, and its size-related arguments always designate the
number of elements to process.
void process(std::vector< double > const& matrix) { // Note that dump_elements accepts the number of elements in the matrix, not its size in bytes BOOST_LOG(lg) << "Matrix dump: " << logging::dump_elements(matrix.data(), matrix.size()); }
Tip | |
---|---|
Both these manipulators can also be used with regular output streams, not necessarily loggers. |
#include <boost/log/utility/manipulators/auto_newline.hpp
>
Sometimes it can be useful to be able to insert a newline character in
the output stream, but only if it hasn't been inserted as part of the
previous output. For example, if a string may or may not end with a newline,
and we cannot easily tell which one it is each time. The auto_newline
manipulator can be used
to ensure that all such strings are reliably terminated with a newline
and there are no duplicate newline characters. The manipulator will insert
a newline unless the last character inserted into the stream before it
was a newline. Its use is similar to standard stream manipulators:
BOOST_LOG(lg) << "Parameter: " << param.name << ", value: " << param.value << logging::auto_newline;
Note | |
---|---|
This manipulator inspects previous output to the stream, and therefore
can only be used with Boost.Log streams based on |
#include <boost/log/utility/ipc/object_name.hpp
>
In modern operating systems process-shared system resources are typically
identified with names. Unfortunately, different systems have different
requirements on the name syntax and allowed character set. Additionally,
some systems offer support for namespaces in order to avoid name clashes.
The object_name
class is intended to hide these differences.
An object name can be constructed from a UTF-8 string identifier and a scope. A portable identifier can contain the following characters:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 . _ -
Note | |
---|---|
The character set corresponds to POSIX Portable Filename Character Set. |
Use of other characters may result in non-portable system-specific behavior.
The scope is identified with the object_name::scope
enumeration:
global
- The name
has global scope; any process in the system has the potential to
open the resource identified by the name. On some systems this scope
may not be available or require elevated privileges.
user
- The name is
limited to processes running under the current user.
session
- The name
is limited to processes running in the current login session.
process_group
- The
name is limited to processes running in the current process group.
Currently, on Windows all processes running in the current session
are considered members of the same process group. This may change
in future.
The scopes are not overlapping. For instance, if an object is created in the global scope, the object cannot be opened with the same name but in user's scope. Some of the scopes may require elevated privileges to create or open objects in.
Warning | |
---|---|
Object name scoping should not be considered a security measure. Objects
may still be accessible by processes outside of their name scopes.
The main purpose of the scopes is to avoid name clashes between different
processes using |
#include <boost/log/utility/ipc/reliable_message_queue.hpp
>
The reliable_message_queue
class implements a reliable one-way channel of passing messages from
one or multiple writers to a single reader. The format of the messages
is user-defined and must be consistent across all writers and the reader.
The queue does not enforce any specific format of the messages, other
than they should be supplied as a contiguous array of bytes. The queue
internally uses a process-shared storage identified by an object
name (the queue name).
The queue storage is organized as a fixed number of blocks of a fixed size. The block size must be an integer power of 2 and is expressed in bytes. Each written message, together with some metadata added by the queue, consumes an integer number of blocks. Each read message received by the reader releases the blocks allocated for that message. As such the maximum size of a message is slightly less than block size times capacity of the queue. For efficiency, it is recommended to choose block size large enough to accommodate most of the messages to be passed through the queue. The queue is considered empty when no messages are enqueued (all blocks are free). The queue is considered full at the point of enqueueing a message when there is not enough free blocks to accommodate the message.
The queue is reliable in that it will not drop successfully sent messages that are not received by the reader, other than the case when a non-empty queue is destroyed by the last user. If a message cannot be enqueued by the writer because the queue is full, the queue will act depending on the overflow policy specified at the queue creation:
block_on_overflow
- Block the thread until there is enough space to enqueue the message
or the operation is aborted by calling stop_local
.
fail_on_overflow
- Return an error code from the send operation. The error code is
operation_result::no_space
.
throw_on_overflow
- Throw an exception from the send operation. The exception is capacity_limit_reached
.
The policy is object local, i.e. different writers and the reader can have different overflow policies. Here is an example of writing to the message queue:
int main() { typedef logging::ipc::reliable_message_queue queue_t; // Create a message_queue_type object that is associated with the interprocess // message queue named "ipc_message_queue". queue_t queue ( keywords::name = logging::ipc::object_name(logging::ipc::object_name::user, "ipc_message_queue"), keywords::open_mode = logging::open_mode::open_or_create, keywords::capacity = 256, keywords::block_size = 1024, keywords::overflow_policy = queue_t::fail_on_overflow ); // Send a message through the queue std::string message = "Hello, Viewer!"; queue_t::operation_result result = queue.send(message.data(), static_cast< queue_t::size_type >(message.size())); // See if the message was sent switch (result) { case queue_t::operation_result::succeeded: std::cout << "Message sent successfully" << std::endl; break; case queue_t::operation_result::no_space: std::cout << "Message could not be sent because the queue is full" << std::endl; break; case queue_t::operation_result::aborted: // This can happen is overflow_policy is block_on_overflow std::cout << "Message sending operation has been interrupted" << std::endl; break; } return 0; }
create the queue, if not yet created |
|
if the queue has to be created, allocate 256 blocks... |
|
... of 1 KiB each for messages |
|
if the queue is full, return error to the writer |
Typically, the queue would be used for sending log records to a different
process. As such, instead of using the queue for writing directly, one
would use a special sink backend for that. See text_ipc_message_queue_backend
documentation.
Receiving messages from the queue is similar. Here is an example of a log viewer that receives messages from the queue and displays them on the console.
int main() { try { typedef logging::ipc::reliable_message_queue queue_t; // Create a message_queue_type object that is associated with the interprocess // message queue named "ipc_message_queue". queue_t queue ( keywords::name = logging::ipc::object_name(logging::ipc::object_name::user, "ipc_message_queue"), keywords::open_mode = logging::open_mode::open_or_create, keywords::capacity = 256, keywords::block_size = 1024, keywords::overflow_policy = queue_t::block_on_overflow ); std::cout << "Viewer process running..." << std::endl; // Keep reading log messages from the associated message queue and print them on the console. // queue.receive() will block if the queue is empty. std::string message; while (queue.receive(message) == queue_t::succeeded) { std::cout << message << std::endl; // Clear the buffer for the next message message.clear(); } } catch (std::exception& e) { std::cout << "Failure: " << e.what() << std::endl; } return 0; }
Note | |
---|---|
The queue does not guarantee any particular order of received messages from different writer threads. Messages sent by a particular writer thread will be received in the order of sending. |
A blocked reader or writer can be unblocked by calling stop_local
. After this method is called,
all threads blocked on this particular object are released and return
operation_result::aborted
. The other instances of the
queue (in the current or other processes) are unaffected. In order to
restore the normal functioning of the queue instance after the stop_local
call the user has to invoke
reset_local
.
This part of the library is provided in order to simplify logging initialization and provide basic tools to develop user-specific initialization mechanisms. It is known that setup capabilities and preferences may vary widely from application to application, therefore the library does not attempt to provide a universal solution for this task. The provided tools are mostly intended to serve as a quick drop-in support for logging setup and a set of instruments to implement something more elaborate and more fitting users' needs.
Some of the features described in this section will require the separate library binary, with name based on "boost_log_setup" substring. This binary depends on the main library.
#include <boost/log/utility/setup/console.hpp
> #include <boost/log/utility/setup/file.hpp
> #include <boost/log/utility/setup/common_attributes.hpp
>
The library provides a number of functions that simplify some common initialization procedures, like sink and commonly used attributes registration. This is not much functionality. However, it saves a couple of minutes of learning the library for a newcomer.
Logging to the application console is the simplest way to see the logging library in action. To achieve this, one can initialize the library with a single function call, like this:
int main(int, char*[]) { // Initialize logging to std::clog logging::add_console_log(); // Here we go, we can write logs right away src::logger lg; BOOST_LOG(lg) << "Hello world!"; return 0; }
Pretty easy, isn't it? There is also the wadd_console_log
function for wide-character console. If you want to put logs to some
other standard stream, you can pass the stream to the add_console_log
function
as an argument. E.g. enabling logging to std::cout
instead of std::clog
would look like this:
logging::add_console_log(std::cout);
What's important, is that you can further manage the console sink if
you save the shared_ptr
to the sink that this function returns. This allows you to set up things
like filter, formatter and auto-flush flag.
int main(int, char*[]) { // Initialize logging to std::clog boost::shared_ptr< sinks::synchronous_sink< sinks::text_ostream_backend > > sink = logging::add_console_log(); sink->set_filter(expr::attr< int >("Severity") >= 3); sink->locked_backend()->auto_flush(true); // Here we go, we can write logs right away src::logger lg; BOOST_LOG(lg) << "Hello world!"; return 0; }
Similarly to console, one can use a single function call to enable logging to a file. All you have to do is to provide the file name:
int main(int, char*[]) { // Initialize logging to the "test.log" file logging::add_file_log("test.log"); // Here we go, we can write logs right away src::logger lg; BOOST_LOG(lg) << "Hello world!"; return 0; }
The add_console_log
and add_file_log
functions do not conflict and may be combined freely, so it is possible
to set up logging to the console and a couple of files, including filtering
and formatting, in about 10 lines of code.
Lastly, there is an add_common_attributes
function that registers two frequently used attributes: "LineID"
and "TimeStamp". The former counts log record being made and
has attribute value unsigned int
. The latter, as its name implies,
provides the current time for each log record, in the form of boost::posix_time::ptime
(see Boost.DateTime).
These two attributes are registered globally, so they will remain available
in all threads and loggers. This makes the final version of our code
sample look something like this:
int main(int, char*[]) { // Initialize sinks logging::add_console_log()->set_filter(expr::attr< int >("Severity") >= 4); logging::formatter formatter = expr::stream << expr::attr< unsigned int >("LineID") << ": " << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") << " *" << expr::attr< int >("Severity") << "* " << expr::message; logging::add_file_log("complete.log")->set_formatter(formatter); boost::shared_ptr< sinks::synchronous_sink< sinks::text_ostream_backend > > sink = logging::add_file_log("essential.log"); sink->set_formatter(formatter); sink->set_filter(expr::attr< int >("Severity") >= 1); // Register common attributes logging::add_common_attributes(); // Here we go, we can write logs src::logger lg; BOOST_LOG(lg) << "Hello world!"; return 0; }
#include <boost/log/utility/setup/filter_parser.hpp
> #include <boost/log/utility/setup/formatter_parser.hpp
>
Filter and formatter parsers allow constructing filters and formatters
from a descriptive string. The function parse_filter
is responsible for recognizing filters and parse_formatter
- for recognizing formatters.
In the case of filters the string is formed of a sequence of condition expressions, interconnected with boolean operations. There are two operations supported: conjunction (designated as "&" or "and") and disjunction ("|" or "or"). Each condition itself may be either a single condition or a sub-filter, taken in round brackets. Each condition can be negated with the "!" sign or "not" keyword. The condition, if it's not a sub-filter, usually consists of an attribute name enclosed in percent characters ("%"), a relation keyword and an operand. The relation and operand may be omitted, in which case the condition is assumed to be the requirement of the attribute presence (with any type).
filter: condition { op condition } op: & and | or condition: !condition not condition (filter) %attribute_name% %attribute_name% relation operand relation: > < = != >= <= begins_with ends_with contains matches
Below are some examples of filters:
Table 1.8. Examples of filters
Filter string |
Description |
---|---|
|
The filter returns |
|
The filter returns |
!( |
The filter returns |
|
The filter returns |
The formatter string syntax is even simpler and pretty much resembles Boost.Format format string syntax. The string is interpreted as a template which can contain attribute names enclosed with percent signs ("%"). The corresponding attribute values will replace these placeholders when the formatter is applied. The placeholder "%Message%" will be replaced with the log record text. For instance, the following formatter string:
[%TimeStamp%] *%Severity%* %Message%
will make log records look like this:
[2008-07-05 13:44:23] *0* Hello world
Note | |
---|---|
Previous releases of the library also supported the "%_%" placeholder for the message text. This placeholder is deprecated now, although it still works for backward compatibility. Its support will be removed in future releases. |
It must be noted that by default the library only supports those attribute value types which are known at the library build time. User-defined types will not work properly in parsed filters and formatters until registered in the library. It is also possible to override formatting rules of the known types, including support for additional formatting parameters in the string template. More on this is available in the Extending the library section.
Note | |
---|---|
The parsed formatters and filters are generally less optimal than the equivalent ones written in code with template expressions. This is because of two reasons: (*) the programmer usually knows more about types of the attribute values that may be involved in formatting or filtering and (*) the compiler has a better chance to optimize the formatter or filter if it is known in compile time. Therefore, when performance matters, it is advised to avoid parsed filters and formatters. |
#include <boost/log/utility/setup/settings.hpp
> #include <boost/log/utility/setup/from_settings.hpp
>
The headers define components for library initialization from a settings
container. The settings container is basically a set of named parameters
divided into sections. The container is implemented with the basic_settings
class template.
There are several constraints on how parameters are stored in the container:
So basically, settings container is a layered associative container,
with string keys and values. In some respect it is similar to Boost.PropertyTree,
and in fact it supports construction from boost::ptree
.
The supported parameters are described below.
Tip | |
---|---|
In the tables below, the |
Table 1.9. Section "Core". Logging core settings.
Parameter |
Format |
Description |
---|---|---|
Filter |
Filter string as described here |
Global filter to be installed to the core. If not specified, the global filter is not set. |
DisableLogging |
"true" or "false" |
If |
Sink settings are divided into separate subsections within the common top-level section "Sinks" - one subsection for each sink. The subsection names denote a user-defined sink name. For example, "MyFile".
Note | |
---|---|
Previous versions of the library also supported top-level sections starting with the "Sink:" prefix to describe sink parameters. This syntax is deprecated now, although it still works when parsing a settings file for backward compatibility. The parser will automatically put these sections under the "Sinks" top-level section in the resulting settings container. Support for this syntax will be removed in future releases. |
Table 1.10. Sections under the "Sinks" section. Common sink settings.
Parameter |
Format |
Description |
---|---|---|
Destination |
Sink target, see description |
Sink backend type. Mandatory parameter. May have one of these
values: Console,
TextFile,
Syslog.
On Windows the following values are additionally supported:
SimpleEventLog,
Debugger.
Also, user-defined sink names may also be supported if registered
by calling |
Filter |
Filter string as described here |
Sink-specific filter. If not specified, the filter is not set. |
Asynchronous |
"true" or "false" |
If |
Besides the common settings that all sinks support, some sink backends also accept a number of specific parameters. These parameters should be specified in the same section.
Table 1.11. "Console" sink settings
Parameter |
Format |
Description |
---|---|---|
Format |
Format string as described here |
Log record formatter to be used by the sink. If not specified, the default formatter is used. |
AutoNewline |
"Disabled", "AlwaysInsert" or "InsertIfMissing" |
Controls whether the backend should automatically insert a
trailing newline after every log record, see |
AutoFlush |
"true" or "false" |
Enables or disables the auto-flush feature of the backend.
If not specified, the default value |
Table 1.12. "TextFile" sink settings
Parameter |
Format |
Description |
---|---|---|
FileName |
File name pattern |
The active file name pattern for the sink backend. This parameter is mandatory. |
TargetFileName |
File name pattern |
The target file name pattern for the sink backend. If not specified, active file name is preserved after rotation. |
Format |
Format string as described here |
Log record formatter to be used by the sink. If not specified, the default formatter is used. |
AutoNewline |
"Disabled", "AlwaysInsert" or "InsertIfMissing" |
Controls whether the backend should automatically insert a
trailing newline after every log record, see |
AutoFlush |
"true" or "false" |
Enables or disables the auto-flush feature of the backend.
If not specified, the default value |
Append |
"true" or "false" |
Enables or disables appending
to the existing file instead of overwriting it. If not specified,
the default value |
RotationSize |
Unsigned integer |
File size, in bytes, upon which file rotation will be performed. If not specified, no size-based rotation will be made. |
RotationInterval |
Unsigned integer |
Time interval, in seconds, upon which file rotation will be performed. See also the RotationTimePoint parameter and the note below. |
RotationTimePoint |
Time point format string, see below |
Time point or a predicate that detects at what moment of time to perform log file rotation. See also the RotationInterval parameter and the note below. |
EnableFinalRotation |
"true" or "false" |
Enables or disables final file rotation on sink destruction,
which typically happens on program termination. If not specified,
the default value |
Target |
File system path to a directory |
Target directory name, in which the rotated files will be stored. If this parameter is specified, rotated file collection is enabled. Otherwise the feature is not enabled and all corresponding parameters are ignored. |
MaxSize |
Unsigned integer |
Total size of files in the target directory, in bytes, upon which the oldest file will be deleted. If not specified, no size-based file cleanup will be performed. |
MinFreeSpace |
Unsigned integer |
Minimum free space in the target directory, in bytes, upon which the oldest file will be deleted. If not specified, no space-based file cleanup will be performed. |
MaxFiles |
Unsigned integer |
Total number of files in the target directory, upon which the oldest file will be deleted. If not specified, no count-based file cleanup will be performed. |
ScanForFiles |
"All" or "Matching" |
Mode of scanning
for old files in the target directory, see |
Warning | |
---|---|
The text file sink uses Boost.Filesystem internally, which may cause problems on process termination. See here for more details. |
The time-based rotation can be set up with one of the two parameters: RotationInterval or RotationTimePoint. Not more than one of these parameters should be specified for a given sink. If none is specified, no time-based rotation will be performed.
The RotationTimePoint parameter should have one of the following formats, according to the Boost.DateTime format notation:
Table 1.13. "Syslog" sink settings
Parameter |
Format |
Description |
---|---|---|
Format |
Format string as described here |
Log record formatter to be used by the sink. If not specified, the default formatter is used. |
LocalAddress |
An IP address |
Local address to initiate connection to the syslog server. If not specified, the default local address will be used. |
TargetAddress |
An IP address |
Remote address of the syslog server. If not specified, the local address will be used. |
Table 1.14. "SimpleEventLog" sink settings
Parameter |
Format |
Description |
---|---|---|
Format |
Format string as described here |
Log record formatter to be used by the sink. If not specified, the default formatter is used. |
LogName |
A string |
Log name to write events into. If not specified, the default log name will be used. |
LogSource |
A string |
Log source to write events from. If not specified, the default source will be used. |
Registration |
"Never", "OnDemand" or "Forced" |
Mode of log source registration in Windows registry, see |
The user is free to fill the settings container from whatever settings source he needs. The usage example is below:
logging::settings setts; setts["Core"]["Filter"] = "%Severity% >= warning"; setts["Core"]["DisableLogging"] = false; // Subsections can be referred to with a single path setts["Sinks.Console"]["Destination"] = "Console"; setts["Sinks.Console"]["Filter"] = "%Severity% >= critical"; setts["Sinks.Console"]["Format"] = "%TimeStamp% [%Severity%] %Message%"; setts["Sinks.Console"]["AutoFlush"] = true; // ...as well as the individual parameters setts["Sinks.File.Destination"] = "TextFile"; setts["Sinks.File.FileName"] = "MyApp_%3N.log"; setts["Sinks.File.AutoFlush"] = true; setts["Sinks.File.RotationSize"] = 10 * 1024 * 1024; // 10 MiB setts["Sinks.File.Format"] = "%TimeStamp% [%Severity%] %Message%"; logging::init_from_settings(setts);
Note | |
---|---|
Initialization from settings does not automatically create any attributes or loggers, the application developer is still responsible for creating those. It can be said that loggers and attributes define the data that is exported by the application and the settings only describe how that data is going to be presented to the application user. |
The settings reader also can be extended to support custom sink types. See the Extending the library section for more information.
#include <boost/log/utility/setup/from_stream.hpp
>
Support for configuration files is a frequently requested feature of
the library. And despite the fact there is no ultimately convenient and
flexible format of the library settings, the library provides preliminary
support for this feature. The functionality is implemented with a simple
function init_from_stream
, which
accepts a std::istream
and reads the library settings
from it. The function then passes on the read settings to the init_from_settings
function, described above.
Therefore the parameter names and their meaning is the same as for the
init_from_settings
function.
The settings format is quite simple and widely used. Below is the description of syntax and parameters.
# Comments are allowed. Comment line begins with the '#' character # and spans until the end of the line. # Logging core settings section. May be omitted if no parameters specified within it. [Core] DisableLogging=false Filter="%Severity% > 3" # Sink settings sections [Sinks.MySink1] # Sink destination type Destination=Console # Sink-specific filter. Optional, by default no filter is applied. Filter="%Target% contains \"MySink1\"" # Formatter string. Optional, by default only log record message text is written. Format="<%TimeStamp%> - %Message%" # The flag shows whether the sink should be asynchronous Asynchronous=false # Enables automatic stream flush after each log record. AutoFlush=true
Here's the usage example:
int main(int, char*[]) { // Read logging settings from a file std::ifstream file("settings.ini"); logging::init_from_stream(file); return 0; }
See a more complete example.