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.