TI Utilities API
Data Structures | Macros | Typedefs | Enumerations
Log Interface

The Log module provides APIs to instrument source code. More...

Data Structures

struct  Log_Module
 

Macros

#define Log_MODULE_DEFINE(...)
 
#define Log_MODULE_USE(...)
 
#define Log_EVENT_DEFINE(name, fmt)
 
#define Log_EVENT_USE(name, fmt)
 
#define Log_printf(module, level, ...)
 
#define Log_event(module, level, ...)
 
#define Log_buf(module, level, ...)
 
#define _Log_DEFINE_LOG_VERSION(module, version)
 

Typedefs

typedef enum Log_Level Log_Level
 
typedef const struct Log_Module Log_Module
 
typedef void(* Log_printf_fxn) (const Log_Module *handle, uint32_t header, uint32_t index, uint32_t numArgs,...)
 
typedef void(* Log_buf_fxn) (const Log_Module *handle, uint32_t header, uint32_t index, uint8_t *data, size_t size)
 

Enumerations

enum  Log_Level {
  Log_DEBUG = 1,
  Log_VERBOSE = 4,
  Log_INFO = 16,
  Log_WARNING = 64,
  Log_ERROR = 256,
  Log_ALL = 1 + 4 + 16 + 64 + 256,
  Log_ENABLED = 512
}
 

Detailed Description

The Log module provides APIs to instrument source code.

To access the LOG APIs, the application should include its header file as follows:

#include <ti/log/Log.h>

Beta Disclaimer

The logging ecosystem are to be considered beta quality. They are not recommended for use in production code by TI. APIs and behaviour will change in future releases. Please report issues or feedback to E2E.

Definitions

The following terms are used throughout the log documentation.

Term Definition
LogModule A parameter passed to Log APIs to indicate which software module the log statement originated from. Modules also control the routing of logs to sinks.
LogLevel The severity or importance of a given log statement.
Sink Also simply called a logger. This is a transport specific logger implementation.
The Logging framework is flexible such that multiple sinks may exist in a single firmware image.
CallSite A specific invocation of a Log API in a given file or program.
Record The binary representation of a log when it is stored or transported by a given sink. The log record format varys slightly with each sink depending on their implementation and needs. However, they all convey the same information.
Link Time Optimization (LTO) A feature of some toolchains that can significantly reduce the code overhead of the log statements through a process called dead code elimination. In order to maximize the benefits of this, all static libraries and application files should have LTO enabled.

Summary

The following sections describe the usage of the TI logging system implementation. This document will focus on the target (i.e. code that runs) on the embedded device. For associated PC tooling, please see the README in the tools/log/tiutils/ folder.

Desgin Philiosophy:

Stated Limitations

Anatomy of Log Statement

At the core of the logging implementation is heavy use of the C preprocessor. When reading an application, the Log APIs may look like function calls, but the preprocessor expands them heavily.

There are several ways in which the preprocessor is used.

Globally

  1. To enable/disable logs globally. If ti_log_Log_ENABLE is not defined, all statements are removed by the preprocessor. This does not rely on LTO or any other optimization. It removes any traces of logs from the program.

Per Log Statement

  1. (Level filtering): Insert the if statement that checks if the log level of the statement has been enabled in its module configuration. If the log level is not enabled, the process ends here.
  2. (String declaration): Automate placement of constant strings, format strings, and pointers to these strings in the the nonloadable metadata section of the out file. This saves FLASH on the target. Each sring contains a large amount of data, including the following:
    • File and line number of the log statement
    • The log level and module of the log statement
    • The format string
    • The number of arguments
  3. (Argument counting): Log APIs are variadic in nature, up to 8 arguments are supported. However, at preprocess time, the number of arguments must be known.
  4. (Name spacing): Routing from module to sink. The module parameter of the Log API controls which sink its log statements will be routed to. The preprocessor does name expansion to look up the enabled log levels and selected sink function pointers from the module's configuration structure. NOTE: The used sink may require initialization. Please verify with the specific sink documentation on how to initialize the sink.
  5. (Sink API Invocation): With the names resolved and levels checked, the logger is now ready to execute the sink function. This is done via function pointer.

An simplified pseudo-C implementation of what Log_printf(LogModule_App1, Log_DEBUG, "Hello World!"); would expand to is shown below. This will not compile and is not extensive, just for illustration.

// Global log enable check, wrapped around each log site
#if defined(ti_log_Log_ENABLE)
// Check if the level of this specific log statement has been enabled by the module
if (LogMod_LogModule_App1.levels & level) {
// Note that ^^ is the record separator. Pack meta information into format string. This is stored off target.
const string logMeta = "LOG_OPCODE_FORMATED_TEXT^^"../../log.c"^^80^^Log_DEBUG^^LogMod_LogModule_App1^^"Hello World!"^^0";
// Route log to the selected sink implementation. This is done via function pointer.
// The 0 indicates no arguments. If runtime arguments were provided, they would follow.
LogMod_LogModule_App1.printf(pointerToModuleConfig, 0);
}
#endif

From here, the logger has transferred control over to the sink implementation, which varys based on the tansport (e.g. circular buffer in memory or UART).

Modules

When adding log statements to the target software, it is recommended to create a logging module for each software component in the image. Modules enable the reader to understand where the log record originated from. Some log visualizers may allow the reader to filter or sort log statements by module. It is also recommended to namespace modules.

For example, a good module name for the UART driver that exists in source/ti/drivers, could be ti_drivers_UART.

Modules also control the routing of log records to a sink. Routing is controlled via the LogModule panel in SysConfig, but can be changed in plain C code using the macro Log_MODULE_DEFINE and passing the sink specific Log_MODULE_INIT_ to the init parameter within the Log_MODULE_DEFINE macro. An example for the LogBuf sink is below, it will do the following

  1. Create a module called LogModule_App1.
  1. Initialze the module for use with the buffer based LogSink. Use buffer instance called CONFIG_ti_log_LogSinkBuf_0.
  1. Enable only the Log_ERROR level. Other logs will not be stored.
#include <ti/log/Log.h>
#include <ti/log/LogSinkBuf.h>
Log_MODULE_DEFINE(LogModule_App1, Log_MODULE_INIT_SINK_BUF(CONFIG_ti_log_LogSinkBuf_0, Log_ERROR));

TI created libraries will never use Log_MODULE_DEFINE. This leaves the choice of routing logs to their sinks to the end application writer. This is recommended when creating any static libraries to defer the final logging decisions to link time.

Each new module will instantiate a Log_Module structure with a levels bitmap and pointers to the selected sink implementation and sink configuration. See the Log_Module structure for more information.

Levels

Log levels are a way to indicate the severity or importance of the contents of a particular log call site. Each call site takes an argument that allows the user to specify the level. As with modules, log visualization tools allow the user to sort or filter on a given level. This can help the reader to find important or relevant log statements in visualization.

Log levels are also used to control the emission of logs. Each call site will check that the level is enabled before calling the underlying log API.

Depending on optimization, the check at each log statement for whether the given level is enabled or not may end up being optimized away, and the entire log statement may be optimized away if the log level is not enabled.

if ((level) & module.levels) {// Call Log API
}

Optimization level -flto for both the TICLANG toolchain and GCC will typically be able to optimize the above statement.

Remarks

Log Metadata

Each time a Log API is invoked, a metadata string is placed in the .out file. This string contains information about the API type, file, line module, level, and other information associated with the log call site. Each call site emits a string to a specific memory section called .log_data. In addition to this, a pointer to the string in .log_data is stored in another section called .log_ptr. Because the .log_ptr section is always in the same location, and each entry is the same size, an indexing-scheme can be used to refer to each log-string. Entry 0 in .log_ptr would point to the first string, entry 1 would point to the second string, etc. This Is necessary on some devices where transmitting an entire 32-bit address as a reference to the string is not possible, and instead an 8-bit index can be transmitted across the Log sink implementation instead. In order to use logging, this section should be added to the linker command file. By default, this section points to a nonloadable region of memory. Meaning that the metadata will not be loaded on the target device. Instead, the various logging visualization tools such as wireshark and TI ROV2 will read the metadata from this section and properly decode the log statements. The benefit of this approach is that very little memory is consumed on target. Additionally, the log transport only needs to store or send pointers to this meta section when a log API is called.

This approach minimizes the amount of memory consumed on device and bytes sent over the transport. This section can be loaded on target if desired or if you are creating a custom logger. The design does not preclude this.

In order to use the logging framework, the log section must be added to the linker command file. Here is a sample for the TI linker. Other examples can be found in the TI provided linker files for each toolchain.

MEMORY
{
// List other memory regions here
LOG_DATA (R) : origin = 0x90000000, length = 0x40000
LOG_PTR (R) : origin = 0x94000008, length = 0x40000
}
SECTIONS
{
.log_data : > LOG_DATA, type = COPY
.log_ptr : { *(.log_ptr*) } > LOG_PTR align 4, type = COPY
}

Sinks

Sinks are responsible for storing or transporting the log record. In general there are two categories of sinks:

  1. Those that perform storage of logs.
  2. Those that stream logs over a transport medium, and thus do not perform storage.

Sinks may vary in their implementation based on the nature of the storage or transport that they support, but they all have the following in common:

In addition, some sinks require initialization. This will be listed in the documentation for the sink implementation. Sinks are closely tied to their associated host side tooling. Since the log statements are not parsed at all by the target code, this must be delegated to a program running on a PC. While the binary format of log records may vary across sink implementations, it is suggested that each log record contain:

  1. Timestamp
  1. Pointer to metadata string. This will be looked up by the PC side tooling in the out file.
  1. Runtime arguments

This is the minimum amount of information needed to decode a log statement.

Usage

This section provides a basic usage summary and a set of examples in the form of commented code fragments. Detailed descriptions of the LOG APIs are provided in subsequent sections.

Synopsis

// Import the Log header
#include <ti/log/Log.h>
// Define your log module and log sink
// If using SysConfig, it will be done automatically, or it can be done manually:
// Use helper macro from <ti/log/LogSinkBuf.h> to make a sink instance (buffer + config) with 100 entries.
Log_SINK_BUF_DEFINE(MyBufferSink, LogSinkBuf_Type_CIRCULAR, 100);
// Use helper macro from <ti/log/Log.h> to make a module pointing at the new sink instace.
// This example will enable all log levels
Log_MODULE_DEFINE(MyModule, Log_MODULE_INIT_SINK_BUF(MyBufferSink, Log_ALL))
// Some log sinks may require special initialization to configure hardware. Refer to the documentation of
// the sink you wish to use. For example, LogSinkITM must be initialised like this before it can be used:
// LogSinkITM_init();
// Invoke one of the log APIs you want to use for either premade events or formatted strings
Log_printf(MyModule, Log_DEBUG, "The answer is %d", 42);
uint8_t buffer[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Log_buf(MyModule, Log_VERBOSE, buffer, sizeof(buffer));

Examples

Log Event:

The following example demonstrates how to create a log event object and use it in the code. There are two steps to using a log event: 1. instantiation and 2. call site(s). Instantiation creates the event and the necessary metadata, and call site is where the event is actually recorded by the logger framework.

// Create a log event data type called LogEvent_count
// The log module is MyModule
// The format string is "count=%d" -- this should describe what the event does
Log_EVENT_DEFINE(LogEvent_count, "count=%d");

Later on, in the application, the count event is consumed. Note the log module must match between event creation and call site. In the code below, a LogEvent record is created for serialization or stage by the Log sink.

Log_EVENT_USE(LogEvent_count); // If not defined in same file
// ...
Log_event(MyModule, Log_DEBUG, LogEvent_count, count++);

Log Printf:

The following example demonstrates use of the Log printf API. in code. Log will embed the format string in the call site and will take arguments using varadic arguments

Log_printf(MyModule, Log_DEBUG, "Hello World!");

Log Buf:

The following example demonstrates use of the Log buf API. in code.

Buf will embed the format string in the call site and will take the buffer as a pointer and length. Buffers are treated as arrays of bytes. The buffer API should only be used when it is necessary to log data that is only available at runtime. It will actually send or store the entire contents of the buffer, so this API should be used sparingly as it is costly in terms of runtime and memory overhead.

uint8_t bufferToLog[] = {0, 1, 2, 3, 4, 5};
Log_buf(ti_log_LogMain, Log_DEBUG, "The contents of bufferToLog are: ", bufferToLog, sizeof(bufferToLog));

Log API usage:

For a uniform experience with the logging tool, users are recommended to follow certain guidelines regarding the Log API. Typical use-cases for each API call is desribed below

Log_printf

Log_printf should be the default mechanism for emitting a log statement within an application. Along with the Log-levels, Log_printf should be used to communicate debug information as a formatted string, which accepts variadic arguments. In this case, a pointer to the string and the arguments themselves are transported by the Log sink.

Log_printf(MyLibraryLogModule, Log_ERROR, "Library function received illegal argument: %d", arg);

Log_event

Log_event is meant to represent more generic debug-information, and typically something that can occur from anywhere in the application, as opposed to being localised in a single library. Events can also be defined once and referenced from anywhere in the application, so the same event can be used by multiple libraries. A generic example would be an event such as "Entering critical section"

Log_EVENT_DEFINE(LogEvent_enterCritical, "Entering critical section");
Log_EVENT_USE(LogEvent_enterCritical); // If not defined in same file
// ...
Log_event(MyModule, Log_DEBUG, LogEvent_enterCritical);

Log_buf

When the debug-information to be emitted is a large amount of dynamic data, and is not suitable as an argument to printf, then Log_buf should be used. Log_buf can transport the contents of large dynamic buffers, and as a consequence has a larger overhead and should be used sparsely.

Macro Definition Documentation

§ Log_MODULE_DEFINE

#define Log_MODULE_DEFINE (   ...)

§ Log_MODULE_USE

#define Log_MODULE_USE (   ...)

§ Log_EVENT_DEFINE

#define Log_EVENT_DEFINE (   name,
  fmt 
)

§ Log_EVENT_USE

#define Log_EVENT_USE (   name,
  fmt 
)

§ Log_printf

#define Log_printf (   module,
  level,
  ... 
)

§ Log_event

#define Log_event (   module,
  level,
  ... 
)

§ Log_buf

#define Log_buf (   module,
  level,
  ... 
)

§ _Log_DEFINE_LOG_VERSION

#define _Log_DEFINE_LOG_VERSION (   module,
  version 
)

Typedef Documentation

§ Log_Level

typedef enum Log_Level Log_Level

§ Log_Module

typedef const struct Log_Module Log_Module

§ Log_printf_fxn

typedef void(* Log_printf_fxn) (const Log_Module *handle, uint32_t header, uint32_t index, uint32_t numArgs,...)

§ Log_buf_fxn

typedef void(* Log_buf_fxn) (const Log_Module *handle, uint32_t header, uint32_t index, uint8_t *data, size_t size)

Enumeration Type Documentation

§ Log_Level

enum Log_Level
Enumerator
Log_DEBUG 
Log_VERBOSE 

This should be the default level, reserved to be used by users to insert into applications for debugging. Exported libraries should avoid using this level.

Log_INFO 

This level is recommended to be used in libraries to emit verbose information

Log_WARNING 

This level is recommended to be used in libraries to emit simple information

Log_ERROR 

This level is recommended to be used in libraries to emit warnings. It is up to the library developer to decide what constitutes a warning, but it should typially indicate something unexpected, but not something that leads to system failure

Log_ALL 

This level is recommended to be used in libraries to emit errors. Typically, this should be used when something has failed and the system is unable to continue correct operation

Log_ENABLED 

This enables all levels

© Copyright 1995-2023, Texas Instruments Incorporated. All rights reserved.
Trademarks | Privacy policy | Terms of use | Terms of sale