4、系统日志实战:自定义日志标签、日志轮转策略、远程日志收集

日志这东西,平时你可能觉得它不起眼。但系统一旦出问题,它就是你的救命稻草。我在QNX项目里摸爬滚打这么多年,可以负责任地告诉你:日志系统设计得好不好,直接决定了你定位问题的速度。

这一章,咱们就聊聊日志的实战技巧。说白了,就是三件事:怎么给日志打标签方便检索、怎么让日志文件不撑爆磁盘、怎么把日志集中到一台服务器上统一看。

4.1 自定义日志标签:让日志会说话

默认的 slog 输出,信息量其实挺有限的。你想想看,一个系统里几十个进程都在写日志,你 grep 出来一堆时间戳,根本分不清谁是谁。

我个人习惯,每个模块都定义一个唯一的日志标签。这样一眼就能看出是哪个组件在说话。

4.1.1 在代码中定义标签

QNX 的 slogf() 函数允许你传入一个字符串作为标签。比如这样:

#include <sys/slog.h>
#include <sys/slogcodes.h>

#define MY_TAG "CAN_DRIVER"

void can_send_message(uint32_t id, uint8_t *data) {
    slogf(_SLOG_SETCODE(_SLOG_SYSLOG, 0), _SLOG_INFO,
          MY_TAG " SEND: id=0x%x len=%d", id, data[0]);
    
    // 实际发送逻辑...
    
    slogf(_SLOG_SETCODE(_SLOG_SYSLOG, 0), _SLOG_DEBUG,
          MY_TAG " SEND_OK: id=0x%x", id);
}

这里有个小技巧:MY_TAG 直接写在格式化字符串前面,中间用空格隔开。这样 sloginfo 显示的时候,标签和消息内容自然就分开了。

我的经验:标签命名建议用模块名_子模块的格式,比如 CAN_DRIVER_RXCAN_DRIVER_TX。别用太短的缩写,否则 grep 的时候容易误匹配。

4.1.2 用 sloginfo 过滤标签

日志写好了,怎么快速找到想看的内容?sloginfo-f 参数就是干这个的。

# 只看 CAN_DRIVER 的日志
sloginfo -f CAN_DRIVER

# 只看错误级别的日志,并且包含 CAN
sloginfo -f CAN -l ERR

# 实时跟踪某个标签
sloginfo -f CAN_DRIVER -w

嗯,这里要注意:-f 参数是模糊匹配。如果你的标签是 CAN_DRIVER,用 -f CAN 也能匹配到。我个人建议尽量用完整标签,避免误伤。

4.2 日志轮转策略:别让日志撑爆磁盘

嵌入式设备的存储空间通常很有限。我曾经遇到过一台设备连续跑了三个月,日志文件涨到了 2GB,直接把根分区撑爆了。系统挂了,客户急得跳脚。

从那以后,日志轮转就成了我必做的配置。

4.2.1 slogger2 的轮转配置

QNX 的 slogger2 自带轮转功能。配置文件一般在 /etc/slogger2.conf

# 每个日志文件最大 10MB
log_file_size 10485760

# 保留最近 5 个文件
log_file_count 5

# 日志文件路径
log_file_path /var/log/system_log

# 压缩旧日志(可选)
log_compress 1

配置好之后,重启 slogger2 服务:

slogger2 -u /etc/slogger2.conf
核心逻辑:当当前日志文件达到 log_file_size 时,slogger2 会自动重命名文件,然后新建一个空文件继续写。旧文件会按序号递增,超过 log_file_count 的最旧文件会被删除。

4.2.2 自定义轮转脚本

有些场景下,slogger2 的轮转策略不够灵活。比如你想按天轮转,或者轮转后把日志上传到服务器。这时候就得自己写脚本了。

我常用的方案是用 cron 定时任务 + 脚本:

#!/bin/sh
# /etc/scripts/rotate_log.sh

LOG_DIR="/var/log"
MAX_SIZE=10485760  # 10MB
RETENTION_DAYS=7

for logfile in $(find $LOG_DIR -name "*.log" -type f); do
    size=$(stat -c%s "$logfile")
    if [ $size -gt $MAX_SIZE ]; then
        timestamp=$(date +%Y%m%d_%H%M%S)
        mv "$logfile" "${logfile}.${timestamp}"
        touch "$logfile"
        
        # 删除超过保留天数的旧日志
        find $LOG_DIR -name "${logfile}.*" -mtime +$RETENTION_DAYS -delete
    fi
done

然后在 crontab 里每小时执行一次:

0 * * * * /etc/scripts/rotate_log.sh
避坑指南:我曾经犯过一个错误——轮转脚本和 slogger2 同时操作同一个日志文件,结果文件被写坏了。建议要么只用 slogger2 的轮转,要么停掉 slogger2 的轮转功能,完全由脚本控制。别两个一起上。

4.3 远程日志收集:集中管理才是王道

当你有几十台甚至上百台设备时,挨个登录上去看日志是不现实的。远程日志收集,说白了就是把所有设备的日志统一发到一台服务器上。

4.3.1 基于 syslog 的远程转发

QNX 原生支持 syslog 协议。配置起来很简单:

在每台设备上编辑 /etc/syslog.conf

# 将所有日志转发到 192.168.1.100 的 UDP 514 端口
*.* @192.168.1.100:514

重启 syslogd:

syslogd -f /etc/syslog.conf

在日志服务器上,启动一个 syslog 接收端:

syslogd -r -b 192.168.1.100

-r 参数表示允许接收远程日志,-b 指定绑定的 IP 地址。

我的建议:生产环境中,UDP 可能会丢包。如果日志完整性要求高,可以考虑用 TCP 协议。把 @ 改成 @@ 就是 TCP 模式:*.* @@192.168.1.100:514

4.3.2 用 slogger2 实现远程收集

slogger2 也支持远程转发。配置方式更灵活:

# 在 slogger2.conf 中添加远程目标
remote_target 192.168.1.100:9998
remote_protocol tcp
remote_compress 1

服务器端启动接收:

slogger2_recv -p 9998 -o /var/log/remote/

这样,所有设备的日志都会实时传输到服务器上,按设备名或 IP 分目录存储。

4.3.3 日志收集的注意事项

问题 影响 解决方案
网络中断 日志丢失 本地缓存 + 断点续传
带宽占用 影响业务通信 压缩传输、限制速率
时间不同步 日志时序混乱 所有设备启用 NTP
日志量过大 服务器磁盘爆满 服务器端也做轮转和归档

嗯,这里要特别强调时间同步。我记得有一次排查一个故障,发现两台设备的日志时间差了 3 秒,导致事件顺序完全对不上。折腾了半天才发现是 NTP 没配好。所以,远程日志收集的前提,一定是所有设备的时间是统一的。

4.4 实战:搭建一个完整的日志系统

说了这么多,咱们来整合一下。一个生产级的日志系统,应该包含这几个部分:

  1. 本地日志:每个进程用 slogf() 输出带标签的日志
  2. 本地轮转:slogger2 或脚本控制日志文件大小和数量
  3. 远程转发:通过 syslog 或 slogger2 将日志发送到中央服务器
  4. 服务器端存储:按设备、时间分目录存储,并做二次轮转
  5. 日志分析:用 grepawk 或 ELK 等工具做检索和告警

我个人习惯在服务器上再跑一个简单的告警脚本。比如,如果某个设备连续 5 分钟没有发送日志,就发邮件通知。这能帮你第一时间发现设备离线或日志系统故障。

总结一句话:日志系统不是写出来就完事了。你得让它能转、能传、能查。这三件事做好了,系统运维就成功了一半。

下一章,咱们会深入 QNX 的进程调试工具,聊聊怎么用 pidinslay 这些命令快速定位进程问题。到时候见。