1、日志系统概述:为什么手表需要日志系统、日志系统的基本概念、日志级别定义(DEBUG/INFO/WARN/ERROR)

1.1 为什么智能手表需要日志系统?

先问大家一个问题:你写的手表程序跑着跑着突然死机了,你怎么办?

我刚开始做嵌入式那会儿,遇到这种问题只能干瞪眼。拿个LED灯在那闪,靠肉眼猜程序跑到哪了。说实话,那效率低得让人抓狂。后来我学乖了——每个项目第一件事,先把日志系统搭好。

智能手表跟普通嵌入式设备不一样。它要处理的东西太多了:心率传感器、加速度计、蓝牙通信、触摸屏响应、GUI渲染……这些模块一旦出问题,你根本不知道是哪一环崩了。

我个人习惯是:没有日志系统的代码,绝不发布。这不是小题大做。你想想看,手表戴在用户手上,出了问题你连现场都看不到。没有日志,你就像蒙着眼睛修车。

日志系统的核心价值:

  • 故障定位——程序崩溃了,能知道最后执行到哪一行
  • 性能分析——哪个函数跑得慢,哪个中断占用了太多时间
  • 行为追踪——用户操作了什么,系统响应了什么
  • 调试辅助——开发阶段快速验证逻辑是否正确

我在项目中遇到过最典型的一个案例:手表在低电量模式下蓝牙会断连。查了两天没头绪,最后靠日志发现是电源管理模块在进入低功耗前,把蓝牙的时钟给关了。嗯,这种问题没有日志,你查一个月也未必能找到。

1.2 日志系统的基本概念

说白了,日志系统就是一个记录程序运行时状态的机制。它把关键信息按一定格式输出到某个地方——可能是串口、SD卡文件、或者通过蓝牙传到手机。

一个完整的日志系统,通常包含这几个要素:

  • 日志源——谁在打日志?是哪个模块?比如蓝牙模块、传感器模块
  • 日志级别——这条日志有多重要?是调试信息还是错误报告?
  • 时间戳——这条日志是什么时候产生的?精确到毫秒甚至微秒
  • 日志内容——具体描述了什么事件或数据
  • 输出目标——日志写到哪里去?串口、Flash、还是RAM缓冲区

你可能会问:手表资源那么紧张,搞个日志系统会不会太奢侈?

其实不会。一个好的日志系统,在发布版本里可以完全关闭,不占任何CPU时间。只有在调试版本里才开启。我一般会在代码里用宏来控制:

// 调试版本开启日志
#ifdef DEBUG_ENABLE
    #define LOG_DEBUG(fmt, ...)  log_output(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
#else
    #define LOG_DEBUG(fmt, ...)  // 空宏,不产生任何代码
#endif

这样,发布版本里日志代码就像不存在一样。零开销。

我的小技巧:在智能手表这种资源受限的设备上,我建议把日志输出到环形缓冲区(Ring Buffer)。这样即使串口没接,日志也能保存在内存里。等出问题了,再通过某种方式把缓冲区内容导出来。

1.3 日志级别定义:DEBUG / INFO / WARN / ERROR

日志级别这东西,说白了就是给日志分个三六九等。不同的级别代表不同的严重程度。我见过有些新手把所有日志都打成一个级别,结果满屏都是信息,根本分不清哪个重要。

我们嵌入式领域最常用的四个级别,我一个个说:

级别 含义 使用场景 发布版本是否保留
DEBUG 调试信息 开发阶段,查看变量值、函数入口出口、状态变化
INFO 普通信息 系统启动、模块初始化、关键状态切换 可选
WARN 警告 非致命但需要注意的情况,如电池电压偏低、内存不足
ERROR 错误 功能异常、通信失败、传感器无响应

DEBUG 级别

这个级别只在开发阶段用。我习惯用它来打印函数的入参和返回值,或者某个循环的执行次数。举个例子:

LOG_DEBUG("心率传感器读取完成,值: %d bpm", heart_rate);

发布版本里,这些日志全部被宏吃掉,不占任何空间。

INFO 级别

INFO 用来记录系统正常运行时的关键节点。比如手表开机了、蓝牙连接成功了、固件版本号是多少。我个人习惯在发布版本里保留 INFO 级别的日志,但只输出到内部缓冲区,不通过串口实时打印——省电。

LOG_INFO("系统启动完成,固件版本: v2.1.3,电池电量: %d%%", battery_level);

WARN 级别

警告意味着「还能跑,但有点不对劲」。我曾经遇到过手表在低温环境下屏幕刷新率自动降低,这就是一个典型的 WARN 场景——不是错误,但值得关注。

LOG_WARN("电池温度过低 (%d°C),已降低屏幕刷新率", temp);

ERROR 级别

错误就是出事了。传感器读不到数据、文件系统写入失败、蓝牙配对超时……这些必须记录下来。我一般会在 ERROR 日志后面加一个系统状态快照,方便事后分析。

LOG_ERROR("加速度计通信失败,错误码: 0x%02X", err_code);

避坑指南:我曾经犯过一个错误——把所有日志都设成 ERROR 级别。结果真出问题的时候,满屏都是 ERROR,根本分不清哪个是致命的,哪个只是小故障。记住:级别不是随便选的,它代表了你的判断力

1.4 日志级别的过滤机制

实际项目中,我们不会把所有级别的日志都输出。那样信息量太大了,反而找不到重点。一般会设置一个日志阈值

// 设置日志级别阈值,只输出 WARN 及以上级别的日志
#define LOG_LEVEL_THRESHOLD  LOG_LEVEL_WARN

// 日志输出函数内部判断
void log_output(int level, const char* fmt, ...) {
    if (level < LOG_LEVEL_THRESHOLD) {
        return;  // 低于阈值的日志直接丢弃
    }
    // 实际输出逻辑...
}

开发阶段,阈值设成 DEBUG,所有日志都看得到。发布版本,阈值设成 WARN 或 ERROR,只保留关键信息。这样既不影响性能,又能保证出问题时能抓到线索。

嗯,这里要注意:阈值设置要灵活。我一般会在手表里留一个隐藏菜单,可以动态调整日志级别。这样用户反馈问题时,我可以远程让他把级别调低,收集更多信息。

1.5 小结

日志系统不是什么高大上的东西,它就是你的第二双眼睛。在智能手表这种资源受限、实时性要求高的设备上,一个设计良好的日志系统能帮你省下大量调试时间。

记住几个要点:

  • 日志要分级,别一股脑全打出来
  • 发布版本要能完全关闭 DEBUG 日志
  • WARN 和 ERROR 级别一定要保留,这是你的救命稻草
  • 日志输出目标要灵活,串口、内存、文件都可以

下一章,我会带大家实际搭建一个适合智能手表的轻量级日志系统。到时候咱们手写代码,把今天讲的概念落地。