The logger module is the recommended method for sending messages to a console, unlike the printk() function, which will not return until all bytes of the message are sent. The logger module supports both in-place and deferred logging among many other advanced features such as:
Compile time filtering on module level
Run time filtering independent for each backend
Timestamping with user-provided function
Dedicated API for dumping data
Coloring of logs
printk() support – printk message can be redirected to the logger
As you will see, the logger module is highly configurable at compile time and at run time. By using proper configuration options, logs can be gradually removed from compilation to reduce image size and execution time when logs are not needed. During compilation, logs can be filtered out based on module and severity level.
More in this (Lesson 7)
When the logging module is used, it will create a low priority thread (log_process_thread_func) by default. The task of this thread is to take the deferred “queued” logs and push them out to a console.
Logs can also be compiled in, but filtered at run time using a dedicated API. The run time filtering is independent for each backend and each module. There are four severity levels available in the system, see the table below.
1 (most severe)
Severe error conditions
Conditions that should be taken care of
Informational messages that require no action
4 (least severe)
Logger module: Severity levels
The following set of macros are available for each level:
LOG_X for standard printf-like messages, where X can be DBG, INF, WRN, or ERR.
[00:00:00.382,965] is a timestamp associated with the generation of the message. It uses the format hh:mm:ss.ms,us. The logger gets the timestamp by internally calling the kernel function k_cycle_get_32(). This routine returns the current time since boot up (uptime), as measured by the system’s hardware clock. You could change this to return an actual time and date if an external Real-time clock is present on the system.
<inf> indicates the severity level, i.e information.
Less4_Exer2 is the name of the module generating the log message.
nRF Connect SDK Fundamentals is the actual message.
As another example, the following lines:
LOG_DBG("A log message in debug level");
LOG_WRN("A log message in warning level!");
LOG_ERR("A log message in Error level!");
would print the following on the console:
The first logging command LOG_INF("Exercise %d",2) is used to generate a formatted string with an integer place holder %d, similar to what have we seen with printk(). Instead of the constant 2, you could place any integer variable to print its value.
The remaining three logging lines will print messages in three different severity levels. Note that messages with Warning severity level will be displayed in yellow and messages with Error severity level will be displayed in red. This is because the coloring of error and warning logs feature (LOG_BACKEND_SHOW_COLOR) is enabled by default as we will see in Exercises 2 and 3.
We also have the LOG_HEXDUMP_X macros for dumping data where X can be DBG, INF, WRN, or ERR.
The LOG_HEXDUMP_X macro takes three parameters: a pointer to the data to be printed, the size in bytes of the data to be printed, and a string to describe the data.
There are two configuration categories for the logger module: configurations per module and global configuration. When logging is enabled globally, it works for all modules. However, modules can disable logging locally. Every module can specify its own logging level (LOG_LEVEL_[level]) or use LOG_LEVEL_NONE, which will disable the logging for that module.
The module logging level will be honored unless a global override is set. A global override can only increase the logging level. It cannot be used to lower module logging levels that were previously set higher. It is also possible to globally limit logs by providing a maximum severity level present in the system. For instance, if the maximum level in the system is set to INFO, messages less severe than the info level (i.e DEBUG) will be excluded.
Each module that is using the logger must specify a unique name and register itself to the logger. We will cover how to do this in Exercise 2. If the module consists of more than one file, registration is performed in one file, but each file must declare the module name.
As we have seen, the logger API has two types of messages: standard and hexdump. When the logger API is called, a message is created and added to a dedicated, configurable buffer containing all log messages. Each message contains a source ID, timestamp, and severity level. A standard message contains a pointer to the string and any arguments. A hexdump message contains copied data. We will cover how to use the logger module in Exercise 2 and Exercise 3.
The logger module is designed to be thread-safe and minimizes the time needed to log the message. Time-consuming operations like string formatting or obtaining access to the transport (i.e UART, RTT or whatever backend you are using) are not performed immediately when the logger API is called.