Tomographer  v5.3
Tomographer C++ Framework Documentation
Logging and Loggers

Tomographer provides a lightweight framework for logging, i.e. producing messages which inform the user and/or developer about what the program is doing. Objects who would like to log messages take a template type parameter Logger, and an instance of such a type usually provided to its constructor. The Logger type must be a subclass of Tomographer::Logger::LoggerBase.

Basic Usage

Log messages have different levels of importance, which are Tomographer::Logger::ERROR, Tomographer::Logger::WARNING, Tomographer::Logger::INFO, Tomographer::Logger::DEBUG, and Tomographer::Logger::LONGDEBUG. Please read the documentation for these levels and make sure you choose the right level for your messages.

To log messages into a logger object logger, one simply calls, depending on the level, one of for example:

logger.longdebug("origin_fn()", "Iteration k=%d, new value is = %.4g", k, newvalue);
logger.debug("origin_fn()", "Starting new instance of a random walk with %d iterations", num_iterations);
logger.info("origin_fn()", "Data successfully read, dim = %d", data->dim);
logger.warning("origin_fn()", "Failed to nice() process to value %d", nice_val);
logger.error("origin_fn()", "Can't read file `%s': %s", fname.c_str(), strerror(errno));

As you see, for each level, you have a corresponding method to call. The first argument is a constant string literal which should specify where the message originated from. It need not be displayed by all loggers, but is really helpful to trace bugs and track down where the program actually is.

Your messages should not end in newlines. Newlines will automatically be added by loggers which log into files and/or the console.

Formatting Flavors

Each of the above methods comes in different flavors, depending on whether you would like to print a message which is already formatted, to have your message formatted printf -style, or to use C++ streams to display your message:

logger.debug(const char * origin, const char * fmt, ...); // printf-style
logger.debug(const char * origin, const std::string & msg); // already formatted
logger.debug(const char * origin, Fn fn); // lambda callback

The last variant takes a callable argument and calls it with an std::ostream& argument. The function should then stream whatever it wants into the stream. This is particularly useful with C++11 lambdas, for example:

logger.debug("origin_fn()", [value](std::ostream& stream) {
stream << "Value is = " << value;
});

This last idiom is also very useful if producing the log message itself is resource-consuming. Imagine you wish to pretty-print a histogram for debugging:

logger.debug("origin_fn()", [&histogram](std::ostream& stream) {
stream << "Histogram: \n" << histogram.prettyPrint();
});

The call to the lambda, and thus to histogram.prettyPrint(), will only be performed if the logger will indeed eventually print the message.

Warning
When using printf-style logging methods in templated classes, remember to always cast the value to a known type, or else your code may be invalid for a different type. It is preferrable in such cases to use the C++ lambda stream mechanism. Compare:
template<typename ValType = double> my_function(ValType value) {
// INVALID if ValType=float,int,... (likely crash!) :
logger.debug("my_function()", "value is %f", value);
// Safe, although possibly inappropriate format (say if ValType=int):
logger.debug("my_function()", "value is %f", (double)value);
// Safe:
logger.debug("my_function()", [&](std::ostream & stream) {
stream << "value is " << value;
});
}

Scoped Logger with (Semi-)Automatic Origin Handling

To avoid specifying the origin parameter for repeated calls within the same class or function, you may use a Tomographer::Logger::LocalLogger, where you set the origin once in the constructor and don't specify it later on. Also, you may use it recursively. In the following example, the origin of the log messages are automatically set to "my_function()" and "my_function()/some_callback[lambda]", respectively:

template<typename LoggerType>
int my_function(int value, LoggerType & baselogger) {
auto logger = makeLocalLogger(TOMO_ORIGIN, baselogger);
logger.debug("value is %d", value);
auto some_callback = [&](std::string some_other_value) {
auto innerlogger = logger.subLogger("some_callback[lambda]");
innerlogger.debug([&](std::ostream& str) {
str << "Inside callback: " << some_other_value;
});
};
some_callback("42");
}

A more complete example is provided on the documentation page for Tomographer::Logger::LocalLogger.

Querying/Setting the Logger Level

Most loggers store their own level. This might not be the case, however, for example for a proxy logger which relays calls to another logger. Such loggers don't "store" their runtime level but are capable of querying it. This is controlled by the logger traits, see Write a New Custom Logger.

Any logger may be directly queried whether a message at a given log level will be emitted or discarded:

if (logger.enabledFor(Tomographer::Logger::INFO)) {
logger.info(...); // will emit message and not discard it
}
Note
In order to prepare a log message only if it is to be displayed, it is preferable not to use enabledFor(), but to provide a callback which accepts a reference to a C++ stream as explained above. In this case, the callback is only invoked if the message is actually going to be emitted, and can take into account more specific message filtering (such as filtering by origin).

The level of a logger, stored or queried, may be obtained with the Tomographer::Logger::LoggerBase<Derived>::level() method. But don't abuse of this, usually there is no need to query the level of a logger, and it is much preferable to see if the logger is enabled for a particular level with Tomographer::Logger::LoggerBase<Derived>::enabledFor().

Also, by default there is no public setLevel() method, in case your logger's level is statically fixed or otherwise can't be changed, or if you need a thread-safe logger. Some classes provide however their own API for changing the logger level: For example, FileLogger provides a method setLevel(level).

Specific topics: