LibKISSlog

A trivial but powerful C++ logging template-library.

Download .zip Download .tar.gz View on GitHub

The KISS Logging template-library for C++

LibKISSlog is a trivial light-weight C++ template library that was designed and written according to the KISS ( Keep It Simple & Straightforward) principle. The library leans heavily on the STL for keeping its implementation as simple as its usage, and tries to provide C++ developers with a light-weight, paradigm-pure and flexible alternative to logging libraries that use design and/or implementation decisions that at least the author of LibKISSlog believes to be questionable.

Using libKISSlog

Header and statics.

If your application is to be a single threaded application, or you are using a hand written custom raw logger that is inherantly thread safe, than libKISSlog is a header only library. Simply add the file kisslog.h to one of your inc directories and in your program add an include for it.
#include <kisslog.h>
It is sugested that for single threaded application you'd add a simple typedef to your program right after including kisslog.h.
typedef kisslog::threading::SINGLE concurrency_mode_t
When you plan to log from several threads to a non explicitly thread safe raw logger such as the built-in syslog raw logger, than you should do two things. First you should make sure that the file kisslog-mt-static.cpp will get linked with your program. This file contains a single static definition that is needed in multi threaded operation. Next to this you should change the typedef to:
typedef kisslog::threading::MULTI concurrency_mode_t

Typedeffing and instantiating your raw logger

The first thing you will need to do when using libKISSlog is to choose and typedef a propper raw logger. Alternatively you may define your own raw logger, but libKISSlog also comes with two built-in raw loggers that might suite your needs.

Typedeffing a syslog raw logger

If your software targets only POSIX (Linux,*BSD,*NIX,etc) systems and not for example MS-Windows, and your software is meant to be some kind of back-end, server, appliance or other type of daemon type software, than the syslog raw logger is probably most suited to your needs. In order to use a syslog logger as our raw logger we shall define a typedef for it.
typedef kisslog::rawlogger::sysloglogger<kisslog::facility::USER,concurency_mode_t> raw_logger_t
Note that this typedef uses two template parameters. The first template parameter is the so called syslog facility that the rawlogger should use. The libKISSlog library defines USER,DAEMON and LOCAL0 .. LOCAL7 as valid facilities for the syslog raw logger. Have a look at the syslog manual to determine what facility is most suited for your program. The second template argument is the threading mode that the raw logger should use. It is sugested that you always use the raw_logger_t typedef that you defined for your program so you can switch concurency mode in just a single place. In your program you can than instantiate your raw logger:
raw_logger_t rawlogger("demo")
Depending on your system syslog configuration, the constructor argument may be used as a prefix for all your log messages.

Typedeffing the ostream raw logger

If the syslog raw logger is not suitable for your needs and your logging needs don't require wide string support, than alternatively the ostream raw logger may be an option for you. A typedef for an ostream raw logger would look something like:
typedef kisslog::rawlogger::ostreamlogger<concurrency_mode_t> rawlogger_t;
Again we use the concurrency_mode_t as template parameter in our typedef for the rawlogger_t. In your program you can than instantiate your raw logger:
rawlogger rlogger(std::cerr);
This example uses std::cerr as output stream, but you could use any ostream, for example for logging to a file or a network socket.

Writing your own custom raw logger.

If neither of the two built-in raw loggers are suitable for your logging needs, for example you may not like the formatting it does or you might want to log wide strings, than writing up your own raw logger class might be needed. Doing so shall give you full control at the price of being a litle bit work to implement.

A raw logger can either be a normal class or a template class that implements a log template method. LibKISSlog defines a set of log levels that are to be used by libKISSlog as template parameters for the log method of the raw logger.

Here would be an example of a dumb and useless raw logger that logs to cerr with a line number prepended:

class rawlogger_t {
       size_t mLineno;
   public:
       rawlogger_t():mLineNo(0){}
       template <typename T>
       void log(std::string line) {
          mLineno++;
          std::cerr << mLineNo << ":" << kisslog::severity::asPrefix<T,char>() << line;
       }
};

One point of interest in the above code is the usage of kisslog::severity::asPrefix<T,char>(). This basically returns a string representation of the severity level T.

threading

Note that if you write a raw logger that you want to re-use in several projects, especially if you are using thread-unsafe API calls and want to be able to re-use your custom raw logger in both single threaded and multi threaded application, it is sugested that you implement your raw logger as a template class basically like this:

template <typename G>
class base_rawlogger_t {
       size_t mLineno;
   public:
       base_rawlogger_t():mLineNo(0){}
       template <typename T>
       void log(std::string line) {
          threading::guard_if_needed<G> myguard;
          mLineno++;
          std::cerr << mLineNo << ":" << kisslog::severity::asPrefix<T,char>() << line;
       }
};

typedef base_rawlogger_t<concurency_mode_t> rawlogger_t;

Now before you instantiate your logger its a good idea to create a typedef approprite for your program:

typedef base_rawlogger_t<concurrency_mode_t> rawlogger_t;
wide string support
Next to supporting multiple concurency modes you might have an application that uses wide strings instead of char based strings. To accomodate this, libKISSlog, while not supplying wide string based built-in raw loggers, does make it trivial to write your own. Lets look what a wide string version of our previous raw logger looks like:
template <typename G>
class base_rawlogger_t {
       size_t mLineno;
   public:
       base_rawlogger_t():mLineNo(0){}
       template <typename T>
       void log(std::wstring line) {
          threading::guard_if_needed<G> myguard;
          mLineno++;
          std::cerr << mLineNo << ":" << kisslog::severity::asPrefix<T,wchar_t>() << line;
       }
};
typedef base_rawlogger_t<concurency_mode_t> rawlogger_t;

Typedeffing and instantiating our real logger

So far all we've basically done is defining a type for and instantiating a raw logger that should be used by our actual logger. The kisslog::logger class template takes 1 to 3 template arguments. The first template argument is the type of the raw logger. The second template argument is the minimal log level for this logger for what log lines will actually get logged. This second template parameter basically implies that you can switch between doing fully verbose debug logging and logging that only does WARNING and above by changing a typedef. The default level is WARNING. The third and last template argument is the character type this logger is for. This type should match the type used for the creation of the raw logger. A real logger typedef may look something like:

typedef kisslog::logger<
          raw_logger_t,
          kisslog::severity::WARNING,
          wchar_t
        > warnlogger_t;

Now that we have our typedefs in order (myrawlogger and warnlogger), we can instantiate our logger and use it.

myrawlogger rawlogger;
warnlogger_t warnlogger(rawlogger);
..
warnlogger.err() << L"Logging is simple " << 42 << std::endl;
warnlogger.debug() << L"This line won't be logged." << std::endl;

The logger defines a small set of methods, one for each log level, that all return an ostream reference. Given our typedef for warnlogger_t defining kisslog::severity::WARNING as minimal level for logging, the warnlogger.err() method will return a stream reference that maps to our raw logger being invoked, while the warnlogger.debug() invocation will return a null stream. The zero argument logging methods that a logger defines are:

  • debug()
  • info()
  • notice()
  • warning()
  • err()
  • crit()
  • alert()
  • emerg()

Word of caution regarding debug logging and performance.

Please note that while our logger will return null streams that are relatively efficient, the sheer volume of debug logs might turn many operator<<() invocations done in inner loops into a major performance bottleneck. Addressing these issues is beyond the scope of libKISSlog or any other logging library that chooses to use a streaming interface. It is thus sugested that you should probably :

  • Use old and ugly #ifdef constructs for debug logs in inner loops.
  • Never use logs above the debug level in your inner loops.

Logging code in inner loops should thus look something like this:

   #ifdef DEBUG_INNER_LOOPS
     debuglogger.debug() << "Log in performance critical inner loops."
   #endif

Passing loggers around

The libKISSlog API does out of principle not supply any singleton interface and does not use any kind of global mutable state under the hood. Instead, libKISSlog promotes the use of constructor based dependency injection. To accomodate this, any concrete kisslog::logger class is a subclass of the abstract kisslog::logger_base class. This basically allows for constructs like:

class Foo {
    kisslog::logger_base &mLogger;
  public:
    Foo(kisslog::logger_base &logger):mLogger(logger) {}
    void testlog() {
        mLogger.debug() << "Foo has something uninteresting to say" << std::endl;
        mLogger.crit() << "Foo is in big problems." << std::endl;
    }
};
..
Foo foo(warnlogger);
..
foo.testlog();

Note that Foo does not need to know what type of logger its being passed here. This also, next to making dependencies more explicit (what is good), allows for flexibility in what level of logging is used by what sub systems without using bloated Log4J style frameworks, simply by passing different loggers (with different minimum log levels) to the constructors of the different sub-systems:

Foo foo(warnlogger);
Bar bar(debuglogger);
..
foo.testlog();
bar.testlog();

In the above situation, foo will not log any DEBUG, INFO or NOTICE level log lines, while bar will.

Don't like constructor injection?

While the author of libKISSlog believes that singletons and other types of global mutable state are to be avoided at all cost within any software project that aims to create robust, testable and trustworthy software, there is a chance that you as a (potential) libKISSlog user either don't care about robustness, testability or trustworthyness of your software or dont agree with my assertions. If so, you may wish to have a look at the loki library. The loki library contains the facilities thay you may use to create singleton access to a libKISSlog logger. The use of singletons is discouraged, but if you insist on shooting yourself in the foot by introducing excesive global mutable state into your code-base, than using loki would seem like a way to do that.

Why choose libKISSlog?

There are many C++ and C logging libraries around, so why would you pick libKISSlog and not one of the other ones, or do you need a logging library at all, or could you suffice just using the facilities that the STL already offers? Lets look at a long list of factors that may be important for you when choosing a particular library for your project. We will start with a list of factors where libKISSlog comes out good.

No singletons

Many logging libraries use singletons or other forms of global mutable state. While the singleton pattern is a wel known GoF design pattern. A pattern however that has turned out to be a controversial pattern that some consider an anti-pattern. There are issues with testing, issues with multi-threading, problems from a dependency point of view. If you also view the singleton pattern and other forms of global mutable state like global variables as bad practice, than a logging library like libKISSlog may be right for you.

No macros

Many C++ coding standards explicitly ban the use of macros. Logging libraries seem to be a place however where the use of macros is somehow accepted. The question is, is a piece of basic program infrastructure like a log library worth letting you sane coding guideline slide? If you are convinced about the sanity of banning macros, using a macro free logging library like libKISSlog may be right for you.

No run time filtering

C++, other than for example Java, is a language where one of the base paradigms is the use of different techniques for moving much of the CPU work to compile-time. If you believe that filtering of log messages based on severity levels is something that should happen compile-time, than a template library like libKISSlog may be right fopr you.

No big framework

Java is a language of frameworks, people moving from Java to C++ often miss their frameworks, and some C++ logging libraries have arissen from this. This while people who moved from more low level languages to C++, or target more minimal hardware, often have a strong dislike for these in their view bloated frameworks. If you dislike large framework style libraries, than a small and light logging library like libKISSlog may be right for you.

Friendly stream based interface

Streams are undenieable the most programmer friendly way of doing output. A stream however that does not produce any output, for example as a result that it is a null-stream, will as a result of its usage pattern, that combines it with somethime expensive operator<<() invocations, makes the use of streams as basic interface relatively expensive. In much of the code-base this wont be a problem, but in performance critical inner loops it most definetely will. If you believe that inner loop logging is a special case, and a questionable use of logging anyway, that doesnt warant abandoning the friendly stream based interface, than a logging library like libKISSlog with a friendly stream interface may be right for you.

Template library

If you enjoy using the STL and maybe some loki or boost library, than you will probably prefer a C++ template library like libKISSlog over a non template C++ library.

Support for multi-threading

If you are going to log from multiple threads, than you will want a library that like libKISSlog supports multi-threading.

Support for efficient single threading

If you sometimes want to log in multi-threaded programs, but at other times don't want to pay the efficiency price of thread-safe code when logging from a single thread, than you will probably want a library that like libKISSlog makes thread safety optional.

Extendable

If you are the type of user that wants to be able to plug in his/her own log channels, for example logging to an encrypted stream, using JSON formatting, etc, than you will want to use an extendable logging solution like libKISSlog offers.

Per sub-system log-level filtering

If a single filter on log level is not sufficient for you but you want to fine controll what sub-systems would for example log at DEBUG level and above and what sub-systems only at NOTICE level and above, than you will want a logging library that like libKISSlog allows this fine level of control.

No resource management issues

In C++ we have the RAII idiom for doing resource management. Unfortunately some log library authors aparently havnt discovered the joy of using RAII and thus of writing memory leak free code. This should not be a question of taste, you do not want to use any library that leaks memory.

Support for syslog on POSIX systems

On production server software running on POSIX systems, the convention for logging is to use syslog. If your code targets these types of systems you will want a logging library like libKISSlog that supports logging to syslog.

Why NOT choose libKISSlog and alternative libraries.

Next to reasons why you may want to use libKISSlog for your project, there are also reasons why this library may not be the right solution for you.

No built-in asynchonous logging

If you have a system with many concurent threads, the overhead of synchonous metaphor based logging may become a serious bottleneck. Currently libKISSlog does not support the more efficient actor model based asynchonous logging model that is suitable for highly concurent applications. If asynchonous logging is esential, you may want to look at g2log.

Not for Log4J fans

If you have a Java background and LOVE Log4J, than libKISSlog will bring great sorow for you. The libKISSlog library is absolutely nothing like Log4J. If this is the case, there are quite a few bloated Log4J libraries you can choose from, including apaches Log4Cxx.

Inner loop logging

If you do feel inner loop logging is esential, and the use of #ifdef is a non solution, than libKISSlog is not what you want. You may like a logging library like Pantheios instead.

You may not actually need a full blown logging library.

If you look at the implementation of libKISSlog you will see that although it has a great list of features, its actually a quite simple library. The reason for this is that it makes use of what the STL has to offer, most notably the use of an subclassing of std::streambuf. When your logging demands are very specific, than although libKISSlog is prety light, it may be overkill still. You may just want to look at the libKISSlog sources and create your own simple and tailored logging library using these STL facilities yourself.