![]() |
![]() |
TI Utilities API
|
The Log module provides APIs to instrument source code. More...
Data Structures | |
struct | Log_Module |
Log module. More... | |
Macros | |
#define | Log_TI_LOG_VERSION 0.1.0 |
Log version. More... | |
#define | Log_MODULE_DEFINE(name, init) const Log_Module LogMod_ ## name = init |
Defines a log module. More... | |
#define | Log_MODULE_DEFINE_WEAK(name, init) const __weak Log_Module LogMod_ ## name = init |
Defines Log module as weak. More... | |
#define | Log_MODULE_USE(name) extern const Log_Module LogMod_ ## name |
Declares a reference to a log module. More... | |
#define | LOG_MODULE_SYM(name) LogMod_ ## name |
Resolves to the symbol name of the log module. More... | |
#define | Log_buf(module, level, format, data, size) _Log_buf_B(module, level, format, data, size) |
Log a continuous block of memory. More... | |
#define | Log_printf(module, level, ...) _Log_printf_B(LOG_OPCODE_FORMATED_TEXT, module, level, __VA_ARGS__) |
Log an event with a printf-formatted string. More... | |
#define | Log_MODULE_SET_LEVELS(module, levels) |
Set a log module's log level bitmask. More... | |
#define | Log_MODULE_GET_LEVELS(module) |
Get a log module's log level bitmask. More... | |
Typedefs | |
typedef enum Log_Level | Log_Level |
Log level bitmask values. More... | |
typedef const struct Log_Module | Log_Module |
typedef void(* | Log_printf_fxn) (const Log_Module *handle, uint32_t header, uint32_t headerPtr, uint32_t numArgs,...) |
typedef void(* | Log_printfN_fxn) (const Log_Module *handle, uint32_t header, uint32_t headerPtr,...) |
typedef void(* | Log_buf_fxn) (const Log_Module *handle, uint32_t header, uint32_t headerPtr, uint8_t *data, size_t size) |
Enumerations | |
enum | Log_Level { Log_DEBUG = 1 << 0, Log_VERBOSE = 1 << 2, Log_INFO = 1 << 4, Log_WARNING = 1 << 6, Log_ERROR = 1 << 8, Log_ALL = Log_DEBUG + Log_VERBOSE + Log_INFO + Log_WARNING + Log_ERROR, Log_NONE = 0 } |
Log level bitmask values. More... | |
The Log module provides APIs to instrument source code.
To access the LOG APIs, the application should include its header file as follows:
The following terms are used throughout the log documentation.
Log module
: 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.Log level
: The severity or importance of a given log statement.Log sink
: Also simply called a logger. This is a transport-specific logger implementation on the target side. Call site
: 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 varies slightly with each sink depending on their implementation and needs. However, they all convey the same information.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.
Design Philosophy:
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.
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.
This define is pushed to ti_utils_build_compiler.opt
whenever any Log module is enabled in SysConfig.
To enable/disable logs by module. If ti_log_Log_ENABLE_<MyLogModuleName>=1
is not defined, all statements using that Log module are removed by the preprocessor. This does not rely on LTO or any other optimization. Removing the define removes all traces of the log from the compiled code. Just defining the symbol name ti_log_Log_ENABLE_<MyLogModuleName>
without setting it to 1 will not include Log statements during compilation.
These defines are automatically pushed to ti_utils_build_compiler.opt
for all modules configured in SysConfig.
Some TI libraries that have logging enabled also contain multiple log modules. Enabling only a subset of Log modules via the preprocessor will not cause the Log statements associated with the remaining Log modules to be removed since this is a compile-time event. The Log statements associated with individual modules can be removed from logging-enabled TI libraries by recompiling those libraries without the module-level flags in question.
If a Log Module's Log_Module.dynamicLevelsPtr is set to NULL, dynamic module log levels will be disabled for the module and only the constant defined 'levels' bitmap will be used for the Log Module. If a Log Module's Log_Module.dynamicLevelsPtr is not NULL, it will be set to a separate, volatile, levels
bitmap for the Log Module. This separate bitmap will replace the previous, constant defined 'levels' bitmap for all module log level comparisons. In doing so, the log module's log levels will be dynamic and can be changed at runtime by the logging macro Log_MODULE_SET_LEVELS(). Changing a module's log levels as a runtime operation enables total control over which log levels are enabled.
As mentioned, if dynamic module log levels are enabled, comparisons done by the Log APIs on a module's levels
bitmap will be at runtime instead of compile-time. This will both increase code size and increase logging execution time. Before enabling dynamic module log levels, these tradeoffs should be considered.
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.
From here, the logger has transferred control over to the sink implementation, which varies based on the transport (e.g. circular buffer in memory or UART).
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 Log Modules panel in SysConfig, but can be changed in plain C code - despite this being generally discouraged. To do it, use the macro Log_MODULE_DEFINE and pass the sink specific Log_MODULE_INIT_
to the init
parameter within the Log_MODULE_DEFINE macro. Log_MODULE_INIT_
uses delegate functions for printf and buf that are implementation dependent for each sink based on the available hardware and whether the function is implemented for optimization. An example for the LogBuf sink is below, it will do the following
LogModule_App1
.CONFIG_ti_log_LogSinkBuf_0
.Log_ERROR
level. Other logs will not be stored.LogSinkBuf_printfSingleton
for printf and LogSinkBuf_bufDepInjection
for buf.NULL
.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.
If dynamic module log levels are enabled, each new Log_Module structure will include a pointer to a separate, volatile, levels
bitmap. This new bitmap will be used for runtime comparisons of a module's log level. Two logging macros can be used to both set and get a module's log level. Log_MODULE_SET_LEVELS can be used to set the log level of a specific log module and Log_MODULE_GET_LEVELS can be used to retrieve the log level of a specific log module.
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.
Additionally, depending whether dynamic module log levels are enabled or not, checking the level at each log statement will be evaluated at runtime or compile-time. Enabling dynamic module log levels or disabling Link-Time-Optimisation (LTO) will cause each log statement to perform a runtime check and will also increase the code size.
Optimization level -flto
for both the TICLANG toolchain and GCC will typically be able to optimize the above statement.
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 ROV 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.
Sinks are responsible for storing or transporting the log record. In general there are two categories of sinks:
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:
<SinkName>
is the name of the sink.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:
This is the minimum amount of information needed to decode a log statement.
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.
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.
The arguments are type-cast to a uintptr_t, which is an unsigned integer type. This limits the supported format specifiers to the following:
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.
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 described below
Log_printf should be the default mechanism for generating 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.
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.
#define Log_TI_LOG_VERSION 0.1.0 |
Log version.
#define Log_MODULE_DEFINE | ( | name, | |
init | |||
) | const Log_Module LogMod_ ## name = init |
Defines a log module.
Log modules are like namespaces for log statements, but also controls the enabled log levels and decides where the log statement is redirected.
[in] | name | Name of the log module. Gets prefixed with LogMod_ . |
[in] | init | Initialization macro from the wanted sink |
This is a helper to define Log_Module LogMod_yourName
and initialize it with the configuration and functions of the wanted log sink.
For example, you have already used the sink definition macros found in LogSinkITM.h, and now you want to define a new module that uses this:
`Log_MODULE_DEFINE(MyDriver, Log_MODULE_INIT_SINK_ITM(Log_DEBUG | Log_ERROR, LogSinkBuf_printfSingleton, LogSinkBuf_bufSingleton, Null))`
Perhaps you used the LogSinkBuf.h helper macro which needs a unique name per instance and made a separate buffer for critical errors:
`Log_MODULE_DEFINE(MyCritical, Log_MODULE_INIT_SINK_BUF(criticalBuf, Log_ERROR, LogSinkBuf_printfDepInjection, LogSinkBuf_bufDepInjection, NULL)`
You would use this in your application via Log_printf(MyCritical, Log_ERROR, "Oops")
#define Log_MODULE_DEFINE_WEAK | ( | name, | |
init | |||
) | const __weak Log_Module LogMod_ ## name = init |
Defines Log module as weak.
If there are multiple modules containing Log statements per library, special care must be taken not to create link-time failures. Whether Log statements from a library are present in the final binary is determined by the library configuration the application links against (instrumented vs uninstrumented). Each Log statement has a link-time dependency on its Log module. Enabling only a subset of Log modules contained within the library will cause any Log statements from other Log modules of that library to fail at link-time. This is avoided by declaring a weak instance of each Log module in C code that is compiled into the library. That way, the SysConfig-generated Log module definitions will override the weak library ones but they are there if SysConfig does not define that particular module.
[in] | name | Name of the log module. Gets prefixed with LogMod_ . |
[in] | init | Initialization value of the Log_Module struct. |
#define Log_MODULE_USE | ( | name | ) | extern const Log_Module LogMod_ ## name |
Declares a reference to a log module.
Declares that a log module is defined in another file so that it can be used in the file with this macro in it.
Log
and Log_buf
statements.[in] | name | Name of the log module. Gets prefixed with LogMod_ . |
#define LOG_MODULE_SYM | ( | name | ) | LogMod_ ## name |
Resolves to the symbol name of the log module.
Provided for forward compatibility purposes should you have a need to reference the log module symbol directly.
#define Log_buf | ( | module, | |
level, | |||
format, | |||
data, | |||
size | |||
) | _Log_buf_B(module, level, format, data, size) |
Log a continuous block of memory.
Use this macro to send out runtime data from the device. This API should be used when the data is non constant and can only be derived at runtime. It is the most intrusive in terms of record overhead and instructions used.
[in] | module | Log module that the buffer originated from |
[in] | level | Log level of type Log_Level |
[in] | format | Restricted format string |
[in] | data | Pointer to array of bytes (uint8_t *) |
[in] | size | Size in bytes of array to send |
#define Log_printf | ( | module, | |
level, | |||
... | |||
) | _Log_printf_B(LOG_OPCODE_FORMATED_TEXT, module, level, __VA_ARGS__) |
Log an event with a printf-formatted string.
Use this macro to enable printf style logging. This API offers the most flexibility as the construction of the format string is embedded in the call site of the API. It also supports true variadic arguments.
[in] | module | Log module that the buffer originated from |
[in] | level | Log level of type Log_Level |
[in] | ... | Variable amount of arguments. Must match your event or format-string. |
Examples:
Log_printf(MyModule, Log_INFO, "Hello World")
Log_printf(MyModule, Log_DEBUG, "Age: %d", 42)
#define Log_MODULE_SET_LEVELS | ( | module, | |
levels | |||
) |
Set a log module's log level bitmask.
If dynamic module log levels is enabled, use this macro to set a specific log module's log level bitmask as a runtime operation.
[in] | module | Name of the log module. Gets prefixed with LogMod_ . |
[in] | levels | Log level bitmask containing zero or more Log_Level |
#define Log_MODULE_GET_LEVELS | ( | module | ) |
Get a log module's log level bitmask.
If dynamic module log levels is enabled, use this macro to get a specific log module's log level bitmask as a runtime operation.
[in] | module | Name of the log module. Gets prefixed with LogMod_ . |
Log level bitmask values.
One of these enum values should be used in each Log_printf and Log_buf call and one or more should be used in each Log_Module definition.
typedef const struct Log_Module Log_Module |
typedef void(* Log_printf_fxn) (const Log_Module *handle, uint32_t header, uint32_t headerPtr, uint32_t numArgs,...) |
typedef void(* Log_printfN_fxn) (const Log_Module *handle, uint32_t header, uint32_t headerPtr,...) |
typedef void(* Log_buf_fxn) (const Log_Module *handle, uint32_t header, uint32_t headerPtr, uint8_t *data, size_t size) |
enum Log_Level |
Log level bitmask values.
One of these enum values should be used in each Log_printf and Log_buf call and one or more should be used in each Log_Module definition.
Enumerator | |
---|---|
Log_DEBUG | 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_VERBOSE | This level is recommended to be used in libraries to emit verbose information about the operation of the system and system state. This level is typically used for very frequently emitted logs or for very detailed context and state information. Enabling this level in multiple Log_Module may impact the operation of the application or saturate available log sink bandwidth and cause stalls in log record emission. |
Log_INFO | This level is recommended to be used in libraries to emit simple information about the operation of the system and the system state. Enabling this level in multiple Log_Module may impact the operation of the application or saturate available log sink bandwidth and cause stalls in log record emission. |
Log_WARNING | This level is recommended to be used in libraries to emit warnings. It should typically indicate something unexpected, but not something that automatically leads to system failure. Warnings may be generated during the regular operation of an application. Frequent warnings may indicate a temporary or systemic issue with the system that may require intervention such as a reset or alteration to the software or hardware design. The typical usecase for this level is a recoverable failure such as a failed heap allocation or CRC error in a wireless transmission. Enabling this level in a Log_Module will not impact regular device operation or impact available log sink bandwidth. |
Log_ERROR | 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. Errors are not generated during regular system operation. An error record being generated always indicates an issue with system that requires intervention. The intervention depends on the error but could be a system reset or changes to the software or hardware of the system. Enabling this level in a Log_Module will not impact regular device operation or impact available log sink bandwidth. |
Log_ALL | This value enables all levels. Should only be used in Log_Module definitions. |
Log_NONE | This value disables all levels when used by itself. Should only be used in Log_Module definitions. |