第三章 日志系统搭建:syslog与自定义日志等级、日志轮转策略、远程日志收集方案
做嵌入式WebRTC开发,最怕什么?设备在现场跑着,突然通话卡顿、断连、甚至死机。你手里没有屏幕,没有键盘,唯一能抓住的就是日志。日志系统搭得好不好,直接决定了你排查问题的效率。这一章,我就把我在多个嵌入式WebRTC项目里积累的日志搭建经验,掰开了讲给你听。
3.1 为什么syslog是嵌入式首选?
很多刚入行的朋友喜欢自己写日志函数,往文件里fprintf。说实话,我早期也这么干过。但后来发现,自己写的日志系统在嵌入式环境里问题太多:没有等级控制、没有轮转、多线程写日志还会乱掉。
syslog是Linux系统原生的日志服务,它有几个天然优势:
- 标准化:几乎所有嵌入式Linux发行版都支持
- 等级分明:从emerg到debug,8个等级够你用
- 远程能力:原生支持UDP发送到远程日志服务器
- 性能好:异步写入,不会阻塞你的WebRTC音视频线程
我个人习惯,只要板子上有Linux内核,第一件事就是把syslog配好。你想想看,一个通话质量分析,需要毫秒级的时间戳和准确的日志等级,自己写的日志函数能做到吗?
3.2 自定义日志等级:别被8个等级框死
syslog自带的8个等级(emerg、alert、crit、err、warning、notice、info、debug)在通用场景下够用。但做WebRTC通话,你会发现有些场景很尴尬。
举个例子:ICE连接状态变化。这算info还是debug?说它是info吧,ICE状态切换太频繁,日志会刷屏;说它是debug吧,出问题时你又想快速看到它。
我的做法是:在syslog等级基础上,叠加一个模块标签和子等级。
// 自定义日志宏
#define LOG_WEBRTC(level, module, fmt, ...) \
do { \
if (level <= g_log_level) { \
syslog(level, "[%s] " fmt, module, ##__VA_ARGS__); \
} \
} while(0)
// 使用示例
LOG_WEBRTC(LOG_INFO, "ICE", "新连接建立, 候选者类型: %s", cand_type);
LOG_WEBRTC(LOG_DEBUG, "RTP", "序列号: %u, 时间戳: %u", seq, ts);
这样,你可以通过g_log_level全局变量,动态控制日志的详细程度。我在项目中遇到过,现场调试时把等级调到DEBUG,问题复现后立刻调回INFO,既拿到了关键信息,又不会把存储空间撑爆。
小技巧:把模块标签统一用大写字母,比如ICE、RTP、SDP、STUN。这样在日志文件里grep的时候,一眼就能定位到目标模块。
3.3 日志轮转策略:别让日志撑爆Flash
嵌入式设备的存储空间通常很紧张。一块128MB的Flash,系统占一半,应用占一部分,留给日志的空间可能就10MB。如果不做轮转,一个DEBUG级别的日志跑一天就能把空间吃光。
syslogd自带的轮转机制,说实话,在嵌入式环境里不太够用。我推荐用logrotate工具,配合自定义配置。
这是我的典型配置:
/var/log/webrtc.log {
size 5M # 每个日志文件最大5MB
rotate 3 # 保留3个历史文件
compress # 压缩历史文件
delaycompress # 延迟压缩,方便查看最新历史
missingok # 文件不存在不报错
notifempty # 空文件不轮转
postrotate
# 通知应用重新打开日志文件
kill -HUP `cat /var/run/syslogd.pid`
endscript
}
为什么用size而不是daily?因为嵌入式设备的日志产生速度不稳定。通话高峰期,一小时可能产生10MB日志;空闲时段,一天可能只有几百KB。按大小轮转更合理。
| 轮转策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 按大小(size) | 日志产生速度波动大 | 存储空间可控 | 轮转频率不确定 |
| 按时间(daily) | 日志产生速度稳定 | 时间规律,便于归档 | 高峰期可能撑爆空间 |
| 按数量(rotate) | 存储空间极小 | 历史文件数量固定 | 可能丢失重要历史 |
警告:我曾经在一个项目里,因为日志轮转配置不当,导致syslogd进程把Flash写坏了。原因是轮转时同时压缩和删除,I/O压力太大。后来我加了delaycompress和missingok,问题才解决。
3.4 远程日志收集方案:现场出问题,你在办公室就能看
嵌入式设备部署在客户现场,你不可能每次都跑过去插串口线。远程日志收集是必备能力。我常用的方案有三种:
方案一:syslog远程发送(最简单)
修改syslog配置文件,把日志同时发到远程服务器:
# /etc/syslog.conf
# 本地记录
*.* /var/log/webrtc.log
# 远程发送
*.* @192.168.1.100:514
远程服务器上启动一个syslogd监听即可:
syslogd -R 0.0.0.0:514
这个方案的好处是零代码改动,但缺点也很明显:UDP传输可能丢包,而且没有加密。
方案二:TCP长连接+自定义协议(推荐)
我在项目中用得最多的是这个方案。在设备端开一个线程,维护到日志服务器的TCP长连接,把日志按自定义格式发送。
// 伪代码示例
void *log_sender_thread(void *arg) {
int sock = tcp_connect("logserver.company.com", 9000);
while (1) {
char log_buf[1024];
// 从本地日志队列取一条日志
log_queue_pop(log_buf, sizeof(log_buf));
// 发送到远程服务器
send(sock, log_buf, strlen(log_buf), 0);
// 心跳保活
if (need_heartbeat()) {
send(sock, "PING", 4, 0);
}
}
}
TCP方案可靠,但要注意:网络断开时要自动重连,而且不能阻塞主业务线程。我习惯用环形缓冲区做日志队列,生产者(业务线程)和消费者(发送线程)解耦。
方案三:日志文件上传(适合弱网环境)
如果网络不稳定,TCP长连接经常断,那就用这个方案:日志先写本地,轮转后把历史文件通过HTTP/FTP上传到服务器。
我做过一个项目,设备在海上平台,网络时断时续。TCP长连接方案根本跑不起来。后来改成:本地保留3个日志文件,轮转时触发上传,上传失败就等下次轮转再试。效果很好。
核心要点:远程日志收集,可靠性比实时性重要。宁可晚到几分钟,也不能丢日志。因为你要靠这些日志还原现场,少一条关键日志,可能就找不到根因。
3.5 整体架构:一张图看懂
下面这张图,是我在多个项目里沉淀下来的日志系统架构。你看完应该能直接照着搭。
这个架构的核心思想是分层解耦。应用层只管调用日志接口,不用关心日志最终去了哪里。处理层负责等级过滤和模块分类。分发层根据配置,决定日志是写本地、发远程、还是先缓存。底层负责轮转、上传、重连等脏活累活。
我建议你从最简单的方案一开始搭,跑通了再加远程和轮转。别一上来就想搞个大而全的系统,容易把自己绕进去。
3.6 避坑指南:我踩过的几个坑
日志系统看起来简单,但实际落地时坑不少。我挑几个典型的说说:
- 日志打太多,影响WebRTC实时性:我曾经在ICE连接阶段打了大量DEBUG日志,结果导致网络线程被日志I/O阻塞,连接建立时间从500ms变成了3秒。解决办法:把日志写入放到独立线程,用异步队列。
- 远程日志丢数据:UDP发送日志,网络抖动时丢包严重。后来我改成TCP+本地缓存,发送失败就暂存,下次重发。
- 时间戳不准:嵌入式设备如果没有NTP同步,系统时间可能差几个小时。我习惯在日志里同时打上系统时间和上电以来的毫秒数(uptime),这样即使系统时间不准,也能分析出事件发生的相对顺序。
- 日志文件权限问题:WebRTC进程如果以非root用户运行,可能没有权限写日志文件。记得在syslog配置里指定正确的用户和组。
重要提醒:千万不要在生产环境把日志等级开到DEBUG。我见过一个团队,设备上线后忘了改日志等级,结果DEBUG日志每小时产生200MB数据,两天后Flash写满,设备直接死机。教训深刻。