nRF5 SDK v14.0.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 following are the logger frontend configuration 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. 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. When logger is initialized it has no backends attached. Next step is to provide the backend using nrf_log_backend_add() call. nrf_log_backend_add() provides the interface to logger backend which must be already initialized. At any time, backend can be detached from the logger using nrf_log_backend_remove(). Logger support up to 10 backends.

Initialization and addition of common backeds are bundled into nrf_log_default_backends module. NRF_LOG_DEFAULT_BACKENDS_INIT() macro can be used to initialize and attach RTT or UART backend de.

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

#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.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 when it is expected to flush all logs in 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.

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"

If module consists of several files then logger customization should be repeated in every file with one exception. NRF_LOG_MODULE_REGISTER() must be called in only one file.

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

Logger backend interface

Logger backend interface is defined in nrf_log_backend_interface.h. Implementation of three functions must be provided:

Messages provided with the backend_put function are shared between all backends and thus they must be managed by backend implementation. Memory Object module memory objects are used for storing messages. When the logger frontend is processing an entry, it allocates memory for the message from the internal pool, removes the entry from internal circular buffer, and creates the message. Then, it iterates on all attached backends and applies dynamic filtering (if enabled). When the backend gets the message, it must indicate that it is in use by calling nrf_memobj_get. This will ensure that the message will not be freed. nrf_memobj_put must be called when backend processes the message. The message will be returned to the memory pool if it is processed by all backends.

Warning
In the current implementation, each and every logger backend shall run on lower or equal priority as the module that is writing logs.

Documentation feedback | Developer Zone | Subscribe | Updated