Logging framework using c++
Logging is an important utility for any application. This provides user what is happenning under the hood when the application is running. Having a robust logging framework will help the user to understand what exactly application is doing . If something goes wrong logging the events will help one to troubleshoot the errors easily. Especially when the issues are reported from the production environment to support/Dev team.
There are many ways to design and implement logging framework. This article focuses on very simple approach as to how to develop one and glue it with any custom application.
C++ is the language that is chosen to implement , however this can be extended to any objected oriented language.
Pre-requisites / Requirement.
- Events should be logged to console.
- Events should be logged to a log file , created at standard location.
- It should contain the logging levels to indicate the severity of message.
- It should contain the timestamp for each logging message to help in filetring messages based on timestamp.
- User should be able to set the priority to filter out particular level of logs.
- Handle the logging when used by multiple threads.
The current article address all the above mentioned points in its design.
- Define enum to indicate log levels :
INFO — Information
TRACE — Tracing the message , like funtion name , line number.
DEBUG — Debugging information
WARN- Warning message to indicate user to take necessary action.
ERROR — when application is crashed.
Define a logger class indicating
a. log level , initial value and ability to set the value given by user.
b. Methods to print the logs depending on the log level. These methods should take the message and the arguments to log them to console . One way to do this is to use variadic templates, since this will allow us to take variable set of arguments from the user during logging.
- To set the log level :
static void set_log_level(LogLevel level) {
m_level = level;
}
2. Example method using variadic template
template <typename… Args>
static void InfoLog(const char* message, Args… args)
{
if (m_level <= LogLevel::INFO) {
printf(“[INFO]:\t”);
printf(message, args…);
printf(“\n”);
}
}
Driver code :
In order to have single instance of the class thought the application lifecycle, methods are made static. Here is how the driver code looks like.
#include “Logger.h”
int main()
{
Logger::set_log_level(LogLevel::TRACE);
Logger::InfoLog(“infolog %d”, 1);
Logger::DebugLog(“debuglog message %d”, 2);
Logger::CriticalLog(“Logging a critical message”);
return 0;
}
Following link contains complete implementation compiled in visual studio , this solution can be loaded and compiled on windows to see the sample output :
Now that we have a base level implementation , we want to extend this to handle multiple threads.
While running a first example — module-1 , we noticed that when the framwork is accessed by multiple threads , the output is jumbled . Since the output stream by default not a thread safe.
Introduce a locking mechanism to prevent the jumbled output, typically using mutex lock. This provides a good locking and sync mechanism. only drawback here is programmer has to unlock if crical section is locked explicitly. Else this will lead to a Deadlock situation causing application to go for a hung state or crash state.
Hence , RAII (Resouce acquisition is initialization) is used to lock and unlock automatically when the control goes out of scope. Here we have used lock_guard(supported from c++11) we can also use scoped_lock(supported from c++17).
Here is the snippet describing the same :
Driver code to test the same will look soething like as below :
code for the same can be found here :
The last part to is logging the details to a file and adding time stamp. There is slight improvement done in the latest code.
In the previous Implementations we used static methods and variables to keep single instance of logger in the application lifecycle. The same can be achieved using singleton pattern , this saves us from using too many static variables and methods providing an interface to maintain single instance of class thourght the application lifecycle.
File logging is enabled at the beginnging of the application , which will checks if the file is present to log , if present it just open up else creates a new file to log. In the end of application, disable logging is called to close the file logging. This implementation currently is windows specific, but can be made platform independant.
This code handles multi-threaded logging to a file with all necessary details supplied by the user.
code link to follow :
Hope this article helps in getting basic understanding of how a loggging module works.
Future article will cover :
- New methodologies to make the framwork more robust.
- support platform independence.
- Distributed logging.
- Log Analysis
Happy learning :)