clutchlog 0.17
|
Clutchlog is a spatial logging system that targets versatile debugging. It allows to (de)clutch messages for a given: log level, source code location or call stack depth.
<img alt"Clutchlog logo" src="https://raw.githubusercontent.com/nojhan/clutchlog/master/docs/clutchlog_logo.svg" width="400" />
Clutchlog allows to select which log messages will be displayed, based on their locations:
Additionally, Clutchlog will do its best to allow the compiler to optimize out calls, for instance debug messages in "Release" builds.
Additional features:
Adding a message is a simple as calling a macro (which is declutched in Debug build type, when NDEBUG
is not defined):
To configure the display, you indicate the three types of locations, for example in your main
function:
Example of a real-life log session (as seen in the frictionlesser software):
Demo showing fancy styling:
For more detailled examples, see the "Usage" sections below and the tests
directory.
Most of existing logging systems targets service events storage, like fast queuing of transactions in a round-robin database. Their aim is to provide a simple interface to efficiently store messages somewhere, which is appropriated when you have a well known service running and you want to be able to trace complex users interactions across its states.
Clutchlog, however, targets the debugging of a (typically single-run) program. While you develop your software, it's common practice to output several detailled informations on the internal states around the feature you are currently programming. However, once the feature is up and running, those detailled informations are only useful if you encounter a bug traversing this specific part.
While tracing a bug, it is tedious to uncomment old debugging code (and go on the build-test cycle) or to set up a full debugger session that displays all appropriate data (with ad-hoc fancy hooks).
To solve this problem, Clutchlog allows to disengage at runtime your debug log messages in various parts of the program, allowing for the fast tracking of a bug across the execution.
The main entrypoint is the CLUTCHLOG
macro, which takes the desired log level and message. The message can be anything that can be output in an ostringstream
.
There is also a macro to dump the content of an iterable within a separate file: CLUTCHDUMP
. This function takes care of incrementing a numeric suffix in the file name, if an existing file with this name exists.
Note that if you pass a file name without the {n}
tag, the file will be overwritten as is.
Log levels use a classical semantics for a human skilled in the art, in decreasing order of importance:
Note: the log levels constants are lower case (for example: clutchlog::level::xdebug
), but their string representation is not (e.g. "XDebug", this should be taken into account when using clutchlog::threshold
or clutchlog::level_of
).
To configure the global behaviour of the logger, you must first get a reference on its (singleton) instance:
One can configure the location(s) at which messages should actually be logged:
Current levels are defined in an enumeration as clutchlog::level
:
File, function and line filters are indicated using (ECMAScript) regular expressions:
A shortcut function can be used to filter all at once:
Strings may be used to set up the threshold:
Note that the case of the log levels strings matters (see below).
The output stream can be configured using the clutchlog::out
method:
The format of the messages can be defined with the clutchlog::format
method, passing a string with standardized tags surrounded by {}
:
Available tags are:
{msg}
: the logged message,{level}
: the current log level (i.e. Critical
, Error
, Warning
, Progress
, Note
, Info
, Debug
or XDebug
),{level_letter}
: the first letter of the current log level,{level_short}
: the current log level, printed in only four letters,{file}
: the current file name,{func}
: the current function,{line}
: the current line number,{level_fmt}
: the style of the current level (i.e. configured with clutchlog::style
),{filehash_fmt}
: a style for file names, which is value-dependant (see clutchlog::filehash_styles
),{funchash_fmt}
: a style for function names, which is value-dependant (see clutchlog::funchash_styles
).Some tags are only available on POSIX operating systems as of now:
{name}
: the name of the current binary,{depth}
: the current depth of the call stack,{depth_marks}
: as many chevrons >
as there is calls in the stack,{depth_fmt}
: a style depending on the current depth value (see clutchlog::depth_styles
),{hfill}
: Inserts a sequence of characters that will stretch to fill the space available in the current terminal, between the rightmost and leftmost part of the log message.The default log format is "[{name}] {level_letter}:{depth_marks} {msg} {hfill} {func} @ {file}:{line}\n"
, it can be overriden at compile time by defining the CLUTCHLOG_DEFAULT_FORMAT
macro.
By default, and if CLUTCHLOG_DEFAULT_FORMAT
is not defined, clutchlog will not put the location-related tags in the message formats (i.e. {name}
, {func}
, and {line}
) when not in Debug builds.
Output lines can be styled differently depending on their content.
For example, output lines can be colored differently depending on the log level.
Or, if you want to declare some semantics beforehand:
Note: this inserts a style marker at the very beginning of the line. If you add other styles later on the line, they will take precedence.
Colors can be specified in several different ways. The ANSI color mode will be automatically detected, depending on the types of arguments passed to styling functions:
clutchlog::fmt::fg
or clutchlog::fmt::bg
will encode a 16-colors mode,clutchlog::fg::none
and clutchlog::bg::none
can be passed in all modes.For example, all the following lines encode a bright red foreground for the critical level (see the "Colors" section below):
You may use styling within the format message template itself, to add even more colors:
Note: messages at the "critical", "error" and "warning" log levels are colored by default. You may want to set their style to none
if you want to stay in control of inserted colors in the format template.
The horizontal filling line (the {hfill}
tag) can be configured separately with clutchlog::hfill_style
, for example:
Note: this will actually reset any styling after the hfill, disabling any style you would have set for the whole message using clutchlog::format
for the remaining of the message.
Available typographies:
Typographic styles are always passed with the named tag (see clutchlog::fmt::typo
), whatever the color mode.
Using the clutchlog::fmt
class, you can style:
clutchlog::fmt::fg
,clutchlog::fmt::bg
.In 16-colors mode, any of the arguments may be passed, in any order, if an argument is omitted, it defaults to no color/style.
Available colors are:
Note: some terminals allow the user to configure the actual encoding of those colors. You may thus notice some difference with the expected rendering of the same colors encoded in the other modes. Use the other color modes if you want to fully control the actual color rendering.
For 256-colors mode, colors are expected to be passed as integers in [-1,255] or the fg::none
and bg::none
tags.
In 256-colors mode, if you want to only encode the background color, you cannot just omit the foreground color, you have to bass a fg::none
tag as first argument.
For 16M-colors mode, colors can be encoded as:
fg::none
and bg::none
tags.In 16M-colors mode, if you want to only encode the background color, you cannot just omit the foreground color, you have to pass a fg::none
tag as first argument.
Some tags can be used to change the style of (part of) the output line,
depending on its content. The {filehash_fmt}
and {funchash_fmt}
will introduce a styling sequence which depends on the current file name, and function name respectively. The chosen style is chosen at random among the candidate ones, but will always be the same for each value.
The set of candidate styles can be configured with clutchlog::filehash_styles
and clutchlog::funchash_styles
, which both take a vector of clutchlog::fmt
objects as argument:
The same idea applies to {depth_fmt}
. However, if clutchlog::depth_styles
is configured, then the styles are chosen in order. That is, a depth of 1 would lead to the first style being chosen. If the current depth of the stack is larger than the number of configured styles, then the last one is used. For example:
If clutchlog::depth_styles
is set, the {depth_marks}
template tag will render with each mark having each own style corresponding to its depth. Note: a depth of zero showing no mark, the first style in the list is never applied to marks.
The default format of the first line of comment added with the dump macro is "# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}"
. It can be edited with the format_comment
method. If it is set to an empty string, then no comment line is added. The default can be modified at compile time with CLUTCHDUMP_DEFAULT_FORMAT
.
By default, the separator between items in the container is a new line. To change this behaviour, you can change CLUTCHDUMP_DEFAULT_SEP
or call the low-level dump
method.
By default, and if CLUTCHDUMP_DEFAULT_FORMAT
is not defined, clutchlog will not put the location-related tags in the message formats (i.e. {file}
and {line}
) when not in Debug builds.
The mark used with the {depth_marks}
tag can be configured with the clutchlog::depth_mark
method, and its default with the CLUTCHLOG_DEFAULT_DEPTH_MARK
macro:
The character used with the {hfill}
tag can be configured wth the clutchlog::hfill_mark
method, and its default with the CLUTCHLOG_DEFAULT_HFILL_MARK
macro:
Clutchlog measures the width of the standard error channel. If it is redirected, it may be measured as very large (or very small). Thus, the clutchlog::hfill_min
clutchlog::hfill_max
accessors allow to set a minimum and a maximum width (in number of characters).
Note: clutchlog will use the measured width, unless it goes out of [clutchlog::hfill_min,clutchlog::hfill_max]
, in which case it will be caped to those bounds.
By default, clutchlog removes 5 levels of the calls stack, so that your main
entrypoint corresponds to a depth of zero. You can change this behaviour by defining the CLUTCHLOG_STRIP_CALLS
macro, or calling clutchlog::strip_calls
.
By default, the {file}
template tag is rendered as the absolute path (which is usualy handy if your terminal detects paths and allows to run a command on click).
You can change this behavior to display shorter names, using clutchlog::filename
, and passing one of the following the shortening method:
clutchlog::filename::base
: the file name itself,clutchlog::filename::dir
: the name of the single last directory containing the file,clutchlog::filename::dirbase
: the last directory and the file names,clutchlog::filename::stem
: the file name without its extension,clutchlog::filename::dirstem
: the last directory and the file without extension.clutchlog::filename::path
: the absolute path (the default).Example:
By default, clutchlog is always enabled if the NDEBUG
preprocessor variable is not defined (this variable is set by CMake in build types that differs from Debug
).
You can however force clutchlog to be enabled in any build type by setting the WITH_CLUTCHLOG
preprocessor variable.
When the NDEBUG
preprocessor variable is set (e.g. in Release
build), clutchlog will do its best to allow the compiler to optimize out any calls for log levels that are under progress
.
You can change this behavior at compile time by setting the CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG
preprocessor variable to the desired maximum log level, for example:
Note that allowing a log level does not mean that it will actually output something. If the configured log level at runtime is lower than the log level of the message, it will still not be printed.
This behavior intend to remove as many conditional statements as possible when not debugging, without having to use preprocessor guards around calls to clutchlog, thus saving run time at no readability cost.
All configuration setters have a getters counterpart, with the same name but taking no parameter, for example:
To control more precisely the logging, one can use the low-level clutchlog::log
method:
A helper macro can helps to fill in the location with the actual one, as seen by the compiler:
A similar dump
method exists:
You can access the identifier of log levels with clutchlog::level_of
:
The CLUTHFUNC
macro allows to wrap any function within the current logger.
For instance, this can be useful if you want to (de)clutch calls to assert
s. To do that, just declare your own macro:
Thus, any call like ASSERT(x > 3);
will be declutchable with the same configuration than a call to CLUTCHLOG
.
The CLUTCHCODE
macro allows to wrap any code within the current logger.
For instance:
You may want to manually increase the stack depth for a given logging call, for instance to subdivise a single function in sections. To do so, you can use the CLUTCHLOGD
macro, which take an additional argument, in the form of the number of additional (fake) stack depths you want:
That way, the depth will be rendered to the actual depth, plus the additional depth delta. Note that the displayed function will stay the same. Any filtering on the stack depth will take into account the fake depth and not the real one.
Here what you would do to setup clutchlog with the default configuration:
And here are all the functions you may call to log something:
Here what you would do to setup clutchlog with the default configuration using 16M-colors mode:
Because access to the call stack depth and program name are system-dependent, the features relying on the depth of the call stack and the display of the program name are only available for operating systems having the following headers: execinfo.h
, stdlib.h
and libgen.h
(so far, tested with Linux).
Clutchlog sets the CLUTCHLOG_HAVE_UNIX_SYSINFO
to 1 if the headers are available, and to 0 if they are not. You can make portable code using something like:
Because access to the current terminal width is system-dependent, the {hfill}
format tag feature is only available for operating systems having the following headers: sys/ioctl.h
, stdio.h
and unistd.h
(so far, tested with Linux).
Clutchlog sets the CLUTCHLOG_HAVE_UNIX_SYSIOCTL
to 1 if the headers are available, and to 0 if they are not. You can make portable code using something like:
If you use unicode characters in your template, the horizontal width will not be computed properly, resulting in incorrectly right-aligned lines. Solving this would require the use of third-party libraries, making portability more difficult.
Some colors/styles may not be supported by some exotic terminal emulators.
Clutchlog needs C++-17
with the filesystem
feature. You may need to indicate -std=c++17 -lstdc++fs
to some compilers.
Calling the CLUTCHLOG
macro with a message using a variable named clutchlog__msg
will end in an error.
What Clutchlog do not provide at the moment (but may in a near future):
What Clutchlog will most certainly never provide:
To use clutchlog, just include its header in your code and either ensure that the NDEBUG
preprocessor variable is not set, either define the WITH_CLUTCHLOG
preprocessor variable.
If you're using CMake (or another modern build system), it will unset NDEBUG
—and thus enable clutchlog— only for the "Debug" build type, which is usually what you want if you use clutchlog, anyway.
To build and run the tests, just use a classical CMake workflow:
There's a script that tests all the build types combinations: ./build_all.sh
.
If you are using Git and CMake, it is easy to include Clutchlog as a dependency.
First, add Clutchlog as a submodule of your repository:
Then, in your CMakeLists.txt
file, add:
And that's it.