日志系统:日志等级定义、输出格式规范与存储轮转策略
日志系统,说白了就是设备的“黑匣子”。
我做了这么多年嵌入式,最怕的就是设备在现场出了问题,却连个像样的日志都拿不出来。你想想看,一个设备跑着跑着突然死机了,或者功能异常了,如果没有日志,你只能靠猜。猜来猜去,最后可能连问题出在哪都不知道。
所以,日志系统不是可有可无的装饰品,它是调试和排错的第一道防线。今天我们就来聊聊日志系统的三个核心要素:等级定义、输出格式、存储与轮转。
一、日志等级定义:DEBUG / INFO / WARN / ERROR
日志等级,说白了就是给日志贴个标签,告诉别人这条日志有多重要。
我个人习惯把日志分成四个等级,这也是业界最通用的做法:
| 等级 | 含义 | 使用场景 |
|---|---|---|
| DEBUG | 调试信息 | 开发阶段,用于跟踪变量值、函数调用流程等 |
| INFO | 普通信息 | 记录设备正常运行状态,如启动完成、连接成功 |
| WARN | 警告信息 | 出现非致命异常,设备仍可继续运行,但需关注 |
| ERROR | 错误信息 | 出现致命错误,设备功能受损或即将停止工作 |
这里有个坑,我必须要提醒你。
所以,我建议在代码里做一个编译开关,比如这样:
#define LOG_LEVEL LOG_LEVEL_INFO
#if LOG_LEVEL <= LOG_LEVEL_DEBUG
#define LOG_DEBUG(fmt, ...) printf("[DEBUG] " fmt, ##__VA_ARGS__)
#else
#define LOG_DEBUG(fmt, ...) // 空定义,编译时直接去掉
#endif
#if LOG_LEVEL <= LOG_LEVEL_INFO
#define LOG_INFO(fmt, ...) printf("[INFO] " fmt, ##__VA_ARGS__)
#else
#define LOG_INFO(fmt, ...)
#endif
这样,发布版本只需要把 LOG_LEVEL 改成 LOG_LEVEL_INFO 或 LOG_LEVEL_WARN,DEBUG日志就自动消失了。干净利落。
二、日志输出格式规范
日志格式,决定了你拿到日志后能不能快速看懂。
我见过最糟糕的日志,就是光秃秃一句话:“连接失败”。谁连接的?什么时候失败的?失败原因是什么?全都没有。这种日志,说白了就是废纸。
一个规范的日志格式,至少应该包含以下信息:
- 时间戳:精确到毫秒,最好带时区
- 日志等级:DEBUG/INFO/WARN/ERROR
- 模块名:比如 WIFI、BLE、SENSOR
- 文件名+行号:方便定位代码位置
- 日志内容:描述发生了什么
我个人习惯的格式是这样的:
[2025-03-21 14:30:45.123] [INFO] [WIFI] [wifi_manager.c:256] WiFi连接成功,SSID: MyHome, IP: 192.168.1.100
你看,一眼就能看出:什么时候、什么模块、什么等级、在哪行代码、发生了什么。这才是好日志。
代码实现上,可以用一个宏来统一格式化:
#define LOG(level, module, fmt, ...) \
do { \
char timestamp[32]; \
get_timestamp(timestamp, sizeof(timestamp)); \
printf("[%s] [%s] [%s] [%s:%d] " fmt "\n", \
timestamp, level, module, __FILE__, __LINE__, ##__VA_ARGS__); \
} while(0)
三、日志存储与轮转策略
日志写好了,存哪?存多久?满了怎么办?
这些问题不解决,日志系统就是空中楼阁。
我建议分两种情况考虑:
1. 嵌入式设备(资源受限)
这类设备Flash空间有限,可能只有几MB。我的做法是:
- 环形缓冲区:在RAM里开一个固定大小的缓冲区,新日志覆盖旧日志。掉电就丢,但速度快,不伤Flash。
- Flash存储:只存ERROR和WARN级别的日志。INFO和DEBUG不存,因为太占空间。
- 轮转策略:比如只保留最近3个日志文件,每个文件256KB。满了就删最旧的。
代码示例:
#define LOG_FILE_MAX_SIZE (256 * 1024) // 256KB
#define LOG_FILE_MAX_COUNT 3
void log_write_to_flash(const char *log_entry) {
// 检查当前文件大小
if (get_file_size(LOG_CURRENT_FILE) > LOG_FILE_MAX_SIZE) {
// 轮转:删除最旧文件,创建新文件
rotate_log_files();
}
// 追加写入
append_to_file(LOG_CURRENT_FILE, log_entry);
}
2. 上位机或服务器(资源充足)
这类场景下,空间不是问题,但管理是问题。我建议:
- 按天分文件:比如 log_2025-03-21.txt
- 按大小轮转:单个文件超过100MB就自动分割
- 定期清理:保留最近30天的日志,更早的自动删除或压缩归档
嗯,这里要注意一点:日志轮转时,一定要保证写入操作的原子性。我曾经遇到过,轮转过程中设备突然断电,结果新旧文件都损坏了,日志全丢。后来我改用“先写临时文件,再重命名”的方式,才彻底解决这个问题。
知识体系总览
下面这张图,是我对日志系统核心逻辑的总结。你可以把它当作一个检查清单,看看自己的日志系统有没有遗漏。
好了,关于日志系统的三个核心要素,我就讲到这里。记住,日志不是写给机器看的,是写给人看的。你写的每一条日志,都是在帮未来的自己(或者你的同事)省时间。所以,认真对待它。
公众号:蓝海资料掘金营,微信deep3321