nRF5 SDK v12.1.0
Logger module

The logger module provides logging capability for your application. It is used by SDK modules and can be also utilized in application code. Main features of this library are:

Logs processing

Logs can be processed in two ways:

If the deferred mode is switched off, the logs are processed in the place where the logger is used. It means that the pointer to a formatted string and the arguments are passed to the logger backend for processing. It is possible for the backend to buffer the logs, but the frontend part of the logging is performed once the backend processing function returns. This approach is straightforward but in many cases not optimal. It can significantly impact performance, especially when logs are used in interrupt context.

In the deferred mode, whenever the logger is used, the pointer to the string and the arguments are stored in an internal buffer and the log is not processed. It is up to the user to trigger processing whenever suitable. In most cases, the processing is performed in an idle stage, when the system would normally sleep. The size of the buffer can be configured, so that it can be adjusted to the amount of log entries and the nature of idle periods in the application. In the deferred mode, logging takes considerably fewer cycles in the place where it is called (compared to in-place mode), because the processing cost is shifted to idle stage. If the buffer is too small, the logger detects it and injects an overflow marker. In such case, logging is blocked until there is room for new entries in the buffer.

Using the deferred mode introduces some limitations to the logging mechanism:

For example, the following way of logging a temporary string is not recommended:

void foo(void)
{
char string_on_stack[] = "stack";
//WRONG! by the time the log is processed, variable content will be invalid
NRF_LOG_INFO("%s",string_on_stack);
}

Instead, use the following way of logging a temporary string:

void foo(void)
{
char string_on_stack[] = "stack";
//nrf_log_push() copies the string into the logger buffer and returns address from the logger buffer
NRF_LOG_INFO("%s",nrf_log_push(string_on_stack));
}

Configuration

The logger configuration consists of two parts:

The following are the logger frontend options:

The following are the logger backend options:

A module can have a section that overrides default logger settings for the given module. Modules can override the following configuration:

Usage

Logger usage can be divided into two tasks:

Controlling the logger

The nrf_log_ctrl.h file contains the API for controlling the logger. Before the logger can be used, it must be first initialized. The NRF_LOG_INIT() macro initializes the logger and the default backend. During initialization, the user must provide a pointer to the timestamp function or NULL if timestamping is disabled. If the deferred mode is used, then it is up to the user to start processing of the buffered log entries. The NRF_LOG_PROCESS() macro is used for that. When NRF_LOG_PROCESS() is called, then the logger attempts to process a single entry and returns true if there are more entries to process or false if the buffer is empty.

The following code snippet presents how NRF_LOG_PROCESS can be incorporated into the non-preemptive scheduler.

#include "nrf_log_ctrl.h"
int main(void)
{
err_code = NRF_LOG_INIT(timestamp_func);
...
while(1)
{
scheduler();
{
//sleep
}
}
}

Use the NRF_LOG_FLUSH() macro to ensure that all logs have been processed.

Use the NRF_LOG_FINAL_FLUSH() macro to reinitialize the backend to the blocking mode and wait for all data to be sent to the host. Use this macro in case of an error, right before a reset. In such a situation, if you use the NRF_LOG_FLUSH() macro, you will not receive the whole log, as the transmission will be interrupted by the reset.

In certain cases (e.g. error handler), it might be required to change the behavior of the backend (e.g. no context changing). At any point in time, the NRF_LOG_HANDLERS_SET() macro can be used to provide new backend functions for handling log entries.

Logging

The nrf_log.h file contains the API used for logging. There are two types of log entries:

The following is an example of the simplest way to log data in a module:

#include "nrf_log.h"
void send_func(uint8_t * p_data, uint32_t length)
{
NRF_LOG_INFO("sending data\r\n");
NRF_LOG_DEBUG("%d bytes, data:\r\n", length);
NRF_LOG_HEXDUMP_DEBUG(p_data, length);
if (phy_is_busy())
{
NRF_LOG_ERROR("Phy is busy\r\n");
}
}

You can customize the logging for each module. Some configuration defines can be redefined before the header is included. Modules can override the severity level, the string prefix, as well as the coloring of info and warning logs.

#define NRF_LOG_MODULE_NAME "SPI"
#if SPI_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL SPI_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR SPI_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR SPI_CONFIG_DEBUG_COLOR
#else //SPI_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL 0
#endif //SPI_CONFIG_LOG_ENABLED
#include "nrf_log.h"

Note: You can also use the following two macros to produce cleaner logs, without the default prefix in the user string:

Due to certain limitations, logging a float requires you to use the following dedicated macros:

NRF_LOG_INFO(„float value:” NRF_LOG_FLOAT_MARKER „\r\n”, NRF_LOG_FLOAG(val))

Because customized logging in modules relies on the redefinition of defines , including "nrf_log.h" in header files might lead to compilation failures.

Performance

The following measurements were performed on nRF52. In case of deferred logs, timing does not depend on string length and only slightly on the number of arguments. In case of in-place processing with RTT, timing is highly dependent on the number of arguments. In case of UART, the difference between deferred and in-place logging is even bigger, and highly dependent on the baud rate.

Number of arguments In-place with RTT Deferred
0 arguments 12 us 2.5 us
6 arguments 300 us 4.0 us

Customizing the logger backend

You can add another implementation of the logger backend. The logger frontend uses two function pointers from the backend to forward the entry processing to the backend:

It might be also important to provide a different implementation for outputing logs from an error condition when the system is limited, such as when the context cannot switch, or when the SoftDevice cannot be used. The NRF_LOG_HANDLERS_SET macro can be used to change handlers on the fly.


Documentation feedback | Developer Zone | Subscribe | Updated