A simple, customized logger, based on Boost.Log v2

Todays applications grow rapidly, complexity increases and finding bugs becomes more and more difficult. Especially for multi threaded applications, for applications that heavily depend on asynchronous external events or for applications that you don’t have local access to, an appropriate logging facility is invaluable for tracking down bugs.

Boost.Log (v2) is a powerful C++ library that provides a simple way to integrate an extensible and performant logging facility in your application.

For simple applications, Boost.Log provides predefined and easy to use macros that don’t need any configuration: The “trivial logging” macros can be used right out of the box and this may be sufficient for small applications.

But the real strength of Boost.Log is its flexibility - you can build nearly any logging system yon can think of with it. Features like attributes, filters and the possibility to create custom backends makes Boost.Log incredibly powerful.

I don’t want to go into detail about how this all can be done - the documentation is quite good anyway - but the simple example below could help you to get started to build your own customized logger based on Boost.Log.

It basically shows how to create a custom global logger, add a sink with two backends and a filter, add attributes and formats the log message.

The complete source code is available on github. It’s tested on Linux+GCC 4.9 and on Window7+Visual Studio 2013.

simplelogger.h:

#ifndef simpleLogger_h__
#define simpleLogger_h__

#define BOOST_LOG_DYN_LINK 1 // necessary when linking the boost_log library dynamically

#include <boost/log/trivial.hpp>
#include <boost/log/sources/global_logger_storage.hpp>

// the logs are also written to LOGFILE
#define LOGFILE "logfile.log"

// just log messages with severity >= SEVERITY_THRESHOLD are written
#define SEVERITY_THRESHOLD logging::trivial::warning

// register a global logger
BOOST_LOG_GLOBAL_LOGGER(logger, boost::log::sources::severity_logger_mt<boost::log::trivial::severity_level>)

// just a helper macro used by the macros below - don't use it in your code
#define LOG(severity) BOOST_LOG_SEV(logger::get(),boost::log::trivial::severity)

// ===== log macros =====
#define LOG_TRACE   LOG(trace)
#define LOG_DEBUG   LOG(debug)
#define LOG_INFO    LOG(info)
#define LOG_WARNING LOG(warning)
#define LOG_ERROR   LOG(error)
#define LOG_FATAL   LOG(fatal)

#endif

simplelogger.cpp:

#include "simpleLogger.h"

#include <boost/log/core/core.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/trivial.hpp>
#include <boost/core/null_deleter.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <fstream>
#include <ostream>

namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;

BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level)

BOOST_LOG_GLOBAL_LOGGER_INIT(logger, src::severity_logger_mt) {
    src::severity_logger_mt logger;

    // add attributes
    logger.add_attribute("LineID", attrs::counter(1));     // lines are sequentially numbered
    logger.add_attribute("TimeStamp", attrs::local_clock());             // each log line gets a timestamp

    // add a text sink
    typedef sinks::synchronous_sink text_sink;
    boost::shared_ptr sink = boost::make_shared();

    // add a logfile stream to our sink
    sink->locked_backend()->add_stream(boost::make_shared(LOGFILE));

    // add "console" output stream to our sink
    sink->locked_backend()->add_stream(boost::shared_ptr(&std::clog, boost::null_deleter()));

    // specify the format of the log message
    logging::formatter formatter = expr::stream
        << std::setw(7) << std::setfill('0') << line_id << std::setfill(' ') << " | "
        << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
        << "[" << logging::trivial::severity << "]"
        << " - " << expr::smessage;
    
    sink->set_formatter(formatter);

    // just log messages with severity >= SEVERITY_THRESHOLD are written
    sink->set_filter(severity >= SEVERITY_THRESHOLD);

    // "register" our sink
    logging::core::get()->add_sink(sink);

    return logger;
}

Our demo application “app” can conviniently write logs by just linking to “simpleLogger” and including “simpleLogger.h”:

app.cpp:

#include "simpleLogger.h"

int main() {
    LOG_TRACE << "this is a trace message";
    LOG_DEBUG << "this is a debug message";
    LOG_WARNING << "this is a warning message";
    LOG_ERROR << "this is an error message";
    LOG_FATAL << "this is a fatal error message";
    return 0;
}

When executing the “app”, it writes the following lines to our two registred backend streams - console and logfile.log (trace and debug logs are filtered out by our sink filter):

0000003 | 2014-12-14, 19:54:53.775614 [warning] - this is a warning message
0000004 | 2014-12-14, 19:54:53.775747 [error] - this is an error message
0000005 | 2014-12-14, 19:54:53.775781 [fatal] - this is a fatal error message

If you found this helpful or have any kind of feedback, please leave a comment below.

comments powered by Disqus