Getting started with the templog library

Basic idea

The basic idea of the templog library is that, on one side, developers insert log statements into their code in order to be able to follow the flow of execution of the program at run-time. Log statements have some string plus optional parameters which are inserted in order to create a log message. They also come with a severity and an intended log audience.

On the other side the log messages are retrieved and can be sent to wherever the application wants its log messages to end up.

Inbetween is the templog library, mainly providing very efficient filtering of log messages.

General overview

So, basically the templog library has two sides: One where log messages are fed into it and one where they can be retrieved.

To input log messages into the library, each log message is passed to a so-called logger. A logger is a type that, at compile-time, filters messages fed into it and, if the log messages pass the filter, forwards them to some other logger. The message's severity and intended audience, passed along with the log message, are used by loggers to filter the messages.

With this, hierarchies of loggers can be built that forward log messages from one to the other. Such log hierarchies resemble trees. At the root of them is non-filtering global logger, which forwards information to wherever the application wants to have them.

To specify this, a write policy for the application needs to be set up which determines, at compile-time, how and where the information needs to be written. There are several pre-defined write policies, but users of the library can also define their own write policies. Among the pre-defined write policies there is also one which provides facilities to switch writers at run-time.

Formatting of information is done using formatting policies. Again, there are several pre-defined formatting policies, but it is also possible to create a custom formatting policy.

Write policy and formatting policy are used to create a global non-filtering logger, which is at the root of the logger hierarchie. There is one pre-defined global non-filtering logger using a set of standard write and formatting policies. Users can use either this pre-defined global logger or set up their own one.

Severity and audience

Each log statement has an associated severity and an intended audience. Log statements can be filtered by loggers based on those two parameters. Since this filtering is done at compile-time, severity and audience are passed as template parameters.

Severities are ordered, that is, the debug severity is considered lower than the error severity. Audiences are unordered.

Loggers, logger hierarchies, and logger granularity

In order to gain maximum flexibility for turning off logging for parts of your application, you can create tree-like hierarchies of loggers that forward information to each other. At the root of this hierarchie is a global logger.

Log messages are fed into a logger, which filters it and, if it passes the filter, forwards it to its forward logger, which also filters it and, if it passes its filter, in turn forwards it to its forward logger etc., until the information is either filtered out or fed to the global logger which passes it on to its writing policy. This filtering of log messages by their severity and intended audience is done at compiler-time. Therefor it does not matter for run-time performance, whether a message passes several loggers until it is filtered out.

Upon definition, a logger's minimum severity, its served audiences, and its forward logger need to be specified. A logger will forward a log message to its forward logger only if the emssage's severity is as least as great as the logger's minimum severity and if the message's audience is in the logger's set of audiences. For example, this

  typedef templog::logger< templog::global_logger
                         , templog::sev_warning
                         , templog::audience_list<templog::aud_developer,templog::aud_support> >
                                             my_logger;

defines my_logger, which forwards to the global logger templog::global_logger if log messages have at least the warning severity and are intended for the developer or support audiences. Messages with a severity below templog::sev_warning or any other audience are discarded.

This logger can then be used to log information:

  TEMPLOG_LOG(my_logger,templog::sev_error,templog::aud_support) << "Some variable has an unexpected value of " << some_var;

This will pass the message into my_logger, which determines that the message's severity sev_error is higher than the logger's minimum severity sev_warning, and the message's intended audience aud_support is in the logger's list of audiences. Therefor the logger will pass the message to its forward logger global_logger, which happens to be the non-filtering global logger. Depending on the global logger's write policy the message will be written to wherever it is set up to write to.

Loggers can be defined per source file, class, module, subsystem, or whatever granularity users prefer. A logger's minimum severity or audience affects all log statements using this logger or any logger forwarding to it. Log messages can be turned on or off at compile-time by changing the logger they use or any logger this logger directly or indirectly forwards to. (Note: Of course, changing a log message's severity or audience certainly also affects whether it is filtered.) So loggers effectively control groups of log statements, providing the ability to trun the log volume up or down. How fine log messages are grouped depends on the granularity of the loggers defined in the logger hierarchy. The finer this granularity, the finer the filtering.

Write policies and formatting policies

In order to achieve miximum performance, formatting policies and write policies have to closely collaborate. Formatting policies determine how log messages get formatted, write policies determine how they get written and where they get written to.

Write policies can either be incremental (objects to be written are written one by one) or non-incremental. In the latter case, objects are first written into a string stream and then stream's string is then written at once. Whether a write policy is incremental or not is specified at its definition. Write policies also need to specify whether they need the library to append a newline to each message or whether that's not necessary.

A write policy passes log messages to a formatting policy. The latter then subsequently calls the write policy's functions to write the information. Which parts of information are written and in which order determines the policies formatting.

The policies pre-defined by the library should be sufficient in many cases. The library defines platform-dependent standard policies which are then used to define the templog::global_logger object, which can be used as the root of a logger hierarchie. Users can also define their own root logger by instantiating the templog::non_filtering_logger using other policies template and use this instead of the predefined global object.

Download

You can download the templog library here.