返回首页

Zephyr 日志系统学习笔记

概述

Zephyr 提供了一个强大且灵活的日志系统,支持多种后端、过滤级别和格式化选项。日志系统对于调试、性能分析和生产环境监控都至关重要。

核心概念

1. 日志模块架构

应用程序
    ↓
日志 API (LOG_* 宏)
    ↓
日志核心 (处理、过滤)
    ↓
后端 (UART、RTT、文件等)

2. 日志级别

级别数值用途
none-0禁用日志
errorLOG_ERR1错误信息
warningLOG_WRN2警告信息
infoLOG_INF3一般信息
debugLOG_DBG4调试信息

3. 日志模块注册

每个源文件需要注册自己的日志模块:

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(my_module, LOG_LEVEL_INF);

void my_function(void) {
    LOG_INF("This is an info message");
    LOG_WRN("Warning: value = %d", value);
    LOG_ERR("Error occurred!");
}

Kconfig 配置

基本配置

# 启用日志系统
CONFIG_LOG=y

# 默认日志级别
CONFIG_LOG_DEFAULT_LEVEL=3  # 0=none, 1=error, 2=warning, 3=info, 4=debug

# 启用运行时过滤
CONFIG_LOG_RUNTIME_FILTERING=y

# 日志缓冲区大小
CONFIG_LOG_BUFFER_SIZE=2048

后端配置

# UART 后端
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BACKEND_UART_SERIAL_NAME="UART_0"

# RTT 后端 (Segger RTT)
CONFIG_LOG_BACKEND_RTT=y
CONFIG_USE_SEGGER_RTT=y

# Native POSIX 后端
CONFIG_LOG_BACKEND_NATIVE_POSIX=y

格式化选项

# 包含时间戳
CONFIG_LOG_TIMESTAMP_64BIT=y

# 自定义时间戳函数
CONFIG_LOG_CUSTOM_TIMESTAMP=y

# 颜色输出
CONFIG_LOG_BACKEND_SHOW_COLOR=y

# 线程信息
CONFIG_LOG_THREAD_ID_PREFIX=y

日志后端

1. UART 后端

最常用的调试后端,通过串口输出日志:

// prj.conf
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_DEFAULT_LEVEL=3

// 代码中
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

int main(void) {
    LOG_INF("System started");
    return 0;
}

2. RTT 后端

Segger Real-Time Transfer,高速调试输出:

CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_RTT_TX_BUFFER_SIZE=1024

需要 J-Link 调试器支持。

3. 自定义后端

可以创建自定义后端输出到其他目标:

static int my_backend_init(const struct log_backend *backend)
{
    // 初始化代码
    return 0;
}

static void my_backend_process(const struct log_backend *backend,
                               union log_msg_generic *msg)
{
    // 处理日志消息
    log_output_msg_process(msg, &log_output);
}

LOG_BACKEND_DEFINE(my_backend, my_backend_api, true);

日志过滤

编译时过滤

// 设置模块日志级别
LOG_MODULE_REGISTER(my_module, LOG_LEVEL_INF);

运行时过滤

启用 CONFIG_LOG_RUNTIME_FILTERING=y 后:

// 设置模块日志级别
log_filter_set(NULL, 0, log_source_id_get(my_module), LOG_LEVEL_DBG);

// 获取当前级别
int level = log_filter_get(NULL, 0, log_source_id_get(my_module), true);

Shell 命令过滤

CONFIG_SHELL=y
CONFIG_SHELL_LOG_BACKEND=y
# 查看/设置日志级别
uart:~$ log level
uart:~$ log level dbg my_module

高级特性

1. 日志消息格式

[<timestamp>] <level>: <module>: <message>

示例:

[00:00:01.234] <inf> main: System started
[00:00:01.567] <wrn> sensor: Temperature high: 85°C

2. 十六进制转储

LOG_HEXDUMP_INF(data, sizeof(data), "Received data:");

输出:

[00:00:01.000] <inf> main: Received data:
00000000: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10

3. 延迟日志

用于低功耗场景,日志先存入缓冲区,稍后处理:

CONFIG_LOG_MODE_DELAYED=y
CONFIG_LOG_BUFFER_SIZE=4096

4. 压缩日志

减少日志存储空间:

CONFIG_LOG_COMPRESS=y

性能优化

1. 禁用不需要的日志

// 生产环境禁用调试日志
LOG_MODULE_REGISTER(my_module, LOG_LEVEL_ERR);

2. 使用条件日志

if (IS_ENABLED(CONFIG_DEBUG_FEATURE)) {
    LOG_DBG("Debug info: %d", value);
}

3. 避免频繁日志

// 使用速率限制
static int32_t log_counter;

void process_data(void) {
    if (++log_counter % 100 == 0) {
        LOG_INF("Processed %d packets", log_counter);
    }
}

nRF Connect SDK 特定

1. Nordic 日志模块

NCS 提供额外的日志功能:

CONFIG_NRF_MODEM_LIB_LOG=y
CONFIG_NRF_MODEM_LIB_LOG_LEVEL_INF=y

2. LTE 模块日志

CONFIG_LTE_LINK_CONTROL_LOG_LEVEL_INF=y
CONFIG_AT_CMD_LIB_LOG_LEVEL_INF=y

3. 蓝牙日志

CONFIG_BT_LOG=y
CONFIG_BT_LOG_LEVEL_INF=y
CONFIG_BT_HCI_CORE_LOG_LEVEL_INF=y

最佳实践

1. 日志级别使用原则

2. 模块命名规范

// 使用清晰、描述性的模块名
LOG_MODULE_REGISTER(ble_manager, LOG_LEVEL_INF);
LOG_MODULE_REGISTER(sensor_driver, LOG_LEVEL_DBG);
LOG_MODULE_REGISTER(power_management, LOG_LEVEL_INF);

3. 结构化日志消息

// 好的做法
LOG_INF("Connection established: addr=%s, interval=%d ms", 
        addr_str, conn_interval);

// 避免
LOG_INF("Connected");  // 信息不足

4. 错误处理示例

int read_sensor_data(struct sensor_data *data)
{
    int ret = sensor_sample_fetch(dev);
    if (ret < 0) {
        LOG_ERR("Failed to fetch sensor data: %d", ret);
        return ret;
    }
    
    ret = sensor_channel_get(dev, SENSOR_CHAN_TEMP, &data->temp);
    if (ret < 0) {
        LOG_WRN("Temperature channel unavailable: %d", ret);
        data->temp = 0;
    }
    
    LOG_DBG("Sensor data: temp=%d.%d°C", 
            data->temp.val1, data->temp.val2);
    return 0;
}

与 printk 的区别

特性LOG 宏printk
过滤级别✅ 支持❌ 不支持
模块标识✅ 自动❌ 手动
时间戳✅ 自动❌ 手动
后端切换✅ 灵活❌ 固定
性能开销可配置固定

推荐: 在正式项目中使用 LOG 宏,开发调试时可以用 printk。

常见问题

1. 日志不显示

检查:

2. 日志丢失

解决:

3. 时间戳不准确

CONFIG_LOG_TIMESTAMP_64BIT=y

确保系统时钟正确配置。


总结

Zephyr 日志系统提供了强大且灵活的日志功能,关键要点:

  1. 使用 LOG_MODULE_REGISTER 注册模块
  2. 合理设置日志级别
  3. 选择合适的后端(UART/RTT)
  4. 生产环境注意性能优化
  5. 遵循日志命名和格式规范

*学习日期: 2026-03-20* *小白 🤖*