
There are a lot of Q & A sites in the network where questions from the category are asked:
- Offer a C ++ logger? (C ++ logging framework suggestions)
- What is the most efficient thread safe C ++ logger? (What is the most efficient thread-safe C ++ logger)
- Logging library for c games
- Asynchronous thread-safe C ++ logger? (Asynchronous thread-safe logging in C ++)
People share their experience and knowledge, but the format of such sites allows only to show the personal preferences of the respondent. For example, one of the most productive loggers is most often called Pantheios, which even the manufacturer’s tests spend more than 100 seconds to write 1M log lines, on modern hardware this is about 30 seconds, is it fast?
')
In this article, I will compare the most famous and well-deserved loggers of recent years and several relatively young loggers by more than 25 criteria.
Motivation
In almost every project I know, logging appeared sooner or later, and the engineers asked themselves “how to solve this problem?”, Someone rummaged in Q & A sites and got answers “I got a logger X, like normal flight” (1), someone then he wrote his logger (2), and someone had reserved patience and studied a whole bunch of loggers about their interests and a week later another made his choice (3).
This article is for all 3 groups:
- For the first group, this article will give a more extensive comparison of loggers than the recommendation “logger X is the best for me”
- For the second group, the article will give an idea of ​​the current level of technology and may tip the scales in the direction of “easier to use ready-made” or “oh, a lot of work, but I can do better” and who knows, maybe another year we will see a breakthrough in this area.
- For the third and most pedantic group, I hope this article will save at least one of the 3 weeks of research, across the industry it can be an impressive number of man-hours
NB The article is quite voluminous, so if you decide to read, please be patient!
Loggers and their basic parameters
The choice of loggers for comparison is a troublesome and not simple matter, in any case the questions will arise: “Why hasn’t logger X been considered?” What can I say, the logic was simple - take 4 well-known loggers and 4 relatively young and “hungry.”
But even comparing these 8 candidates took more than 3 weeks of smoking docks, issues, reading forums, writing tests and collecting results.
One way or another, if a very important logger was missed, you can update the article. The review got:
- Pantheios
- Glog
- log4cpp
- P7
- G3log
- Spdlog
- Easylogging
- Boost.Log (part of the huge Boost library)
General characteristics of selected loggers
| Persons | Languages | Update | Platf. | Comp. |
---|
Pantheios | BSD | C ++ | 2010 | Windows, * nix, OS-X | VC ++, GCC, Intel, Borland, Comeau, Digital Mars, Metrowerks |
---|
Glog | 3-clause BSD | C ++ | 2018 | Windows, * nix, QNX | VC ++, GCC, clang, intel |
---|
log4cpp | LGPL | C ++ | 2017 | Windows * nix Solaris | VC ++, GCC, Sun CC, OpenVMS |
---|
P7 | LGPL | C ++, C, C #, Python | 2018 | Windows * nix | VC ++, GCC, clang, MinGW |
---|
G3log | Public Domain | C ++ 11 | 2018 | Windows * nix | VC ++, GCC, clang |
---|
Spdlog | MIT | C ++ 11 | 2018 | Windows, Linux, Solaris, OS-X, Android | VC ++, GCC, Clang |
---|
Easylogging | MIT | C ++ 11 | 2018 | Windows, Linux, Solaris, OS-X, Android | VC ++, GCC, Clang, Intel |
---|
Boost.Log | Boost ( 1 ) | C ++ | 2016 | Windows Linux ( 2 ) | VC ++, GCC, Clang ( 3 ) |
---|
- Own license www.boost.org/LICENSE_1_0.txt
- www.boost.org/doc/libs/1_62_0/libs/log/doc/html/log/installation.html
- There are a number of other platforms and compilers on which Boost can be compiled, but they are not officially supported and will probably require additional effort.
Documentation and dependencies
It is difficult to dispute the importance of documentation for complex projects, modern loggers with simple projects can be called a big stretch and the availability of good documentation sometimes significantly speeds up the introduction and correction of errors:
| Documentation | Dependencies |
---|
Pantheios | Full (API + usage) | STLSoft |
---|
Glog | Rudimentary, almost absent | Google gflags, weak dependency ( 1 ) |
---|
log4cpp | Generated (Doxygen) (API only) | Boost, weak dependency ( 1 ) |
---|
P7 | Full (API + usage) | Not |
---|
G3log | Basic (common usage methods) | Not |
---|
Spdlog | Basic (common usage methods) | No, header file only ( 2 ) |
---|
Easylogging | Basic (common usage methods) | No, header file only ( 2 ) |
---|
Boost.Log | Basic (common methods of use) plus a basic description of some classes and their methods | Boost |
---|
- Low dependency - part of the code depends on third-party solutions, but does not prevent compilation, and only partially restricts the functionality.
- Header file only - the library is distributed as one or more header files that must be included in each source file of your program; in the case of Easylogging, the compilation speed is significantly reduced. But there is a way out of this situation - to compile these projects in the form of a library and connect it already, though this will require additional time to create a project.
Logger type, memory control and thread safety
At the moment, there are widespread 2 approaches in logging:
- Synchronous - calling Log (..) functions and writing to a file occurs synchronously from one thread
- Asynchronous - a call to the Log (..) function only updates the message queue, and another thread is writing, thus reducing the call time of the Log (..) function from the user stream, and as a result, the maximum performance of the user stream is reached and the log call delays are minimized (..) functions.
However, there are nuances of understanding asynchrony by different manufacturers of logging libraries.
- Type number 1: Some believe that the call to the Log (..) function must be atomic, and thus the order of the log messages in the file will be consistent in time 00:00 -> 00:01 -> 00:02 and so on.
- Type # 2: Others believe that in order to achieve maximum performance, you can sacrifice the atomicity of the call to the Log (..) function and accept the fact that the log messages in the file will be intermittent, for example 00:00 -> 00:05 -> 00 : 01.
Personally, I adhere to the point of view of the first group on asynchronous logging, since the analysis of mixed logs cannot be pleasantly called, especially if it is a large log file and not single logs are mixed but groups of logs of several hundreds or thousands of elements.
Another important aspect of asynchronous logging is control over memory allocation, since in order for your logger to be asynchronous, you must save data in a buffer and write from another thread. And here the important trifle is hidden - what size of buffers will be optimal and can the user influence this parameter? The question is not at all idle, as some of the tested loggers allocated hundreds of megabytes for their needs.
| Type of | Memory control | Flow safety |
---|
Pantheios | Synchronous | not | Yes |
---|
Glog | Synchronous | not | Yes |
---|
log4cpp | Synchronous | not | Yes |
---|
P7 | Asynchronous, (type 1) ( 2 ) | exact (in 1Kb increments) | Yes |
---|
G3log | Asynchronous, (type 2) ( 3 ) | no ( 5 ) | Yes |
---|
Spdlog | Asynchronous, (type 2) ( 3 ) | partial ( 6 ) | Yes |
---|
Easylogging | Synchronous ( 1 ) | not | no ( 4 ) |
---|
Boost.Log | Synchronous, Asynchronous, (type 2) ( 7 ) | no default ( 8 ) | Yes |
---|
- Asynchronous mode is in experimental state.
- Timestamps and messages are not mixed.
- Timestamps and messages may be shuffled.
- The default is not available, you must activate the macro ELPP_THREAD_SAFE
- Uncontrolled memory allocation, with high loads can allocate hundreds of megabytes, author's comment: kjellkod.wordpress.com/2014/08/16/presenting-g3log-the-next-version-of-the-next-generation-of-loggers
This is a std :: queue is used internally that is a std :: deque. It is unbounded but much more memory tolerant than a std :: vector. Internally the queue is wrapped inside the shared_queue.hpp.
- You can set the queue length in the elements, the size of the element in the initial form - 88 bytes + text message (30-160 bytes). The author recommends setting the queue size to 1 million messages for optimal performance, which translates into memory consumption from 120 megabytes to 250 megabytes only for the logging library.
- The order of messages is not guaranteed by the library by default, time stamps can be mixed in the final file when logging from different streams: “Why does the log record have a multithreaded application?” The author of the library suggests using “unbounded_ordering_queue” to overcome this problem using special . attribute "RecordID" which will be sorted
- By default, the library uses “unbounded_fifo_queue” which results in an uncontrollable increase in memory consumption during intensive logging. It is possible to use with the additional setting “bounded_fifo_queue”. In this case, you can set the queue length in the elements. Each element (record in library terminology) occupies about 1KB
Process Failure Handling
Proper handling of process failures (crash handling) is primarily important for asynchronous loggers, since part of the data is stored in buffers and if they are not stored in time, perhaps the most valuable data will be lost right before the failure.
Three approaches are common in intercepting falls:
- Automatic, the library itself adjusts all vectors and will do everything itself, a big disadvantage of such a solution is the paucity of intercepted signals, as well as interference for an application that perhaps would like to handle the crash itself in order to save a crash dump file or flush buffers.
- Manual — the library provides primitives for intercepting crashes and flushing buffers, and the application itself decides when and how to install an interceptor and what to do in the event of a crash.
- Let him fall, everyday life
Pantheios | Not |
---|
Glog | automatic, only under Linux ( 1 ) |
---|
log4cpp | Not |
---|
P7 | manual and automatic ( 2 ) |
---|
G3log | automatic, Linux only ( 3 ) |
---|
Spdlog | No ( 4 ) |
---|
Easylogging | automatic, only under Linux ( 5 ) |
---|
Boost.Log | No ( 6 ) |
---|
- The following signals are intercepted: SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, SIGTERM.
- Intercepted following signals: SIGSEGV, SIGILL, SIGFPE, SIGINT, SIGABRT, SIGBUS, SIGTERM, SIGBUS, PureVirtualCall, VectoredException, Newhandler, InvalidParameterHandler, status_access_, exception_array_bounds_exceeded, exception_datatype_misalignment, exception_flt_divide_by_zero, exception_flt_stack_check, exception_illegal_instruction, exception_int_divide_by_zero, exception_noncontinuable_exception, exception_priv_instruction, exception_stack_overflow
- The following signals are intercepted: SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, SIGTERM, Div by zero, illegal printf, out of bounds, access violation, std :: future_error
- github.com/gabime/spdlog/issues/55 - The defect was “closed” in 2015, i.e. No work is foreseen in this area.
- The following signals are intercepted: SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGINT
- Even for synchronous mode, there is a risk of data loss in the event of a process failure; in the case of asynchronous mode, the risk of data loss is extremely high.
Logging style and output (sink)
The bulk of the libraries supports 2 well-established logging styles:
- Functions with variable argument list (prinf style)
- Overload of the operator “<<”
| Style | Conclusion (Sink) |
---|
Pantheios | Template + overloaded functions F (A), F (A, A), F (A, A, A), ... F (A, <-64->, A) Printf
| File, syslog, console, speech, ACE, COMerror, WinEventLog
|
---|
Glog | Log () << Message | File, syslog, console |
---|
log4cpp | Printf, Log () << Message | File, syslog, console, NT log, IDS / A, OsStream, StringQueue, Win32Debug |
---|
P7 ( 6 ) | Printf | Binary file, console, syslog, text file (Linux: UTF8, Windows: UTF-16) network (own protocol and server ( 2 )), null |
---|
G3log | Printf, Log () << Message | File ( 3 ) |
---|
Spdlog | Printf ( 4 ) | File, syslog, console |
---|
Easylogging | Printf ( 1 ), Log () << Message | File, syslog, console |
---|
Boost.Log | Log () << Message ( 5 ) | File, syslog, console, Win32Debug, WinEventLog, IPC |
---|
- In the case of the use of "Printf" generated an exception, this is probably a temporary problem.
- Own server is used for maximum performance. The server is free, but unfortunately only supports Windows, apparently based on Qt, a request for Linux support was sent to the author. The speed of sending logs over the network in the test configuration was about 3.5 million per second when the CPU was loaded - 13%. Performance tests are discussed in detail in the following chapters.
- The official delivery does not include Sink with support for file rotation and the console, but these extensions can be downloaded github.com/KjellKod/g3sinks
- Format string for Printf library functions is not compatible with the canonical format, which significantly complicates the painless replacement of one logger with another
- Passing a nullptr string as an argument to the logger causes a segmentation fault / access violation
- Most productive Sink: Binary file, Baical
Initialization of logger
Initialization or transfer of parameters is quite an important point because adds flexibility to the logger and eliminates the need to recompile if you decide to change the logging level for example.
Pantheios | Manual (code only) |
---|
Glog | Command line, manual, environment variables |
---|
log4cpp | Manual configuration file ( 1 ) |
---|
P7 | Command line ( 2 ), manual |
---|
G3log | Manual (code only) |
---|
Spdlog | Manual (code only) |
---|
Easylogging | Configuration file, command line, manual |
---|
Boost.Log | Manual configuration file ( 3 ) |
---|
- Detailed and well-organized setting of the parameters of the logger, perhaps the most extensive of all.
- In all other loggers, in order for the command line parameters to be processed, you must transfer them to the logger by hand from the int main (int argc, char * argv []) functions. In this logger, these parameters can be intercepted automatically from any part of the program / module (dll, so).
- The library provides only the most basic primitives for configuration via the configuration file . A more complete discussion of this issue can be found at the link. And as a conclusion
This is a list of It is impossible to give an explicit configuration format description.
Filtering setup
The most common filtering technique is by logging levels, say if the filter is set to ERROR level - then anything that is less than ERROR (TRACE, DEBUG, INFO, WARNING ...) will not be included in the log. This method is very useful for screening out a large amount of unnecessary information at the moment and saving the CPU and disk space.
Pantheios | No ( 1 ) |
---|
Glog | Command line, manual, environment variables |
---|
log4cpp | Configuration file ( 4 ), manual |
---|
P7 | Command line, remotely over the network in real time ( 2 ) ( 3 ), manual |
---|
G3log | No ( 5 ) |
---|
Spdlog | Manual |
---|
Easylogging | Manual ( 6 ) |
---|
Boost.Log | Manual configuration file |
---|
- To organize the filter you need to develop your FrontEnd
- Supported only if the data is sent over the network, in the case of recording to a local file, the server does not have access to the Verbosity level
- In addition to the global level, you can set the levels for each module.
- Hierarchical logging and setting of levels individually for each logger
- By default it is disabled, it is activated by the macro G3_DYNAMIC_LOGGING, then you can manually set the level for all loggers. Significantly reduces performance.
- The support is declared by the manufacturer, but it was not possible to get it to work, during use, the impression was that the function was under development or abandoned.
Unicode support
This part of the testing was one of the saddest, in 2016 the support of unicode in such well-known libraries is still at the level “
not officially ”.
The library is needed in order to save important application data (the user's last name, file path, domain name), and most of the existing ones simply will not allow you to do this if the data does not fit into the trivial char.
Pantheios | Utf-16 ( 1 ) ( 4 ), Utf-8 |
---|
Glog | Not |
---|
log4cpp | Not |
---|
P7 | Windows - UTF-16, * nix - UTF-8, UTF-32 |
---|
G3log | Not |
---|
Spdlog | Not |
---|
Easylogging | Windows Utf-16 ( 2 ), Utf-8 ( 3 ) |
---|
Boost.Log | UTF-8 partially ( 5 ) |
---|
- Almost perfect, except that the final log file does not have a Unicode marker and the encoding in the viewer will need to be selected by yourself.
- Support is stated, but not implemented, unicode characters do not fall into the log file.
- The macro START_EASYLOGGINGPP does not support unicode
- In a single message, you cannot combine an ANSI string, say with UTF-16
- Unicode support is carried out using the Boost.Locale library, impressively wide support is provided for various UTF-8/16/32 formats and other national locales, unfortunately, Boost.Log was only able to get to work with the UTF-8 file format and only using the initialization function synchronous logger "logging :: add_file_log", all other attempts failed and unicode characters did not reach the file, perhaps with enough time, this functionality can be made to work
Access to the logger
In modern libraries, the question “who owns the logger” remains behind the scenes, most often you can write LOG (ERROR) << “My message” and the library takes care of everything itself. This simplicity is achieved using global variables. I will leave ethical use of global variables behind the scenes, after all this is a special case, but the simplicity of using a global variable in the case of a simple application turns against the developer of a complex application consisting of many dynamic or static modules.
Another option to gain access to the logger is to create the object yourself and control its life cycle.
And the last option is a hybrid one, the logger objects are created in manual mode, and then global variables (registry) or shared memory are used, which is common to the whole process including dynamic modules.
Pantheios | Global variables, automatic initialization |
---|
Glog | Global variables, automatic initialization |
---|
log4cpp | Global variables, automatic and manual initialization |
---|
P7 | Shared memory, manual initialization |
---|
G3log | Global variables, automatic and manual initialization |
---|
Spdlog | Global variables, manual initialization |
---|
Easylogging | Global variables, automatic initialization |
---|
Boost.Log | Global variables, automatic and manual initialization |
---|
File rotation
Pantheios | Not |
---|
Glog | The size |
---|
log4cpp | Size ( 2 ) |
---|
P7 | Time, size ( 1 ) ( 2 ) |
---|
G3log | Size, not available by default ( 1 ) |
---|
Spdlog | Size, time (day) ( 1 ) |
---|
Easylogging | The size |
---|
Boost.Log | Time, size ( 1 ) ( 2 ) |
---|
- Each file in the title contains the date and time.
- Supported option is “max. number of files "allowing you to store only the last N files
Time accuracy
Many of the loggers considered in this article were developed with an eye to high performance, with a potential of millions of messages per second. But in addition to high speeds, accurate high resolution time stamps are needed, since If you have a couple of tens or even hundreds of messages in the log file with the same time stamp, this means that some of the information about the execution time has already been lost.
Pantheios | Windows: 10ms ( 1 ), custom back-end can help increase accuracy Linux: theor. minimum value of 1ns, depending on the hardware
|
---|
Glog | Windows: 10ms ( 1 ) Linux: theor. minimum value of 1ns, depending on the hardware
|
---|
log4cpp | Windows: 10ms ( 1 ) Linux: theor. minimum value of 1ns, depending on the hardware
|
---|
P7 | Windows: 100ns Linux: theor. minimum value of 1ns, depending on the hardware
|
---|
G3log | Windows: 1ms Linux: 1us
|
---|
Spdlog | Windows: 1ms Linux: theor. minimum value of 1ns, depending on the hardware
|
---|
Easylogging | Windows: 1ms Linux: 1us
|
---|
Boost.Log | Windows: 10ms ( 1 ) Linux: theor. minimum value of 1ns, depending on the hardware
|
---|
- Sometimes you can get a granularity of 1 millisecond, but much more often a quantum is 10 milliseconds.
Performance
Many loggers from the list in this article state that performance is one of their top priorities.
I took this statement more than seriously and conducted a series of tests:
- in default configuration
- in terms of equal memory usage
- in single stream mode
- in multi-thread mode
For each test, the time and CPU spent by the logger on saving 1 million messages to a file are measured. 3 measurements are taken and average values ​​are calculated.
We also conducted tests in debug (optimization disabled) and release (optimization O2) assembly. The following configuration was used for the tests:
- Window 7x64 (6.1.7601 Service Pack 1 Build 7601)
- Visual studio 2015, update 2
- RAM: 16Gb (DDR3-1600 / PC3-12800)
- CPU: Intel Core i7-870
- HDD: Samsung EVO SSD 850 256GB (SATA3)
In order to bring the tests closer to actual use, the following information was saved for each log message:
- Message number
- Source file name
- Code line number
- Source function name
- Logger / Module Name
- Level (error, warning, ...)
- Time
- Current thread ID (thread Id)
- CPU core number
- Text message
The code that was executed for each logger (requires C ++ 11 support for compilation):
Source text#include <stdio.h> #include <atomic> #include <thread> #include <vector> //Include specific logger headers #include "Logger headers ..." using namespace std; using namespace std::chrono; //Use this macro to switch on multi-threading mode //#define MULTI_THREAD int main(int argc, char* argv[]) { //Logger initialization //.. unsigned int thread_count = 4; unsigned int howmany = 1'000'000; vector<thread> threads; auto start = system_clock::now(); #if !defined(MULTI_THREAD) for(unsigned int i=0; i < howmany; i++) { //Has to be customized for every logger LOG(INFO) << " Message + all required information, #" << i; } #else howmany /= thread_count; for (int t = 0; t < thread_count; ++t) { threads.push_back(std::thread([&] { for(unsigned int i=0; i < howmany; i++) { //Has to be customized for every logger LOG(INFO) << " Message + all required information, #" << i; } })); } for(auto &t:threads) { t.join(); }; howmany *= thread_count; #endif auto delta = system_clock::now() - start; auto delta_d = duration_cast<duration<double>> (delta).count(); LOG(INFO) << "Time = " << (double)howmany / delta_d << " per second, total time = " << delta_d; //Logger uninitialization if necessary return 0; }
One thread
One thread should save 1 million messages to a file, filtering is disabled, file rotation is disabled. Runtime and average CPU utilization are measured.
| Debug Time (ms) | Debug CPU (%) | Release Time (ms) | Release CPU (%) |
---|
Pantheios | 140 300 | 13% | 28,400 | 13% |
---|
Glog | 52 500 | 13% | 8,270 | 13% |
---|
log4cpp | 130 570 | 13% | 13,806 | 13% |
---|
P7 ( 1 ) ( 2 ) ( 6 ) | 520 | 14% | 100 | 14% |
---|
G3log ( 1 ) ( 3 ) | 102 990 | 38% | 3,660 | 37% |
---|
Spdlog ( 1 ) ( 4 ) | 64,250 | 13% | 869 | 13% |
---|
Spdlog ( 1 ) ( 5 ) | 65,660 | 13% | 885 | 13% |
---|
Easylogging | 271,060 | 13% | 9,100 | 13% |
---|
Boost.Log ( 1 ) ( 7 ) | 2 310 200 | 17% | 44 300 | 9% |
---|
Boost.Log ( 1 ) ( 8 ) | 649 480 | 25% | 12,680 | 25% |
---|
- Asynchronous logging
- Compiled with the option “/P7.Pool=1024” - the total amount of available memory is 1 megabyte.
- Measurements can be considered synthetic, since most of the time the logger adds the data to the buffers, and recording occurs upon exiting the application and this work takes time exceeding the logging time by an order of magnitude, 1 second logging and 10 seconds of saving data.
- Logging under the conditions recommended by the manufacturer “spdlog :: set_async_mode (1048576)” while the logger consumes about 250 megabytes of memory
- Logging in conditions of equal memory consumption “spdlog :: set_async_mode (4096)” - in this case, the logger has a buffer of 4k elements, each element takes about 250 bytes, which ultimately results in a memory consumption of about 1 megabyte.
- Recording was carried out in a binary file, text in UTF-16 format
- Logging in conditions of equal memory consumption (asynchronous_sink + text_file_backend + bounded_ordering_queue + block_on_overflow), the queue length is 1024 elements, each element takes about 1100 bytes, which ultimately results in a memory consumption of just over 1 megabyte
- Synthetic test. Logging in the most comfortable conditions, default logger configuration (asynchronous_sink + text_file_backend + unbounded_fifo_queue). There are no restrictions in memory consumption, there is no sorting of messages in the order of receipt, the memory consumption was fixed at 1.8GB
4 threads
4 threads should save 1 million messages to a file in total, filtering is disabled, file rotation is disabled.
Runtime and average CPU utilization are measured.
| Debug Time (ms) | Debug CPU (%) | Release Time (ms) | Release CPU (%) |
---|
Pantheios | 10,600 | 48% | 9 500 | 48% |
---|
Glog | 30,200 | 93% | 5,900 | 93% |
---|
log4cpp | 149 600 | 18% | 16 900 | nineteen% |
---|
P7 ( 1 ) ( 2 ) ( 6 ) | 790 | nineteen% | 230 | nineteen% |
---|
G3log ( 1 ) ( 3 ) | 39,700 | 75% | 2,300 | 75% |
---|
Spdlog ( 1 ) ( 4 ) | 11,510 | 13% | 270 | 25% |
---|
Spdlog ( 1 ) ( 5 ) | 73,240 | 25% | 4,653 | 25% |
---|
Easylogging | 328 230 | nineteen% | 8 575 | 25% |
---|
Boost.Log ( 1 ) ( 7 ) | 2 645 120 | 14% | 48,290 | 14% |
---|
Boost.Log ( 1 ) ( 8 ) | 655 470 | 65% | 13,560 | 65% |
---|
- Asynchronous logging
- Compiled with the option “/P7.Pool=1024” - the total amount of available memory is 1 megabyte.
- Measurements can be considered synthetic, since most of the time the logger adds the data to the buffers, and recording occurs upon exiting the application and this work takes time exceeding the logging time by an order of magnitude, 1 second logging and 10 seconds of saving data.
- Logging under the conditions recommended by the manufacturer “spdlog :: set_async_mode (1048576)” while the logger consumes about 250 megabytes of memory
- Logging in conditions of equal memory consumption “spdlog :: set_async_mode (4096)” - in this case, the logger has a buffer of 4k elements, each element takes about 250 bytes, which ultimately results in a memory consumption of about 1 megabyte.
- Recording was carried out in a binary file, text in UTF-16 format
- Logging in conditions of equal memory consumption (asynchronous_sink + text_file_backend + bounded_ordering_queue + block_on_overflow), the queue length is 1024 elements, each element takes about 1100 bytes, which ultimately results in a memory consumption of just over 1 megabyte
- Synthetic test. Logging in the most comfortable conditions, default logger configuration (asynchronous_sink + text_file_backend + unbounded_fifo_queue). There are no restrictions in memory consumption, there is no sorting of messages in the order of receipt, the memory consumption was fixed at 1.8GB
Filtration
The logger should process 1 million messages and filter them, i.e. In the final file will not get any 1 message.
Measurement time is measured.
| Debug 1 thread, time (ms) | Debug 4 threads, time (ms) | Release 1 thread, time (ms) | Release 4 threads, time (ms) |
---|
Pantheios ( 1 ) | - | - | - | - |
---|
Glog | 55 520 | 28,240 | 6,840 | 4,790 |
---|
log4cpp | 200 | 70 | 80 | 45 |
---|
P7 | 84 | 102 | 23 | 42 |
---|
G3log | 5 530 | 1950 | 24 | 9 |
---|
Spdlog | 269 | 134 | 6 | 32 |
---|
Easylogging ( 2 ) | - | - | - | - |
---|
Boost.Log | 26,407 | 8,554 | 699 | 389 |
---|
- Filtering is not available by default.
- Couldn't make filtering work
Performance review
The performance of many loggers was very good.
Unfortunately, almost all loggers except P7 have a huge performance gap between the debug and release builds, sometimes the coefficient reaches 74 (Spdlog: 65660/885). This can complicate debugging projects due to increased logging delays.
The tests performed in a certain sense can be called synthetic, since not one developer who implements a logging library into her application does not want it to allocate 250 megabytes of memory for its needs or consume 75% of CPU or more.
Typically, an integrator developer wants the library to be invisible and do its job with minimal hardware requirements, especially when it comes to small embedded systems.
Therefore, I recalculated the best performance of each logger (
tests with equal memory consumption ) in order to determine how many message logs can be written using only 1% CPU and a reasonable but still large amount of memory equal to 1mb
The formula for calculating:
((1,000 ms / test time in ms) * 1,000,000 messages) / use CPU in the test
| Number of messages per second with 1% CPU |
---|
P7 | 714,285 messages (1,000,000 * (1,000 / 100 ms) / 14%) |
---|
Spdlog | 86,918 messages (1,000,000 * (1,000 / 885 ms) / 13%) |
---|
Glog | 9 301 messages (1000000 * (1000/8270 ms) / 13%) |
---|
Easylogging | 8,453 messages (1,000,000 * (1,000 / 9,100 ms) / 13%) |
---|
G3log | 7,384 messages (1,000,000 * (1,000 / 3,660 ms) / 37%) |
---|
log4cpp | 5,571 messages (1,000,000 * (1,000 / 13,806 ms) / 13%) |
---|
Pantheios | 2,708 messages (1,000,000 * (1,000 / 28,400 ms) / 13%) |
---|
Boost.Log | 2 508 messages (1000000 * (1000/44300 ms) / 9%) |
---|
findings
Pantheios
This library made quite mixed impressions, on the one hand, the author approached the project thoroughly and thoughtfully, good functional reviews, documentation, on the other hand poor performance (although the author notes the opposite (1)), the lack of file rotation and other trifles spoil the overall impression.
So, the advantages of the library:
- Type-safe pattern-based calls
- Good and complete documentation.
- One of the few loggers that supports unicode, but with some limitations
- Low memory consumption
- A large number of supported Sink
- A large number of supported compilers
Minuses:
- Development seems to be stopped
- Static linking during compilation of the logger and sink (file, network, etc.)
- Incredibly long compilation even on powerful computers and as a mediated result - the size of binary files
- One of the lowest performance indicators of all loggers reviewed.
- Logger configuration in code only (no configuration file support, command line ...)
- No file rotation support
- Synchronous execution, i.e. all delays Sink (write to the file for example) will affect the speed of execution of your code.
- It is not possible to combine unicode and ANSI strings within a single message.
- High threshold of entry (difficult enough to learn and confusing)
- C ++ Diagnostic Logging Critical Diagnostic Logging Cards (up to two orders of magnitude) www.pantheios.org/essentials.html
NB: The library is one of the largest and most complex of all compared, so it is likely that many of its features have not been considered.
Glog
Despite the fact that the library is rather poor in functionality and does not shine with performance, but this library has inspired a number of other projects that have outgrown its “father” count to the head. Only this is already a fat plus in the karma of the authors.
Advantages of the library:
- Easy-to-understand library
- Process failure support
- Ability to configure via the command line
- Low memory consumption
Minuses:
- Very weak documentation
- Weak support for process failures - limited signals, only Linux
- High overhead when filtering messages
- Repeated gap (up to 9 times) in performance of Debug and Release code
- Synchronous execution, i.e. all delays Sink (write to the file for example) will affect the speed of execution of your code.
- Active development suspended
- No unicode support
- Timestamp accuracy is insufficient
- Highest CPU consumption in multi-threading tests with a very modest logger performance gain compared to thread 1
log4cpp
Another well-deserved logger made a pretty good impression, no big disappointments, no high-profile promotions, no unfulfilled promises.
Advantages of the library:
- Generated documentation
- A large number of supported Sink
- Low memory consumption
- Configuration file support
- Good message filtering performance
Minuses:
- Synchronous execution, i.e. all delays Sink (write to the file for example) will affect the speed of execution of your code.
- No unicode support
- Repeated gap (up to 10 times) in performance of Debug and Release code
- Low performance (last place)
- Line size should not exceed 1024 characters - otherwise assertion
- If the path to the log files does not exist, an exception is generated.
P7
One of the most unusual loggers among those considered in this article, the package includes not only a logger, but also a server for receiving messages over the network, viewing log files, filtering, exporting, verbosity remotely and many other functions. As in the case of SpdLog, the sight was on performance and the ability to use on embedded devices, as the project is sharpened on the network and precise memory management.
It should also be noted that in the case of using Sink = FileBin or Sink = Baical, the project provides free software for network reception, viewing, filtering, etc.:
Advantages of the library:
- Complete documentation
- High performance + high precision timestamps, a large lead over the closest competitor SpdLog
- Unicode support
- Full control over memory consumption
- Asynchronous - the call to the Log function is independent of the data record, thus delaying the call to the function.
- Using Shared memory (instead of “global” variables) to access loggers and Sink, which allows you to access the logger from any parts of the process code, even from dynamic modules written in other languages ​​supported by the library, such as C # or C
- The kit includes wrappers for C, C #, Python
- In addition to logs, the library supports recording and monitoring in real time.
- The ability to control remotely (via the network) the logging level on each device, process or module, enable / disable telemetry counters
- Of all the considered loggers, there is a minimum processor requirement in terms of performance.
- Available for several languages ​​and if different parts of the application are written in C #, C ++, C, they can all use the same logger
- Easy to connect your own Text Sink
- Wide selection of Sink
- The Baical server core is an open source project and can be compiled under Windows, Linux 32/64
Minuses:
- Highly intensive use of a multi-stream logger reduces (by a factor of 2.3) the performance of the logger (charge for a sequence of log messages and timestamps)
- Developing your own binary binary data Sink
- Due to the serialization of the log messages, it is not possible to load all the processor cores evenly
G3log
The heir to G2Log which in turn was the result of a rethinking of Glog. The author pursued first of all the performance and conducts a rather active educational work on this subject (
1 ) (
2 ). Unfortunately, the performance tests turned out to be synthetic, and the result was far from expectations.
Advantages of the library:
- Good filtration rate
- Handling process failures (generation of stack trace when information is available), unfortunately only under Linux
- Overall satisfactory documentation
- Asynchronous - the call to the Log function is independent of the data record, thus delaying the call to the function.
Minuses:
- No unicode support
- Poor performance, with high CPU consumption
- Performance measurements can be considered synthetic, since most of the time the logger puts the data into buffers, and the recording occurs upon exiting the application and this work takes time exceeding the logging time by an order of magnitude
- Full asynchrony - timestamps and messages can be mixed in groups or one at a time with intensive logging
- Uncontrolled memory allocation reaching hundreds of megabytes, sometimes gigabytes with intensive logging
- Logger configuration by code only
- Timestamp accuracy is insufficient
- Repeated gap (up to 30 times) in performance of Debug and Release code
- High CPU consumption
- Compilers with C ++ 11 or higher
- kjellkod.wordpress.com/2014/08/16/presenting-g3log-the-next-version-of-the-next-generation-of-loggers
- kjellkod.wordpress.com/2015/06/30/the-worlds-fastest-logger-vs-g3log
Spdlog
Another logger written with an eye to performance, unfortunately good performance can only be obtained when using hundreds of megabytes of RAM. The functionality is standard for many other loggers, the main focus is speed.
Advantages of the library:
- Overall satisfactory documentation
- Not exacting to the processor
- Asynchronous - the call to the Log function is independent of the data record, thus delaying the call to the function.
- Partial control of memory consumption
- Good set of helper macros
- Speed, second place in the overall standings, but the backlog from the first place is a colossal
Minuses:
- No process failure handling
- The format string for Printf library functions is incompatible with the canonical format, which makes it difficult to painlessly replace one logger with another, in fact it’s a one-way ticket
- Full asynchrony - timestamps and messages can be mixed in groups or one at a time with intensive logging in the final file
- For optimal performance, it consumes about 250-300 megabytes of RAM, while reducing the amount of memory, performance decreases by almost 18 times.
- No unicode support
- Only header files that I attribute to minus, since the inclusion of these files in each file of your project significantly slows down the compilation, it is possible to use precompiled headers
- Logger configuration in code only
- Accuracy of timestamps is insufficient, especially in the light of achieved performance (tens and hundreds of thousands of messages per second with a timestamp resolution of 1ms)
- Compilers with C ++ 11 or higher
- Multiple break (up to 74 times) in performance of Debug and Release code
Easylogging
Logger with a light-weight target, single header file (about 6700 lines of code). The functionality is standard for many other loggers.
Advantages of the library:
- Overall satisfactory documentation
- Not exacting to the processor
- Moderate memory consumption
- Good set of helper macros
- Support configuration file command line
- Declared a large number of supported platforms
Minuses:
- Synchronous execution, i.e. all delays Sink (write to the file for example) will affect the speed of execution of your code.
- Poor performance
- Failed to start filtering, perhaps the option is under development
- Unicode support is declared, but in fact Unicode characters do not reach the file and are lost on the way.
- Only the header file, which I attribute to the minus, since including it in each file of your project slows down the compilation significantly, it is possible to use precompiled headers
- Timestamp accuracy is insufficient
- Compilers with C ++ 11 or higher
- Multiple break (up to 40 times) in performance of Debug and Release code
- It is necessary to enable thread-safety with a special macro (ELPP_THREAD_SAFE), disabled by default
Boost.Log
Logger with an eye on functionality, simplicity, as well as performance, such priorities and goals are pursued by the author:
motivation.We must pay tribute, the project has been developing for almost 9 years and is present in such a large project as Boost.
Advantages of the library:
- Support for synchronous and asynchronous logging (calling the Log function is independent of writing data, thus delaying the call to the function)
- Good enough documentation covering basic needs.
- Log record attributes (stream number, source file, counters, etc.) simplify logging
- Supported per sink formatting
- Ability to use predicates to filter
- Declared a large number of supported Sink
- Unicode support
- Configuration file support
Minuses:
- One of the most complex and confusing logging libraries presented in this article. Entirely built on a mixture of complex macros and templates. The study was spent the most time. Even more time is spent on learning "under the hood", perhaps therefore one of the main participants is only one person.
- Lowest performance of all loggers reviewed
- Segmentation fault / access violation when executing code
char *pStr = nullptr;
- Accuracy of time marks is not enough, although with such a performance it is not so critical (up to a dozen messages in one time period)
- Repeated gap (up to 54 times) in the performance of the Debug and Release code, it should be noted that in Debug mode, the execution time becomes completely unacceptable (tens of minutes for logging)
- Unicode support does not appear to be well-tested functionality (could not be started for asynchronous logging to a file with sorting and fixed-size FIFO)
- There is no handling of process failures, in the light of the fact that storing data buffers is important even for a synchronous logger — represents a significant risk of losing important data.
- Log record attributes are not trivial - for example, the name of the source function, file, line number. Creating your own attributes is also not a trivial task.
- Tangled compilation with its utilities, but it should be noted that the same compiled binaries are supplied, the problem is common to the whole Boost
- Including a library into your project significantly slows down compilation and linking (even when using precompiled headers)
- All parts of the library code are covered by the generation of exceptions, however, you can set your own interceptor, which reduces inconvenience for people who prefer not to catch exceptions from the logger during each call.
Total
Attention!
Subjective opinion of the author.- Pantheios — , , ,
- Glog — , , . — , .
- Log4cpp — , - ( , ) , /.
- P7 — , embedded . , , , ( home ). – , . , ( Sink ) (5-7)
- G3Log, SpdLog — , , –
- Easylogging — , ,
- Boost.Log — , , - , , nullptr . , - .
Thank you very much for your attention and your time, I hope this article has helped you in drawing up a general idea of ​​the realities of modern loggers.