日志库
概述
ESP-IDF 提供了一套灵活的日志系统,包括两个可配置版本 Log V1 和 Log V2,可通过 CONFIG_LOG_VERSION 参数进行选择。本文档概述了这两个日志系统版本的特性、配置及使用方法,并比较了二者的性能表现。
Log V1:默认的原始实现方式,具备简洁性,针对早期日志和 DRAM 日志进行了优化,但 flash 占用较高,缺乏灵活性。
Log V2:增强的实现方式,更加灵活,降低了 flash 占用,并集中处理日志格式,但需要更多的堆栈。
Log V2 向后兼容 Log V1,这意味着使用 Log V1 编写的项目可以直接切换到 Log V2,无需额外修改。但是,由于兼容性限制,使用 Log V2 特定功能的项目不能恢复到 Log V1。
Log V1 的特性
日志格式由
format
参数定义,在编译时嵌入了 flash 中。相比 ESP_LOG,能更快记录早期日志和 DRAM 日志。
实现简单,但具有局限性:
由于包含冗余的格式化信息,二进制文件体积较大。
不支持自定义日志格式,缺乏灵活性。
编译错误所指向的宏中的参数位置编号不准确。
Log V2 的特性
通过单个函数
esp_log()
集中处理格式。仅存储用户定义的格式字符串,从而减小二进制文件大小。
仅在输出需要且日志级别允许记录时,才会获取时间戳。
允许自定义日志输出:
为某个层级(全局、文件或日志消息)启用或禁用颜色、时间戳或标签。
输出不经过格式化处理的原始日志(适用于二进制日志)。
为引导加载程序和应用程序采用不同的日志设置。
格式参数可以动态设置为变量,构建日志消息更灵活。
在引导加载程序、ISR、启动代码和受限环境中,日志处理机制保持统一。
缺点:
消耗更多的栈和内存。
日志处理速度比 Log V1 略慢,但与传输数据的时间相比(例如通过 UART),差异可以忽略不计。
日志级别
对于应用程序和引导加载程序,日志级别需要分别配置。开发者可以通过 Kconfig 选项为每个模块设置不同的日志级别,从而实现配置的独立性。例如,可以为引导加载程序启用简洁的日志,而为应用程序启用详细的调试日志。使用引导加载程序专用的 Kconfig 选项,可以为引导加载程序独立配置日志级别,不会影响主应用程序。
日志库共有六个详细程度级别:
Verbose - 输出高度详细且频繁的调试信息,通常包括内部状态,可能会使输出过于繁杂。(最高级别)
Debug - 输出详细的诊断信息(例如变量值、指针地址等),适用于调试。
Info - 输出描述系统正常运行的一般信息。
Warning - 输出可能引发问题,但已被处理或减轻影响的事件。
Error - 仅输出严重错误,这些错误如果不进行干预处理,软件无法自行恢复。
None - 无日志输出,即完全禁用日志。(最低级别)
日志级别设置
通过日志级别设置,可以选择将哪些日志包含在二进制文件中,并决定这些日志在运行时的可见性。日志级别设置包括以下两种:
日志级别:指定在运行时显示哪些级别的日志。引导加载程序的 日志级别 通过 CONFIG_BOOTLOADER_LOG_LEVEL 配置,而应用程序的 日志级别 通过 CONFIG_LOG_DEFAULT_LEVEL 设置。通过函数
esp_log_get_default_level
能够获取当前日志级别。最高日志级别:指定将哪些日志级别包含在二进制文件中。高于此级别的日志会在编译时丢弃,不包含在最终镜像中。对于应用程序,最高日志级别 可以设置得高于 日志级别,从而在二进制文件中包含额外的日志,必要时,便可通过
esp_log_level_set()
启用这些日志以帮助调试。使用 CONFIG_LOG_MAXIMUM_LEVEL 选项可以为应用程序启用此功能。引导加载程序不支持此功能,其 最高日志级别 始终与 日志级别 相同。
例如,如果将 日志级别 设置为 Warning,最高日志级别 设置为 Debug,则二进制文件会包含 Error、Warning、Info 和 Debug 级别的日志。然而,在运行时仅输出 Error 和 Warning 级别的日志,除非通过 esp_log_level_set()
显式更改日志级别。根据具体需求,日志级别可以提高或降低。
设置 最高日志级别
根据 LOG_LOCAL_LEVEL
的定义,可使用此参数覆盖特定源文件或组件的 最高日志级别,而无需修改 Kconfig 选项。此参数能设置一个本地的 最高日志级别,从而启用或排除二进制文件中的特定日志。
通过此方法,能够有效为代码的特定部分提供更详细的日志,而无需全局提高 最高日志级别,避免了对二进制文件大小产生不必要的影响。
更改某个源文件的 最高日志级别 (不要在头文件中添加该定义,因为头文件采用单次包含的机制,可能无法生效):在包含
esp_log.h
之前,使用esp_log_level_t
中的一个值来定义LOG_LOCAL_LEVEL
,指定将哪些日志消息包含在该源文件的二进制文件中。// 在某个 my_file.c 文件中 #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE #include "esp_log.h"
更改整个组件的 最高日志级别:在组件的 CMakeLists.txt 文件中定义
LOG_LOCAL_LEVEL
。这确保指定的日志级别适用于组件内的所有源文件,指定将哪些日志消息包含在二进制文件中:# 在组件的 CMakeLists.txt 文件中 target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE")
运行时更改 日志级别
仅应用程序支持在运行时更改日志级别,启动引导加载程序不支持此功能。
默认情况下,系统启动时会启用 日志级别 以下的所有日志级别。可以使用函数 esp_log_level_set()
全局或按模块设置 日志级别。模块可通过标签识别,这些标签是人类可读以零结尾的 ASCII 字符串。此功能依赖于 CONFIG_LOG_DYNAMIC_LEVEL_CONTROL,此选项默认启用。如无需此功能,可以将其禁用,以减少代码量并提升性能。
例如,将所有组件的日志级别设置为 ERROR
(全局设置):
esp_log_level_set("*", ESP_LOG_ERROR);
根据模块(标签)调整日志输出的功能依赖于 CONFIG_LOG_TAG_LEVEL_IMPL,该选项默认启用。如不需要此功能,可以将其禁用,以减少代码量并提升性能。
例如,仅将 Wi-Fi 组件的日志级别设置为 WARNING
(特定模块设置):
esp_log_level_set("wifi", ESP_LOG_WARN);
使用日志库
在每个使用日志功能的 C 文件中定义 TAG
变量。
// #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE // 可选:增加包含在二进制文件中的日志级别(仅适用于本文件)
#include "esp_log.h"
static const char* TAG = "MyModule";
// ...
ESP_LOGI(TAG, "Baud rate error %.1f%%. Requested: %d baud, actual: %d baud", error * 100, baud_req, baud_real);
ESP_EARLY_LOGW(TAG, "Early log message %d", i++);
ESP_DRAM_LOGE(DRAM_STR("TAG_IN_DRAM"), "DRAM log message %d", i++); // 如果需要,使用 DRAM_STR 宏添加 DRAM
I (112500) MyModule: Baud rate error 1.5%. Requested: 115200 baud, actual: 116928 baud
W (112500) MyModule: Early log message 1
E TAG_IN_DRAM: DRAM log message 2
备注
TAG
变量指向存储在 flash 中的一个字符串字面量。如果在单个构建单元(翻译单元)中多次使用相同的 TAG
字符串,编译器和链接器通常会通过 字符串池化 过程将其优化为 flash 中的单个副本。然而,如果不同的组件或翻译单元使用了相同的 TAG
字符串,每个组件或单元在 flash 中都会存储一个副本,除非应用了全局链接器优化。
日志库提供了多种宏以适应不同的使用场景,例如通用日志记录、早期启动日志记录和受限环境日志等,如下所示。选择合适的宏并据此构建相应的程序结构,有助于优化性能,确保可靠运行。但是,建议在设计程序结构时尽量避免在受限环境中进行日志记录。
Verbose:
ESP_LOGV
,ESP_EARLY_LOGV
,ESP_DRAM_LOGV
.Debug:
ESP_LOGD
,ESP_EARLY_LOGD
,ESP_DRAM_LOGD
.Info:
ESP_LOGI
,ESP_EARLY_LOGI
,ESP_DRAM_LOGI
.Warning:
ESP_LOGW
,ESP_EARLY_LOGW
,ESP_DRAM_LOGW
.Error:
ESP_LOGE
,ESP_EARLY_LOGE
,ESP_DRAM_LOGE
.
这些宏可分为以下三组:
ESP_LOGx: 标准日志宏,适用于正常运行期间的大多数用例。在非受限环境下,可在应用程序代码中使用这些宏来记录日志,但不要在中断服务例程 (ISR)、早期启动阶段或 flash 缓存被禁用时使用。这些宏的一个重要特点是,它们使用 Newlib 库的 vprintf 函数进行格式处理和日志输出。
ESP_EARLY_LOGx: 专为早期启动阶段的受限环境设计,在堆分配器或系统调用尚未初始化时使用。这些宏通常用于关键的启动代码或中断被禁用的关键区域。这些宏的一个重要特点是,它们使用 ROM 的 printf 函数,以微秒为单位输出时间戳,并且不支持按模块设置日志详细级别。
ESP_DRAM_LOGx: 专为受限环境设计,在中断被禁用或 flash 缓存不可访问时记录日志。这些宏可能会影响性能,应谨慎使用。这些宏适用于其他日志宏可能无法可靠运行的关键区域或中断例程。这些宏的特点是,它们使用 ROM 的 printf 函数,不输出时间戳,将格式参数分配在 DRAM 中以确保缓存禁用时的可访问性,并且不支持按模块设置日志详细级别。
备注
使用 DRAM_STR("my_tag") 宏在 DRAM 中分配标签。这能够确保在 flash 缓存被禁用时仍能访问标签。
Log V1 和 Log V2 的区别在于,在 Log V2 中,所有来自这些宏的日志都发送到同一个处理程序进行处理。该处理程序可以自动检测受限环境(例如,早期启动、禁用中断或 flash 缓存不可访问的情景),并动态选择适当的打印函数,确保在不同的运行环境中实现高效的日志记录。
日志格式
Log V1:仅支持全局禁用颜色格式。其他格式选项(如时间戳和标签)始终启用。
Log V2:
允许完全自定义日志格式,包括全局、按文件、按模块、为单个日志消息禁用颜色、标签和时间戳格式。
更精细的日志输出控制,更适用于特定的用例和环境。
// #define ESP_LOG_COLOR_DISABLED (1) /* 仅用于 Log v2 */
// #define ESP_LOG_TIMESTAMP_DISABLED (1) /* 仅用于 Log v2 */
#include "esp_log.h"
static const char* TAG = "boot";
// ...
ESP_LOGI(TAG, "chip revision: v%d.%d", major, minor);
I (56) boot: chip revision: v3.0
level name |end of line
| |
[0;32mI (56) boot: chip revision: v3.0[0m
|_____| |___||____||_________________||_|
|start | |tag | |end color
|color | |user string
|timestamp
日志系统支持以下格式选项,并且同时适用于应用程序和引导加载程序:
Color:增加颜色代码,全局增强日志的可见性。由 CONFIG_LOG_COLORS 控制,默认情况下禁用,因为 ESP-IDF 监视工具 idf.py monitor 可以通过 级别名称 检测日志级别并应用标准的 IDF 颜色方案。
对于 Log V2,选项 CONFIG_LOG_COLORS_SUPPORT 支持在运行时为特定日志、文件或组件添加颜色输出,即使全局颜色已禁用。此时要为特定上下文启用颜色,请使用
ESP_LOG_COLOR_DISABLED
。
Level Name:表示日志详细级别的单个字母(I, W, E, D, V),显示在每条日志消息的开头,用于识别日志级别。这在禁用颜色时非常有用,例如在禁用颜色时 ESP-IDF 监视工具就会使用该信息。
Timestamp:为日志消息全局添加时间戳。由 CONFIG_LOG_TIMESTAMP_SOURCE 控制。
None:不显示时间戳。在日志分析或调试中,当时间不关键时非常有用,还能够节省处理性能和内存。仅适用于 Log V2。
Milliseconds since boot (18532) (默认):通过 RTOS 时钟 tick 计数乘以 tick 周期得出。
System time (HH:MM:SS.sss) 14:31:18.532:以小时、分钟、秒和毫秒显示时间。
System time (YY-MM-DD HH:MM:SS.sss) (2023-08-15 14:31:18.532):同上,还包括日期。
Unix time in milliseconds (1692099078532):以毫秒显示 Unix 时间。
对于 Log V2,选项 CONFIG_LOG_TIMESTAMP_SUPPORT 支持在运行时为特定日志、文件或组件添加时间戳输出,即使全局时间戳已禁用。要为特定上下文启用 Milliseconds since boot 时间戳,请使用
ESP_LOG_TIMESTAMP_DISABLED
。
Tag:显示用户定义的源模块标识符。
对于 Log V2,可以将 tag 设置为
NULL
传递给宏,在这种情况下,tag 不会被打印,且无法按组件进行日志级别检查。
End Line:在日志消息的末尾添加换行符。
以下选项仅适用于 Log V2,并与提供的日志宏一起使用。这些定义可以用和 LOG_LOCAL_LEVEL
相同的方式设置。它们的作用范围取决于定义的位置(例如文件、组件或全局):
ESP_LOG_CONSTRAINED_ENV:
定义为
1
时,强制日志处理程序esp_log()
使用适合指定作用域的安全 printf 函数。
ESP_LOG_FORMATTING_DISABLED:
默认为
0
,即启用所有格式化项,如颜色、时间戳、标记和末尾换行。定义为
1
时,为指定范围禁用所有的格式化项。
ESP_LOG_COLOR_DISABLED: 要求 CONFIG_LOG_COLORS_SUPPORT 启用。
如果全局颜色 (CONFIG_LOG_COLORS) 已禁用,则定义为
0
,以启用指定范围的颜色输出。如果启用了全局颜色 (CONFIG_LOG_COLORS),则定义为
1
,表示禁用指定范围的颜色输出。
ESP_LOG_TIMESTAMP_DISABLED: 要求启用 CONFIG_LOG_TIMESTAMP_SUPPORT。
如果已禁用全局时间戳(CONFIG_LOG_TIMESTAMP_SOURCE),则定义为
0
,以启用指定范围的时间戳输出。如果全局时间戳(CONFIG_LOG_TIMESTAMP_SOURCE)已启用,则定义为
1
,表示禁用指定范围的时间戳输出。
设置每条日志的输出格式
上述定义可以与提供的日志宏无缝配合使用。如果需要更高的灵活性,或需要在运行时调整设置,例如根据某个值(例如温度)调整日志级别,可以使用其他的宏来实现。需要注意的是,在这种情况下,日志不能从二进制文件中丢弃,因为它们绕过了编译时的日志级别检查。
下面的示例演示了如何调整单个日志消息的格式:
#include "esp_log.h"
esp_log_config_t configs = {
.opts = {
.log_level = ESP_LOG_INFO, // 设置 log level
.constrained_env = false, // 指定是否为受限环境
.require_formatting = true, // 启用格式处理
.dis_color = ESP_LOG_COLOR_DISABLED, // 使用全局颜色设置
.dis_timestamp = ESP_LOG_TIMESTAMP_DISABLED, // 使用全局时间戳设置
.reserved = 0, // 保留后续使用
}
};
// ...
if (temperature > 55) {
configs.opts.log_level = ESP_LOG_WARN;
}
//与 ESP_LOGx 宏相似,但可以采用自定义配置
// 如果 configs 变量为常量,编译器在编译过程中会排除低于 maximum log level 的日志
//如果 configs 不是常量则不适用
ESP_LOG_LEVEL_LOCAL(configs, TAG, "Temp = %dC", temperature);
// // 注意:以下调用绕过了编译时日志级别检查
// 这些日志无法从二进制文件中丢弃
esp_log(configs, TAG, "Temp = %dC", temperature);
ESP_LOG_LEVEL(configs, TAG, "Temp = %dC", temperature);
日志级别控制
只有应用程序支持在运行时更改日志级别。引导加载程序不支持此功能。
日志库允许在运行时使用函数 esp_log_level_set()
调整每个模块(标签)的日志输出。此功能仅适用于非受限环境(ESP_LOGx 宏)。受限环境(如 ESP_EARLY_LOGx 或 ESP_DRAM_LOGx)不支持动态日志级别,因为它们的日志处理程序中没有锁和轻量级要求。
// 将所有组件的日志级别设置为ERROR(全局设置)
esp_log_level_set("*", ESP_LOG_ERROR);
// 将 Wi-Fi 组件的日志级别设置为 WARNING(特定模块设置)
esp_log_level_set("wifi", ESP_LOG_WARN);
// 将 DHCP 客户端的日志级别设置为 INFO(模块相关设置)
esp_log_level_set("dhcpc", ESP_LOG_INFO);
下列三种设置可在运行时全局更改日志级别,或为单个模块(标签)更改日志级别:
Dynamic Log Level Control (CONFIG_LOG_DYNAMIC_LEVEL_CONTROL,默认已启用):动态日志级别控制。启用后,可以通过
esp_log_level_set()
函数在运行时更改日志级别。该功能提高了灵活性,但也增加了内存和性能开销。如需考虑二进制文件的大小,并且无需在运行时动态更改日志级别,建议禁用此选项,特别是在 CONFIG_LOG_TAG_LEVEL_IMPL 设置为 None 时,以尽量减小程序大小。如果你的应用程序不需要动态调整日志级别,禁用此选项可以提高效率:
降低内存消耗:
IRAM: 约 260 bytes
DRAM: 约 264 bytes
Flash: 约 1 KB
提高日志操作性能,最多提高 10 倍。
Tag-Level Checks (CONFIG_LOG_TAG_LEVEL_IMPL,默认值为 Cache + Linked List):标签级别检查,决定了如何检查每个标签的日志级别,影响内存使用和查找速度:
None:完全禁用按标签进行日志级别检查,能够减少开销,但失去了运行时的灵活性。
Linked List:仅使用链表实现按标签设置日志级别(不使用缓存)。这种方法会遍历链表中的所有标签来确定日志级别,因此当标签数量较大时,会导致查找速度变慢,但与 Cache 方式相比,能节省更多内存空间。链表方法对日志标签进行完整的字符串比较,从而识别日志级别。与 Cache 方法不同,链表方法不依赖于标签指针比较,因此更适用于动态的标签定义。如需优先考虑节省内存、对特定模块启用或禁用日志,或希望使用定义为变量的标签,请选择此方法。选择此方法会自动启用 Dynamic Log Level Control (动态日志级别控制)功能。运行
ESP_LOGx
宏遇到新标签时,链表中的项会分配到堆栈上。
Cache + Linked List (默认):缓存 + 链表,通过缓存与链表结合的方式进行日志标签级别检查,实现了内存占用和运行速度之间的平衡。缓存用于存储最近访问的日志标签及其对应的日志级别,加速了常用标签的查找。这是因为缓存方式会比较标签指针,与执行完整字符串相比速度更快。对不常用标签,通过链表进行日志级别查找。注意,使用动态标签定义时,此选项可能无法正常工作,因为它依赖缓存中的标签指针比较,不适用于动态定义的标签。此混合方法利用了常用标签的缓存速度优势和不常用标签的链表存储效率,提升了日志级别查找的总体效率。选择此选项会自动启用 Dynamic Log Level Control。
有一些缓存配置可以平衡内存使用和查找性能。这些配置决定了日志标签级别的存储和访问方式,详见 CONFIG_LOG_TAG_LEVEL_CACHE_IMPL。
Array:数组方式,实现简单,不进行重新排序,适合注重简洁性的低内存应用。
Binary Min-Heap (默认配置)最小二叉堆,优化的实现方式,支持快速查找并自动重新排序,适用于具有充足内存的高性能应用。其容量由 缓存大小 (CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE) 定义,默认包含 31 个条目。
缓存容量越大,查找常用日志标签的性能越高,但内存消耗也会增加。相反,缓存容量越小越节省内存,但可能导致不常用的日志标签被更频繁地移除。
Master Log Level (CONFIG_LOG_MASTER_LEVEL,默认禁用):这是一个可选设置,专为特定调试场景设计。此设置启用后,会在生成时间戳和标签缓存查找之前,启用全局 master 日志级别检查。这一选项适用于编译大量日志的情况,可以在运行时有选择地启用或禁用日志,同时在不需要日志输出时尽量减少对性能的影响。
例如,通常可以在在时间紧迫或 CPU 密集型操作期间临时禁用日志,并在之后重新启用日志。
备注
对于 Log V1,此功能可能会基于已编译日志的数量而显著增加程序大小。对于 Log V2 影响很小,因为检查已集成到了日志处理程序中。
如果启用此功能,master 日志级别默认为 CONFIG_LOG_DEFAULT_LEVEL,并可在运行时通过
esp_log_set_level_master()
进行调整。此全局检查优先于esp_log_get_default_level
。以下代码片段演示了此功能的原理。将 Master Log Level 设置为
ESP_LOG_NONE
,会在全局范围内禁用所有日志。此时,esp_log_level_set()
不会影响日志输出。但是,当 Master Log Level 调整为更高级别后,日志会按照esp_log_level_set()
的配置打印出来:// master 日志级别在启动时为 CONFIG_LOG_DEFAULT_LEVEL, 且等于 ESP_LOG_INFO ESP_LOGI("lib_name", "Message for print"); // 打印 INFO 消息 esp_log_level_set("lib_name", ESP_LOG_WARN); // 为 lib_name 启用 WARN 级别日志 // 全局禁用所有日志,esp_log_level_set 目前没有作用 esp_log_set_level_master(ESP_LOG_NONE); ESP_LOGW("lib_name", "Message for print"); // master 日志级别阻止了打印 esp_log_level_set("lib_name", ESP_LOG_INFO); // 开启 lib_name 的 INFO 日志 ESP_LOGI("lib_name", "Message for print"); // master 日志级别阻止了打印 // 全局启用所有 INFO 日志 esp_log_set_level_master(ESP_LOG_INFO); ESP_LOGI("lib_name", "Message for print"); // 打印 INFO 信息
备注
即使按标签禁用日志,处理时间仍需约 10.9 微秒。要减少这一开销,可考虑使用 Master Log Level 或禁用 Tag-Level Checks 功能。
缓冲区日志
日志系统提供用于记录缓冲区数据的宏。这些宏可在引导加载程序和应用程序中使用,且不限制日志版本。可用的宏有:
ESP_LOG_BUFFER_HEX
和ESP_LOG_BUFFER_HEX_LEVEL
:记录十六进制字节缓冲区。数据按每行 16 个字节分割。ESP_LOG_BUFFER_HEX
仅适用于Info
日志级别。#include "esp_log_buffer.h" uint8_t buffer[] = { 0x54, 0x68, 0x65, 0x20, 0x77, 0x61, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x67, 0x65, 0x74, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66 }; ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, sizeof(buffer), ESP_LOG_DEBUG);
I (954) MyModule: 54 68 65 20 77 61 79 20 74 6f 20 67 65 74 20 73 I (964) MyModule: 74 61 72 74 65 64 20 69 73 20 61 6e 64 20 66
ESP_LOG_BUFFER_CHAR
和ESP_LOG_BUFFER_CHAR_LEVEL
:记录可打印字符的缓冲区。每行最多包含 16 个字符。ESP_LOG_BUFFER_CHAR
仅适用于Info
日志级别。#include "esp_log_buffer.h" char buffer[] = "The quick brown fox jumps over the lazy dog."; ESP_LOG_BUFFER_CHAR_LEVEL(TAG, buffer, sizeof(buffer), ESP_LOG_WARN);
I (980) MyModule: The quick brown I (985) MyModule: fox jumps over I (990) MyModule: the lazy dog.
EP_LOG_BUFFER_HEXDUMP
:以格式化的十六进制转储方式输出缓冲区内容,同时显示内存地址和相应的 ASCII 值。适用于调试原始内存内容。#include "esp_log_buffer.h" uint8_t buffer[] = { 0x54, 0x68, 0x65, 0x20, 0x77, 0x61, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x67, 0x65, 0x74, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x20, 0x69 }; ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, sizeof(buffer), ESP_LOG_INFO);
I (1013) MyModule: 0x3ffb5bc0 54 68 65 20 77 61 79 20 74 6f 20 67 65 74 20 73 |The way to get s| I (1024) MyModule: 0x3ffb5bd0 74 61 72 74 65 64 20 69 73 20 74 6f 20 71 75 69 |tarted is to qui|
输出中包含的行数取决于缓冲区的大小。
性能测试
在任务中使用日志时,任务栈必须配置至少 2 KB 的空间,确保有足够的内存进行日志操作。
使用日志组件中的测试工具,基于默认设置(最大和默认日志级别设置为 INFO,禁用颜色支持,未启用 master 日志级别,启用时间戳),在不同芯片上进行了如下两组测试:
日志 API 性能测试
日志 API 堆栈用量测试
esp_rom_printf
和 esp_rom_vprintf
的结果相似,同样,vprintf
和 printf
也得出相似结果。因此,下表仅展示每对相似测试中的一个结果。
功能 |
ESP32 |
ESP32C2 |
ESP32C3 |
---|---|---|---|
esp_rom_printf |
128 |
192 |
192 |
ESP_EARLY_LOGI V1 |
128 |
192 |
192 |
ESP_EARLY_LOGI V2 |
336 |
324 |
324 |
ESP_DRAM_LOGI V1 |
128 |
192 |
192 |
ESP_DRAM_LOGI V2 |
336 |
324 |
324 |
vprintf |
1168 |
384 |
1344 |
ESP_LOGI V1 |
1184 |
384 |
1344 |
ESP_LOGI V2 |
1152 |
592 |
1504 |
Log V1 和 Log V2 之间的堆栈使用量差异可以忽略不计。
功能 |
ESP32 |
ESP32C2 |
ESP32C3 |
---|---|---|---|
esp_rom_printf |
1 |
2 |
1 |
ESP_EARLY_LOGI V1 |
15 |
24 |
14 |
ESP_EARLY_LOGI V2 |
28 |
36 |
25 |
ESP_DRAM_LOGI V1 |
6 |
9 |
5 |
ESP_DRAM_LOGI V2 |
19 |
22 |
14 |
vprintf |
15 |
9 |
7 |
ESP_LOGI V1 |
27 |
16 |
12 |
ESP_LOGI V2 |
77 |
54 |
40 |
关于通过 UART 输出日志的性能,Log V1 和 Log V2 的几乎完全相同。与通过 UART 发送日志所需的时间相比,Log V2 在处理开销方面带来的微小差异可以忽略不计。因此,在大多数实际用例中,切换到 Log V2 对性能的影响可以忽略。
内存占用(字节)
以下测试使用了 esp_timer
示例和 ESP32 的默认设置,最大和默认日志级别为 INFO,禁用颜色支持,启用时间戳。启用 Log V2 后重新构建了示例,然后使用以下命令比较内存占用的差异:
idf.py size --diff ~/esp/logv2/build_v1
日志系统版本 |
IRAM |
DRAM |
flash 代码 |
flash 数据 |
App 二进制大小 |
---|---|---|---|---|---|
Log V2 |
+1772 |
–36 |
–956 |
–1172 |
181104 (–384) |
日志系统版本 |
引导加载程序二进制大小 |
---|---|
Log V2 |
26272 (+160) |
启用 Log V2 会增加 IRAM 的使用量,同时减少整个应用程序的二进制文件大小、flash 代码和数据量。
通过 JTAG 将日志记录到主机
默认情况下,日志库使用类似 vprintf 的函数将格式化的输出写入专用 UART。通过调用一个简单的 API,所有日志输出都可以路由到 JTAG,从而使日志记录速度提高数倍。详情请参阅章节 记录日志到主机。
线程安全
在受限环境(或 ESP_EARLY_LOGx 和 ESP_DRAM_LOGx)记录日志时不使用锁机制,因此,如果其他任务并行记录日志,可能会导致日志损坏的罕见情况。为降低此类风险,建议尽可能使用通用宏。
通用宏 (ESP_LOGx) 通过在日志输出过程中获取锁来确保线程安全。在 Log V2 中,flockfile
在多个 vprintf
调用进行格式化处理时提供了额外保护。
日志首先写入内存 buffer,然后发送到 UART 打印,从而确保不同任务之间的线程安全。除非需要确保可靠的日志输出,否则应避免在受限环境中记录日志。
应用示例
大多数 ESP-IDF 组件和示例都会使用日志库。如需查看有关日志功能的应用示例,请前往 ESP-IDF 的 examples 目录。与日志最相关的示例如下:
API 参考
Header File
This header file can be included with:
#include "esp_log.h"
Functions
-
void esp_log(esp_log_config_t config, const char *tag, const char *format, ...)
Logs a formatted message using the provided log message configs and a variable argument list.
- 参数
config -- Configuration and level of the log message.
tag -- The tag string used to indicate the component from which to log. It is also used to check whether logging is enabled for that tag (depends on CONFIG_LOG_TAG_LEVEL_IMPL). If NULL then the tag is not printed.
format -- The format string for the log message.
... -- Optional arguments to be formatted according to the format string.
-
void esp_log_va(esp_log_config_t config, const char *tag, const char *format, va_list args)
Logs a formatted message using the provided log message configs and a variable argument list.
- 参数
config -- Configuration and level of the log message.
tag -- The tag string used to indicate the component from which to log. It is also used to check whether logging is enabled for that tag (depends on CONFIG_LOG_TAG_LEVEL_IMPL). If NULL then the tag is not printed.
format -- The format string for the log message.
args -- List of arguments.
Macros
-
ESP_EARLY_LOGE(tag, format, ...)
Early log macros to output logs in startup code, before heap allocator and syscalls have been initialized. The log level can be changed per-tag using
esp_log_level_set(TAG, level)
.macro to output logs in startup code, before heap allocator and syscalls have been initialized. Log at
ESP_LOG_ERROR
level.参见
printf
,ESP_LOGE
,ESP_DRAM_LOGE
In the future, we want to become compatible with clang. Hence, we provide two versions of the following macros which are using variadic arguments. The first one is using the GNU extension##__VA_ARGS__
. The second one is using the C++20 feature__VA_OPT__(,)
. This allows users to compile their code with standard C++20 enabled instead of the GNU extension. Below C++20, we haven't found any good alternative to using##__VA_ARGS__
. macro to output logs in startup code atESP_LOG_ERROR
level.
-
ESP_EARLY_LOGW(tag, format, ...)
macro to output logs in startup code at
ESP_LOG_WARN
level.
-
ESP_EARLY_LOGI(tag, format, ...)
macro to output logs in startup code at
ESP_LOG_INFO
level.
-
ESP_EARLY_LOGD(tag, format, ...)
macro to output logs in startup code at
ESP_LOG_DEBUG
level.
-
ESP_EARLY_LOGV(tag, format, ...)
macro to output logs in startup code at
ESP_LOG_VERBOSE
level.
-
ESP_LOGE(tag, format, ...)
Normal logging macros to output logs. The log level can be changed per-tag using
esp_log_level_set(TAG, level)
.macro to output logs at
ESP_LOG_ERROR
level.
-
ESP_LOGW(tag, format, ...)
macro to output logs at
ESP_LOG_WARN
level.
-
ESP_LOGI(tag, format, ...)
macro to output logs at
ESP_LOG_INFO
level.
-
ESP_LOGD(tag, format, ...)
macro to output logs at
ESP_LOG_DEBUG
level.
-
ESP_LOGV(tag, format, ...)
macro to output logs at
ESP_LOG_VERBOSE
level.
-
ESP_DRAM_LOGE(tag, format, ...)
Macros to output logs when the cache is disabled. Unlike normal logging macros, it's possible to use this macro when interrupts are disabled or inside an ISR. Placing log strings in DRAM reduces available DRAM, so only use when absolutely essential.
Usage:
ESP_DRAM_LOGE(DRAM_STR("my_tag"), "format", ...), or
ESP_DRAM_LOGE(TAG, "format", ...)`, where TAG is a char* that points to a str in the DRAM. macro to output logs when the cache is disabled atESP_LOG_ERROR
level.
-
ESP_DRAM_LOGW(tag, format, ...)
macro to output logs when the cache is disabled at
ESP_LOG_WARN
level.
-
ESP_DRAM_LOGI(tag, format, ...)
macro to output logs when the cache is disabled at
ESP_LOG_INFO
level.
-
ESP_DRAM_LOGD(tag, format, ...)
macro to output logs when the cache is disabled at
ESP_LOG_DEBUG
level.
-
ESP_DRAM_LOGV(tag, format, ...)
macro to output logs when the cache is disabled at
ESP_LOG_VERBOSE
level.
-
ESP_LOG_LEVEL_LOCAL(configs, tag, format, ...)
runtime macro to output logs at a specified configs. Also check the level with
LOG_LOCAL_LEVEL
.
-
ESP_LOG_LEVEL(configs, tag, format, ...)
runtime macro to output logs at a specified level and with ESP_LOG_CONFIGS_DEFAULT.
参见
printf
- 参数
configs -- it includes level and other log configurations.
tag -- tag of the log, which can be used to change the log level by
esp_log_level_set
at runtime.format -- format of the output log. See
printf
... -- variables to be replaced into the log. See
printf
Header File
This header file can be included with:
#include "esp_log_level.h"
Functions
-
static inline esp_log_level_t esp_log_get_default_level(void)
Get the default log level.
This function returns the default log level. The default log level is used by the definition of ESP_LOGx macros and can be overridden for specific tags using
esp_log_level_set("*", level)
. If CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=n, changing the default log level is not possible.- 返回
The default log level.
-
void esp_log_set_level_master(esp_log_level_t level)
Master log level.
Allows one to set a higher CONFIG_LOG_MAXIMUM_LEVEL but not impose a performance hit during normal operation (only when instructed). An application may set esp_log_set_level_master(level) to globally enforce a maximum log level. ESP_LOG macros above this level will be skipped, rather than doing a tag lookup.
The Master log level is not applicable for the bootloader.
- 参数
level -- Master log level
-
esp_log_level_t esp_log_get_level_master(void)
Returns master log level. The Master log level is not applicable for the bootloader.
- 返回
Master log level
-
void esp_log_level_set(const char *tag, esp_log_level_t level)
Set log level for given tag.
If logging for given component has already been enabled, changes previous setting.
To raise log level above the default one for a given file, define LOG_LOCAL_LEVEL to one of the ESP_LOG_* values, before including esp_log.h in this file.
If CONFIG_LOG_DYNAMIC_LEVEL_CONTROL is not selected the static (no-op) implementation of log level is used. Changing the log level is not possible, esp_log_level_set does not work.
备注
Note that this function can not raise log level above the level set using CONFIG_LOG_MAXIMUM_LEVEL setting in menuconfig.
- 参数
tag -- Tag of the log entries to enable. Must be a non-NULL zero terminated string. Value "*" resets log level for all tags to the given value. If the tag is NULL then a silent return happens.
level -- Selects log level to enable. Only logs at this and lower verbosity levels will be shown.
-
esp_log_level_t esp_log_level_get(const char *tag)
Get log level for a given tag, can be used to avoid expensive log statements.
If CONFIG_LOG_DYNAMIC_LEVEL_CONTROL is not selected the static (no-op) implementation of log level is used. Changing the log level is not possible, esp_log_level_set does not work. This function returns the default log level.
- 参数
tag -- Tag of the log to query current level. Must be a zero terminated string. If tag is NULL then the default log level is returned (see esp_log_get_default_level()).
- 返回
The current log level for the given tag.
Macros
-
ESP_LOG_LEVEL_LEN
Number of bits used to represent the log level
-
ESP_LOG_LEVEL_MASK
Mask for log level
-
ESP_LOG_GET_LEVEL(config)
Returns level from config.
-
ESP_LOG_ENABLED(configs)
Check if a specific log level is enabled at compile-time.
This macro checks whether logging for the specified log level is enabled based on the current local log level setting (
LOG_LOCAL_LEVEL
). It uses a compile-time check to determine if logging for the specified level should be included in the binary, helping to exclude logs that are not configured.- 参数
configs -- it includes log configs and level.
- 返回
true if the specified log level is enabled, false otherwise.
Enumerations
-
enum esp_log_level_t
Log level.
Values:
-
enumerator ESP_LOG_NONE
No log output
-
enumerator ESP_LOG_ERROR
Critical errors, software module can not recover on its own
-
enumerator ESP_LOG_WARN
Error conditions from which recovery measures have been taken
-
enumerator ESP_LOG_INFO
Information messages which describe normal flow of events
-
enumerator ESP_LOG_DEBUG
Extra information which is not necessary for normal use (values, pointers, sizes, etc).
-
enumerator ESP_LOG_VERBOSE
Bigger chunks of debugging information, or frequent messages which can potentially flood the output.
-
enumerator ESP_LOG_MAX
Number of levels supported
-
enumerator ESP_LOG_NONE
Header File
This header file can be included with:
#include "esp_log_buffer.h"
Functions
-
void esp_log_buffer_hex_internal(const char *tag, const void *buffer, uint16_t buff_len, esp_log_level_t level)
Logs a buffer of hexadecimal bytes at the specified log level.
This function logs a buffer of hexadecimal bytes with 16 bytes per line. The log level determines the severity of the log message.
备注
This function does not check the log level against the ESP_LOCAL_LEVEL. The log level comparison should be done in esp_log.h.
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
level -- Log level indicating the severity of the log message.
-
void esp_log_buffer_char_internal(const char *tag, const void *buffer, uint16_t buff_len, esp_log_level_t level)
This function logs a buffer of characters with 16 characters per line. The buffer should contain only printable characters. The log level determines the severity of the log message.
备注
This function does not check the log level against the ESP_LOCAL_LEVEL. The log level comparison should be done in esp_log.h.
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
level -- Log level indicating the severity of the log message.
-
void esp_log_buffer_hexdump_internal(const char *tag, const void *buffer, uint16_t buff_len, esp_log_level_t log_level)
This function dumps a buffer to the log in a formatted hex dump style, displaying both the memory address and the corresponding hex and ASCII values of the bytes. The log level determines the severity of the log message.
备注
This function does not check the log level against the ESP_LOCAL_LEVEL. The log level comparison should be done in esp_log.h.
备注
It is recommended to use terminals with a width of at least 102 characters to display the log dump properly.
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
log_level -- Log level indicating the severity of the log message.
Macros
-
ESP_LOG_BUFFER_HEX_LEVEL(tag, buffer, buff_len, level)
Log a buffer of hex bytes at specified level, separated into 16 bytes each line.
The hex log shows just like the one below:
I (954) log_example: 54 68 65 20 77 61 79 20 74 6f 20 67 65 74 20 73 I (962) log_example: 74 61 72 74 65 64 20 69 73 20 74 6f 20 71 75 69 I (969) log_example: 74 20 74 61 6c 6b 69 6e 67 20 61 6e 64 20 62 65 I (977) log_example: 67 69 6e 20 64 6f 69 6e 67 2e 20 2d 20 57 61 6c I (984) log_example: 74 20 44 69 73 6e 65 79 00
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
level -- Log level
-
ESP_LOG_BUFFER_CHAR_LEVEL(tag, buffer, buff_len, level)
Log a buffer of characters at specified level, separated into 16 bytes each line. Buffer should contain only printable characters.
The char log shows just like the one below:
I (980) log_example: The way to get s I (985) log_example: tarted is to qui I (989) log_example: t talking and be I (994) log_example: gin doing. - Wal I (999) log_example: t Disney
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
level -- Log level.
-
ESP_LOG_BUFFER_HEXDUMP(tag, buffer, buff_len, level)
Dump a buffer to the log at specified level.
The dump log shows just like the one below:
I (1013) log_example: 0x3ffb5bc0 54 68 65 20 77 61 79 20 74 6f 20 67 65 74 20 73 |The way to get s| I (1024) log_example: 0x3ffb5bd0 74 61 72 74 65 64 20 69 73 20 74 6f 20 71 75 69 |tarted is to qui| I (1034) log_example: 0x3ffb5be0 74 20 74 61 6c 6b 69 6e 67 20 61 6e 64 20 62 65 |t talking and be| I (1044) log_example: 0x3ffb5bf0 67 69 6e 20 64 6f 69 6e 67 2e 20 2d 20 57 61 6c |gin doing. - Wal| I (1054) log_example: 0x3ffb5c00 74 20 44 69 73 6e 65 79 00 |t Disney.|
备注
It is highly recommended to use terminals with over 102 text width.
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
level -- Log level.
-
ESP_LOG_BUFFER_HEX(tag, buffer, buff_len)
Log a buffer of hex bytes at Info level.
参见
ESP_LOG_BUFFER_HEX_LEVEL
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
-
ESP_LOG_BUFFER_CHAR(tag, buffer, buff_len)
Log a buffer of characters at Info level. Buffer should contain only printable characters.
参见
ESP_LOG_BUFFER_CHAR_LEVEL
- 参数
tag -- Description tag to identify the log.
buffer -- Pointer to the buffer array containing the data to be logged.
buff_len -- Length of the buffer in bytes.
Header File
This header file can be included with:
#include "esp_log_timestamp.h"
Functions
-
uint32_t esp_log_timestamp(void)
Function which returns timestamp to be used in log output.
This function is used in expansion of ESP_LOGx macros. In the 2nd stage bootloader, and at early application startup stage this function uses CPU cycle counter as time source. Later when FreeRTOS scheduler start running, it switches to FreeRTOS tick count.
For now, we ignore millisecond counter overflow.
- 返回
timestamp, in milliseconds
-
char *esp_log_system_timestamp(void)
Function which returns system timestamp to be used in log output.
This function is used in expansion of ESP_LOGx macros to print the system time as "HH:MM:SS.sss". The system time is initialized to 0 on startup, this can be set to the correct time with an SNTP sync, or manually with standard POSIX time functions.
Currently, this will not get used in logging from binary blobs (i.e. Wi-Fi & Bluetooth libraries), these will still print the RTOS tick time.
- 返回
timestamp, in "HH:MM:SS.sss"
-
uint32_t esp_log_early_timestamp(void)
Function which returns timestamp to be used in log output.
This function uses HW cycle counter and does not depend on OS, so it can be safely used after application crash.
- 返回
timestamp, in milliseconds
Header File
This header file can be included with:
#include "esp_log_color.h"
Header File
This header file can be included with:
#include "esp_log_write.h"
Functions
-
vprintf_like_t esp_log_set_vprintf(vprintf_like_t func)
Set function used to output log entries.
By default, log output goes to UART0. This function can be used to redirect log output to some other destination, such as file or network. Returns the original log handler, which may be necessary to return output to the previous destination.
备注
Please note that function callback here must be re-entrant as it can be invoked in parallel from multiple tasks context.
- 参数
func -- new Function used for output. Must have same signature as vprintf.
- 返回
func old Function used for output.
-
void esp_log_write(esp_log_level_t level, const char *tag, const char *format, ...)
Write message into the log.
This function is not intended to be used directly. Instead, use one of ESP_LOGE, ESP_LOGW, ESP_LOGI, ESP_LOGD, ESP_LOGV macros.
This function or these macros should not be used from an interrupt.
This function does not add any formatting elements such as color, timestamp, or tag. It checks the level and tag level. If logging is allowed then it outputs it as is.
- 参数
level -- Log level of the message.
tag -- It is used to check whether logging is enabled for that tag (depends on CONFIG_LOG_TAG_LEVEL_IMPL).
format -- The format string for the log message. It has to be fully formatted, no additional formatting items will be added.
... -- Optional arguments to be formatted according to the format string.
-
void esp_log_writev(esp_log_level_t level, const char *tag, const char *format, va_list args)
Write message into the log, va_list variant.
This function is provided to ease integration toward other logging framework, so that esp_log can be used as a log sink.
参见
esp_log_write()
This function does not add any formatting elements such as color, timestamp, or tag. It checks the level and tag level. If logging is allowed then it outputs it as is.
- 参数
level -- Log level of the message.
tag -- It is used to check whether logging is enabled for that tag (depends on CONFIG_LOG_TAG_LEVEL_IMPL).
format -- The format string for the log message. It has to be fully formatted, no additional formatting items will be added.
args -- List of arguments.
Type Definitions
-
typedef int (*vprintf_like_t)(const char*, va_list)