Wednesday, August 27, 2014

C++ Logging: EasyLogging++

The Basics Of EasyLogging++

I wanted a basic logging library for C++. The first one I looked at required I first install Java to be able to compile it. Eh? For a modern C++ library? So then I added “header-only” to be key requirements. And shortly after that I sadly started to resign myself to writing my own. Then, luckily, I stumbled across EasyLogging++.

I retrofitted a couple of projects today, stripping out the ad hoc logging, and replacing it with this. The documentation is good, but gets lost in the details some times, and I felt a simpler tutorial was needed. This is my attempt at it:

Here is the Hello World example:
 
#define _ELPP_THREAD_SAFE
#include "easylogging++.h"
_INITIALIZE_EASYLOGGINGPP

int main(int,char**){
LOG(INFO)<<"Hello World!";
}
 
I’ve decided to request it be thread-safe, right from this first example, because most C++11 apps use threads. Remove that line if you definitely have a single-threaded application. (Speaking of which, this library is C++11 only; but there is a link on their website to an earlier version that supports older C++).

The above program prints “Hello World!” to stdout. But, obviously, you want to log to a file. And for a project of any worthwhile size you will end up with a logging configuration file anyway, so let’s add one now. Here is “logging.conf”:
 
-- default 
* GLOBAL:
    TO_FILE = true
    FILENAME = "info.%datetime{%Y%M%d}.log"
    TO_STANDARD_OUTPUT   =  false
* WARNING:
    TO_STANDARD_OUTPUT   =  true
* ERROR:
    TO_STANDARD_OUTPUT   =  true
* FATAL:
    TO_STANDARD_OUTPUT   =  true
 
Here I am saying I want to have one log file per day, using the YYYYMMMDD datestamp in the log filename, and that it will store messages of all log levels. I’m also saying that I want TRACE and INFO messages to only go to the file, but WARNING, ERROR and FATAL to go to both the file and stdout. There may be more elegant ways to do that, but the above works.

You use the config file by adding just one line at the start of main():
 
#define _ELPP_THREAD_SAFE
#include "easylogging++.h"
_INITIALIZE_EASYLOGGINGPP

int main(int,char**){
el::Loggers::configureFromGlobal("logging.conf");
LOG(INFO)<<"Hello World!";
}
 
That code will write “Hello World\n” to e.g. “info.20140827.log”
 
And that is all you need to know; all your other questions will be answered by the documentation. Do please spend some time with the documentation as there is a lot of functionality in this library (e.g. Conditional logging, Occasional Logging, log output of STL containers, log output for your own classes, datestamps to customizable sub-second accuracy, run-time disabling of certain log levels, and even more.)

One feature it does not have, that I wanted, is an asynchronous log queue. I.e. a thread grabs the lock just long enough to push a string on to a queue, with another dedicated thread doing the actual writing to disk. This makes sure your worker threads do not get caught up waiting for a lock because another thread is waiting for disk I/O to finish. However another feature, that EasyLogging++ does have, lessens the impact of this: it only flushes to disk every N log messages. So effectively strings are being pushed to a queue, and it is only once every N times that a thread gets caught waiting for disk I/O to finish. N defaults to 256; I reduced it in my config file to 5, which gives a fair balance between thread wait and log latency (and the risk of losing log messsages). This is not as good as an asynchronous log queue, but I can live with it.
Written with StackEdit.

Saturday, August 16, 2014

Using C++11 std::future to push data from producer to multiple consumers


This is an example of using C++11's std::future to move data from a data producer to multiple consumer threads, in a very stable and thread-safe way. And hopefully it is at least as efficient as the alternatives:

http://stackoverflow.com/a/25339704/841830

Critiques of my approach (or of the alternatives) are very welcome.