pid-log package implement the runtime logging facility of PID. By logging we mean generating messages that inform about the state of a running program. These messages can be read from standard outputs or files and may be useful to notify some tools of interesting events, to provide debug information to developpers, or traces of program execution.
pid-log intend to provide a powerful but easy to configure logging tool for developpers with features:
- possibility to deactivate logs for a the whole program. Useful when you want to get real-time compatible performance and log is not required.
- filtering log messages either according to their severity or depending on their emitting place (component, package, framework).
- generating various log outputs from the same global log trace in a program. For instance usefull to discriminate errors or debug traces from other information.
- formatting of log messages is standardized and can be configured to get various information such as: calling site information (line in source file, calling function), runtime trace generation (date, thread) or PID related information (framework, package, component of the emitting component).
- easy configuration of logger system at global level. This way libraries can be completely agnostic from the way logs they generate are finally managed.
There are a few basic concepts to know before using
- The logger refers to the unique global object that controls the generation of log messages. It can be configured and controlled at runtime. It is accessed either via the
pid::logger()function or via the
- A component is loggable if it can be identified at runtime by the logger. At CMake definition time, it is declared as
- A proxy refers to the object used to generate log traces. This object is local to each loggable component. There is also a global anonymous proxy for non loggable components. Proxies are accessed in code using the
- A filter is an object in charge of filtering log traces according to some rules. The logger has a global filter that is empty by default (i.e. every log message is valid) and can be configured.
A sink is an objec in charge of generating output from log traces. A sink has:
- a filter that is used to filer log messages coming from proxies.
- a formatter used to format the output with additional information
- a set of outputs streams, that are the streams where the filtered messages are written with specific format.
Sinks and filters need to be configured in order for the logger to work properly (i.e. generate outputs from log traces). This is achieved either directly using the
pid-log API or by using configuration files.
Basically, to use logging system of PID, a package needs to depend on the
pid-log package. In root
CMakeLists.txt of the package:
Defining loggable components
Then, let’s suppose you want a component to generate log traces you need to declare your component as loggable. In the
CMakeLists.txt of the package, where the component is defined:
LOGGABLE argument tells PID to generate a specific header file that will be included in public headers (for components having public interface) or sources (e.g. for applications). This header file will be used by the PID system to identify the emitting component at runtime.
LOGGABLE is particularly usefull for libraries because we sometime want to filter log messages coming from a given package or component and
LOGGABLE is used to automatically deduce the calling site. So by default any library that generates log traces using
pid-log should be a loggable object.
The generated header file has pattern name
<package>_<component>.h and is located in the
pid/log subfolder of:
- component include folder if it is a component with public headers (header, static or shared library).
- component source folder otherwise (module, test or application).
Its content looks like:
PID_LOG_COMPONENT_NAME is adapted depending of component and package information.
The only thing that remain to be done by the developer is to include this header in its source file, respecting these constraints:
- this header must be the last one included in your source or header files. Indeed this is the only way you are ensured that this code will not be overwritten by a header of another included loggable component.
- as far as possible include this header only in sources, not in headers. A notable exception is when public headers contain template code that generates log traces.
Defining non loggable components that use the pid-log API
If your component does not generate log traces by itself (i.e. its code is not using the
pid-log API to write logs using proxies), there is no need to declare it as loggable. This may be for instance the case for an application whose generated traces are coming only from the libraries it uses. In this case your component may require to use the
pid-log API to configure the logger, so simply do this as a usual dependency to
In the code using
pid-log API to configure the logger (typically
main() function of your program) you simply have to include the
Now it is time to look inside API of
pid-log to learn:
- how to write log traces in a loggable component.
- how to configure the logger to get desired outputs.
Generating log traces
Log traces can be generated by any component using the
pid-log API. Simply remember that only loggable components can be identified as specific emitting places, while non loggable components are considered as anonymous (and so their traces cannot be discriminated from those of other non loggable components).
To generate log traces a component needs to use a proxy. A proxy is automatically generated, configured and registered in logger using
pid_log is a macro that provides the adequate proxy object (either specific to a loggable component or anonymous) that is usable quite like a c++ stream:
As the example shows, you can use standard output formatters for specific types as well as other stream modifiers:
pid::endladds a newline to the current log message but does not flush the output.
pid::flushflush the proxy. This will launch the message filtering process.
pid::criticalare severity specifiers used to specify the nature of the new log message to generate. The flush of the proxy is automatic whenever the current severity specifier changes.
pid::align_offare used respectively to activate and deactivate the automatic alignment of multi lines messages. Alignment consists in adding same padding spaces for all lines of the message.
Configuring generated outputs
The configuration and control of the logging system in itself should most of time takes place in the main program. This consists in using the API of the logger, you can find it in
<pid/logger.h>. The logger itself is obtained using the
PID_LOGGER macro or
pid::logger() function, which give access to the global logger object. By default the logger is configured to write all logs in standard output without any filtering. The simplest way to configure the logger is to write and use a configuration file:
This simply tells to configure the logger with the given configuration, whose content could look like this:
In this example, the logger will remove any message that is not a debug message (due to the global
accept rule), then any message coming from components belonging to frameworks
another_framework is also removed (due to the global
reject rule). All messages allowed by the global filter are then forwarded to the unique sink. This later simply writes to the standard output messages with labels specifying source code that made the call, and date when message has been generated.
Here are the possible yaml fields available:
activateis used to tell if the logger is activate dor not. In later case no output traces will be generated.
acceptis used to define which traces are accepted. Any log message with at least one adequate property among those listed will be accepted. In the example any debug message is accepted. Any log message whose property does not match any of the accepted properties will be rejected.
rejectis used to define which messages, among those that are accepted, is rejected. In the example, any message coming from a component belonging either to framework
a_frameworkor to framework
another_frameworkwill be rejected.
sinksis the set of sinks used by the logger. Each sink must define at least one output stream (using
reject can be used either at global scope (like in the example) or at sink scope. If used at global scope, they will configure the global filter, otheriwse they configure the sink specific filter. The global filter is used to remove messages before forwarding the messages to the sinks, so every message rejected at global level will never appear in any sink. At sink scope, they are used to filter messages that will be finally printed in outputs. From yaml perspective
reject are maps with following subproperties:
severityis the set of severity matched by the filter. Value for this field can be either a scalar (e.g. with value chosen in
critical) or a sequence of scalars. If the message matches one of the specified severities, the filter condition is satisfied.
frameworksis the set of frameworks matched by the filter. Value for this field can be either a scalar (e.g. with name of target framework as value) or a sequence of scalars. If the message matches one of the specified frameworks, the filter condition is satisfied.
packagesis the set of packages matched by the filter. Value for this field can be either a scalar (e.g. with name of target package as value) or a sequence of scalars. If the message matches one of the specified packages, the filter condition is satisfied.
componentsis the set of packages matched by the filter. Value for this field can be either a scalar (e.g. with name of target component as value) or a sequence of scalars. If the message matches one of the specified components, the filter condition is satisfied.
Each sink defined by
sinks is an element of a sequence. It defines:
outputs: this mandatory property defines where the sink will generate output traces from logged messages. Possible values are
rejectare used to define the sink specific filter.
formatis used to define the format of the generated output. It is a scalar or sequence of scalar used to specify output using specific values:
date(printing timing info),
severity(printing severity info),
source(printing info about source code that generated the log),
function(printing info about function that generated the log message),
thread(printing info about the thread that generated the log message),
component(printing info about component that generated the log message).