Introduction
G3log is an asynchronous "crash safe", logger with support for adding custom made logging sinks. It is open source and cross platform, currently used on Windows, Linux and OSX. G3log was built to be as safe to use as a synchronous logger but blazing fast with all the slow logging work done in background threads.
It comes with an optional logging sink to save logs to file and supports easy adding of custom made logging sinks.
Traditional logger vs the asynchronous g3log. G3log features possible logging sinks that are currently used by some g3log users.
G2log -> G3log
G3log is open source and cross-platform. G3logs builds on the asynchronous logger g2log that was released in 2011 and previously presented here at CodeProject.
An interesting performance benchmarking for g2log should be seen in comparison with g3log, which is roughly 30% faster than g2log. The more LOG calling threads, the greater the performance difference in g3log’s favor
G3log features compelling functionality such as:
- Logging and design-by-contract framework
- LOG calls are asynchronous to avoid slowing down the LOG calling thread
- LOG calls are thread safe
- Queued LOG entries are flushed to log sinks before exiting so that no entries are lost at shutdown
- Catching and logging of SIGSEGV and other fatal signals ensures controlled shutdown
- On Linux/OSX a caught fatal signal will generate a stack dump to the log
- G3log is cross platform, currently in use on Windows, various Linux platforms and OSX
- Completely safe to use across libraries, even libraries that are dynamically loaded at runtime
- Significant performance improvement compared to the snappy g2log.
Using g3log
g3log uses level-specific logging. This is done without slowing down the log-calling part of the software. Thanks to the concept of active object [1][2] g3log gets asynchronous logging – the actual logging work with slow disk or network I/O access is done in one or several background threads
Example usage
Optional to use either streaming or printf-like syntax
Conditional LOG_IF
as well as normal LOG
calls are available. The default log levels are: DEBUG, INFO, WARNING
, and FATAL
.
LOG(INFO) << "streaming API is as easy as ABC or " << 123;
LOGF(WARNING, "Printf-style syntax is also %s", "available");
Conditional Logging
LOG_IF(DEBUG, small < large) << "Conditional logging is also available.
// The streaming API has a printf-like equivalent
LOGF_IF(INFO, small > large,
"Only expressions that are evaluated to true %s,
"will trigger a log call for the conditional API")
Design-by-Contract
CHECK(false)
will trigger a “fatal” message. It will be logged, and then the application will exit. A LOG(FATAL)
call is in essence the same as calling CHECK(false)
.
CHECK(false) << "triggers a FATAL message"
CHECKF(boolean_expression,"printf-api is also available");
Initialization
A typical scenario for using g3log would be as shown below. Immediately at start up, in the main function, g2::LogWorker
is initialized with the default log-to-file sink.
#include<g2log.hpp>
#include<g2logworker.hpp>
#include <std2_make_unique.hpp>
#include "CustomSink.h"
int main(int argc, char**argv) {
using namespace g2;
std::unique_ptr<LogWorker> logworker{ LogWorker::createWithNoSink() };
auto sinkHandle = logworker->addSink(std2::make_unique<CustomSink>(),
&CustomSink::ReceiveLogMessage);
g2::initializeLogging(logworker.get());
- The LogWorker is created with no sinks.
- A sink is added and a sink handle with access to the sinks API is returned
- When adding a sink the default log function must be specified
- At initialization the logging must be initialized once to allow for LOG calls
G3log with sinks
Sinks are receivers of LOG
calls. G3log comes with a default sink (the same as G2log uses) that saves LOG
calls to file. A sink can be of any class type without restrictions as long as it can either receive a LOG
message as a std::string
or as a g2::LogMessageMover
.
The std::string
option will give pre-formatted LOG
output. The g2::LogMessageMover
is a wrapped struct that contains the raw data, g2::LogMessage.
Use this if you prefer your own custom handling and formatting in your sink.
Using g2::LogMessage
is easy:
void FileSink::fileWrite(LogMessageMover message) {
...
std::string entry = message.get().toString();
...
}
Sink Creation
When adding a custom sink a log receiving function must be specified, taking as argument a std::string
for the default log formatting look or taking as argument a g2::LogMessage
for your own custom made log formatting.
auto sinkHandle = logworker->addSink(std2::make_unique<CustomSink>(), &CustomSink::ReceiveLogMessage);
- A sink is owned by the G3log and is transferred to the logger wrapped in a
std::unique_ptr
- The sink’s log receiving function is given as argument to
LogWorker::addSink
LogWorker::addSink
returns a handle to the custom sink.
Calling the custom sink
All public functions of the custom sink are reachable through the handler.
std::future<void> received = sinkHandle->call(&CustomSink::Foo, some_param, other);
Code Examples
Example usage where a custom sink is added. A function is called though the sink handler to the actual sink object.
#include<g2log.hpp>
#include<g2logworker.hpp>
#include <std2_make_unique.hpp>
#include "CustomSink.h"
int main(int argc, char**argv) {
using namespace g2;
std::unique_ptr<LogWorker> logworker{ LogWorker::createWithNoSink() };
auto sinkHandle = logworker->addSink(std2::make_unique<CustomSink>(),
&CustomSink::ReceiveLogMessage);
initializeLogging(logworker.get());
LOG(WARNING) << "This log call, may or may not happend before"
<< "the sinkHandle->call below";
std::future<void> received = sinkHandle->call(&CustomSink::Foo,
param1, param2);
g2::shutDownLogging();
}
Example usage where a the default file logger is used and a custom sink is added
#include<g2log.hpp>
#include<g2logworker.hpp>
#include <std2_make_unique.hpp>
#include "CustomSink.h"
int main(int argc, char**argv) {
using namespace g2;
auto defaultHandler = LogWorker::createWithDefaultLogger(argv[0],
path_to_log_file);
g2::initializeLogging(defaultHandler.worker.get());
LOG(DEBUG) << "Make log call, then add another sink";
defaultHandler.worker->addSink(std2::make_unique<CustomSink>(),
&CustomSink::ReceiveLogMessage);
...
}
It is easy to start using the logger anywhere in your code base.
#include <g2log.hpp>
void SomeFunction() {
...
LOG(INFO) << "Hello World";
}
g3log API
In addition to the logging API: LOG, LOG_IF, CHECK
(and the similar printf-calls) there are a few functions that are helpful for using and tweaking g3log.
Initialization
initializeLogging(...)
Dynamic log levels
I.e. disabling/enabling of log-levels at runtime. Enabling of this feature is done by a
#define G2_DYNAMIC_LOGGING
.
void setLogLevel(LEVELS level, bool enabled_status);
bool logLevel(LEVELS level);
Sink Handling
See g2logworker.hpp
for adding a custom sink, or accessing the default filesink.
std::unique_ptr<SinkHandle<T>> addSink(...)
static g2::DefaultFileLogger createWithDefaultLogger(...)
static std::unique_ptr<LogWorker> createWithNoSink();
Internal Functions
See g2log.hpp
for several internal
functions that usually don’t have to be used by the coder but can if g3log has to be tweaked for special shutdown considerations.
shutDownLogging()
void changeFatalInitHandlerForUnitTesting(...)
Where to get it
Please see https://bitbucket.org/KjellKod/g3log
Thanks
Thanks to great community feedback from users of g2log. With your feedback and eager beta testing g2log could be further improved into the current release of g3log.
Enjoy
Kjell (a.k.a. KjellKod)