Design

Logging In C++

Source Code Accompanies This Article. Download It Now.

Logging is a critical technique for troubleshooting and maintaining software systems. Petru presents a C++ logging framework that is typesafe, thread-safe, and portable.

Going Generic

Another issue with the implementation we built so far is that the code is hardwired to log to stderr, only stderr, and nothing but stderr. If your library is part of a GUI application, it would make more sense to be able to log to an ASCII file. The client (not the library) should specify what the log destination is. It's not difficult to parameterize Log to allow changing the destination FILE*, but why give Log a fish when we could teach it fishing? A better approach is to completely separate our Log-specific logic from the details of low-level output. The communication can be done in an efficient manner through a policy class. Using policy-based design is justified (in lieu of a more dynamic approach through runtime polymorphism) by the argument that, unlike logging level, it's more likely you decide the logging strategy upfront, at design time. So let's change Log to define and use a policy. Actually, the policy interface is very simple as it models a simple string sink:

static void Output(const std::string& msg);

The Logclass morphs into a class template that expects a policy implementation:

A note for multithreaded applications: The Output2FILE policy implementation is good if you don't set the destination of the log concurrently. If, on the other hand, you plan to dynamically change the logging stream at runtime from arbitrary threads, you should use appropriate interlocking using your platform's threading facilities, or a more portable wrapper such as Boost threads. Listing Three shows how you can do it using Boost threads.

Needless to say, interlocked logging will be slower, yet unused logging will run as fast as ever. This is because the test in the macro is unsynchronizeda benign race condition that does no harm, assuming integers are assigned atomically (a fair assumption on most platforms).

Compile-Time Plateau Logging Level

Sometimes, you might feel the footprint of the application increased more than you can afford, or that the runtime comparison incurred by even unused logging statements is significant. Let's provide a means to eliminate some of the logging (at compile time) by using a preprocessor symbol FILELOG_MAX_LEVEL:

This code is interesting in that it combines two tests. If you pass a compile-time constant to FILE_LOG, the first test is against two such constants and any optimizer will pick that up statically and discard the dead branch entirely from generated code. This optimization is so widespread, you can safely count on it in most environments you take your code. The second test examines the runtime logging level, as before. Effectively, FILELOG_MAX_LEVEL imposes a static plateau on the dynamically allowed range of logging levels: Any logging level above the static plateau is simply eliminated from the code. To illustrate:

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!