nRF5 SDK  v14.1.0
Choose documentation:
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
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:

  • Four severity levels - ERROR, WARNING, INFO, and DEBUG.
  • Formatted string (printf-like) log format.
  • Dedicated macro for dumping data.
  • Configurable global severity level.
  • Independent severity level for each module.
  • Enabling and disabling logging per module and globally.
  • Optional timestamping.
  • Optional coloring of the logs for different modules and different severity levels.
  • Prefixing log entries with the module name and severity.
  • Deferring of log processing to idle stage.
  • Logging mechanism decoupled from the logger backend.
  • Multiple backends supported
  • Following backends are provided: RTT, UART, console (nrf_cli)
  • Optional dynamic filtering of logs.

Logs processing

Logs can be processed in two ways:

  • In-place processing.
  • Deferred processing.

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:

  • Number of arguments in a formatted string is limited to six.
  • All arguments are treated as uint32_t. This results in the following:
    • In certain situations (e.g. smaller variables like uint8_t as arguments) the compiler will give a warning and the argument must be masked and cast to uint32_t.
    • The logger cannot handle arguments bigger than 32 bits so they must be explicitly split up by the user into two separate entries.
  • Since the logs are not processed directly, it is risky to pass pointers as arguments, especially to variables that do not have static addresses, for example a variable allocated on the stack. By the time the log is processed, the pointer can point to invalid data. The logger provides a mechanism to overcome this issue by enforcing a string that is copied into the internal logger buffer.

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:

  • NRF_LOG_ENABLED - Enable the logger. If disabled, then the logger and all logger entries are not compiled.
  • NRF_LOG_DEFAULT_LEVEL - Default severity level. Available options are: ERROR (highest), WARNING, INFO, and DEBUG (lowest). This option configures the lowest severity level that can be present in the system. For example, if it is set to the INFO level, all DEBUG entries are not compiled. Every module can set a local severity level and the actual severity level that is applied for the module is the highest pick from local and global. For example, if the module severity level is set to DEBUG but the default severity level is set to INFO, then all debug log entries in the module are not compiled.
  • NRF_LOG_USES_TIMESTAMP - If enabled, then a timestamp is added to every log entry. The user must provide a function that returns a 32-bit timestamp value during initialization. The source of the timestamp is not important for the logger. If timestamping is enabled and this function is not provided, initialization fails with an error.
  • NRF_LOG_USES_COLORS - If enabled, then every string is prefixed with an ANSI escape code with the configured color. The user can configure colors of errors (NRF_LOG_ERROR_COLOR) and warnings (NRF_LOG_WARNING_COLOR) for all modules, and the default color for info and debug logs. Colors for info and debug logs can be overriden by every module.
  • NRF_LOG_DEFERRED - If enabled, then the logs are not processed directly where they are called but deferred to idle state. See Logs processing. When NRF_LOG_DEFERRED is enabled, the size of the internal buffer can also be configured. The size is provided in 32-bit words. Standard entries take from one to eight words.
  • NRF_LOG_ALLOW_OVERFLOW - If enabled, then if internal circular buffer is full oldest entries are removed and new entries are stored.
  • NRF_LOG_FILTERS_ENABLED - If enabled, then logs can be compiled in but not used unless enabled (especially useful when nrf_cli is used as backend).

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

  • Enabling logs in the module.
  • Independent lowest severity level.
  • Info logs coloring.
  • Debug logs coloring.

Usage

Logger usage can be divided into two tasks:

  • Controlling the logger - Initialization, reconfiguration, and triggering processing (in the deferred mode).
  • Logging.

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:

  • Standard logs with syntax very similar to printf - NRF_LOG_<SEVERITY_LEVEL>().
  • Hexdump - a method for dumping the data - NRF_LOG_HEXDUMP_<SEVERITY_LEVEL>().

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:

  • backend_put - this function is called when a log entry is processed by the frontend and passed current backend filtering. A message is provided as an argument. The message contains unprocessed data (string pointer and arguments in case of standard log entry).
  • panic_set - this function is called when NRF_LOG_FINAL_FLUSH() is called. It informs the backend that it should switch to panic mode (e.g. stop using interrupts). Backend can return NRF_ERROR_NOT_SUPPORTED and logger will stop using this backend.
  • flush - optional flush.

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.