nRF5 SDK v15.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:

For API documentation of this module, see Logger module.

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. The operation is not thread-safe and may lead to the corruption of the output.

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 the logger is initialized, it has no backends assigned. The next step is to provide the backend using the nrf_log_backend_add() call. nrf_log_backend_add() provides the interface to the logger backend, which must be already initialized. At any time, a backend can be detached from the logger using nrf_log_backend_remove(). The logger supports up to 10 backends.

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

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 a module consists of several files, then the logger customization must 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.

Filtering logs on instance level

When a module is widely used, filtering on module level is not useful. An extreme example is the module that is used by the logger system. In that case, if a different instance of such module is used elsewhere in the application, logging is impossible unless filtering is applied on instance basis.

The logger provides a set of macros for filtering on instance basis. The approach is similar to filtering on module basis, where for each module a specific set of variables is registered in memory sections (NRF_LOG_MODULE_REGISTER).

Setting up instance level filtering

To use this feature, the instance structure must contain a pointer to the logger structure specific to the instance, and the pointer must be initialized correctly. The logger module provides a set of macros in nrf_log_instance.h. If logs are disabled, the macros are empty and no additional memory is used.

Initialization can be encapsulated into a macro which declares an instance variable (see NRF_BALLOC_DEF).

Declaration of the instance structure:

#include "nrf_log_instance.h"
typedef struct {
...
} foo_t;

Instantiation macro:

#define FOO_DEF(_name) \
NRF_LOG_INSTANCE_REGISTER(foo, _name, ....); \
static foo_t _name = { \
NRF_LOG_INSTANCE_PTR_INIT(p_log, foo, _name) \
... \
}

The NRF_LOG_INSTANCE_REGISTER macro creates a set of variables specific to the instance, such as name, current filtering level, or coloring.

Instance logging

Macros for logging on instance basis are very similar to the standard logging macros with an additional parameter - pointer to the instance.

void foo_init(foo_t * p_foo)
{
//init body
NRF_LOG_INST_INFO(p_foo->p_log, "Init");
}

Log messages specific to the instance are prefixed with <module_name>.<instance_name>, instead of the standard <module_name>.

In the command-line terminal (nrf_cli), logs can be enabled and disabled for the instance, using the same convention as for the modules, for example, log enable info foo.my_foo.

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 the 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 ensures that the message is not freed. nrf_memobj_put must be called when backend processes the message. The message is 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.

Flash logger backend


Documentation feedback | Developer Zone | Subscribe | Updated