接手过一批 Linux 主机日志接入统一日志平台的需求之后我把方案定成了 Rsyslog 方式。最初团队里有人建议在这批机器上装一个采集 Agent好处是字段丰富、能断点续传但坏处也很明显机器多、配置杂Agent 版本一更新就要全线重推。而每台 Linux 主机都自带 Rsyslog改几行配置就能把系统日志和应用日志转发到中心端几乎零侵入。这篇文章就把我这次实施的全过程写出来包括选型逻辑、配置拆解、验证方法以及几个很容易踩的坑。1. 为什么统一日志平台选型时Rsyslog 往往被最先考虑1.1 主机日志接入的常见路线Agent、SDK 与远程转发先把三条路线摆在桌子上给每台机器装采集 Agent在应用里集成 SDK 直接上报或者让机器通过远程 syslog 协议把日志转发到中心端。Agent 的特点是高保真。它能直接读取日志文件解析多行异常栈甚至采集容器元数据在前端做过滤再传输。代价是部署、升级、配置分发都要纳入运维体系机器规模一大Agent 本身就成了一个新的维护负担。SDK 上报适合应用主动推送比如业务埋点、函数计算日志但系统日志、内核消息、服务管理器日志它一概管不到。远程 syslog 转发则恰好覆盖这批“系统自带但没 Agent”的主机它基于标准协议Linux 原生支持不需要额外安装任何东西。用这种方式接入统一日志平台核心就是把 Rsyslog 当作一个轻量转发器本地收集、格式化、加密按需、推送到平台侧接收端。实际场景中三者不是互斥的。很多平台收敛的主机有上百台其中一部分机器只跑 Nginx、内网 DNS、本地应用日志量不大装 Agent 纯属资源浪费另一部分核心业务机器已经装了 Agent 或者用了 SDK。这时用 Rsyslog 做梯度补充是最省事的路径。这次的实施对象就是那批“不想装 Agent”的资源受限主机。1.2 Rsyslog 的优势和适用边界Rsyslog 能长期活在主流里的唯一理由其实不是功能多而是“刚刚好”。它读取 /dev/log 或者 journald 都不需要额外权限系统日志、认证日志、邮件日志都有现成来源它支持 TCP、TLS、RELP 等可靠传输避免了 UDP 丢包问题它还有动作队列可以在网络瞬断时把日志缓存到磁盘恢复后再继续发送。这些特性对于一个日志接入方案来说已经足够扎实。但它也有边界。Rsyslog 对多行日志的处理比较吃力比如 Java 异常堆栈跨了四五行它默认会把每一行当成独立消息处理后续在平台侧要花很大功夫合并。它对结构化格式的支持需要自己写模板不像专门采集器那样开箱就能吐 JSON。如果你的平台对日志字段的完整度要求极高或者需要采集任意路径下的海量日志文件Rsyslog 可能就不是最优解此时更合适的还是专门的采集 Agent。所以我的选型判断标准很简单日志量中等、机器资源紧张、希望零侵入接入的优先 Rsyslog日志量大且要求高保真解析的仍然走 Agent。1.3 整体链路从主机日志到平台检索这次实施的整体链路分四段。第一段在 Linux 主机内系统日志由 imjournal 模块从 journald 读取普通文本日志由 imfile 模块读取指定文件第二段在客户端 rsyslog 的动作层通过 omfwd 模块将经过模板格式化后的日志发送到接收端第三段是平台侧的 rsyslog 接收端它监听一个 TCP 端口把消息按来源主机拆到不同文件或者直接转发到更下游的消费程序第四段才是统一日志平台的写入通道常见的是接入消息队列后由消费端写入检索引擎最后在查询界面上按主机、时间、设施、级别、关键字完成检索。链路听起来简单但每一段都有细节。比如客户端模板里要带上原始主机的 IP 和 tag否则所有日志到了中心端后都变成接收服务器的来源服务端如果直接写入检索引擎必须考虑日期字段时区消息队列如果不做数据压缩流量会跑掉好几倍。后面我会按顺序把这些点展开直接给出可用配置。2. 动手前先理清清单日志源、端口、格式与流量2.1 日志源清单哪些该接、哪些不该接动手配 Rsyslog 之前我先整理了一张日志源清单。清单里的核心字段是日志类别、原始路径、日志量级、重要程度。系统日志这一类包括 /var/log/messages、/var/log/secure、/var/log/cron它们本身已经通过 syslog 通道送到本机 rsyslog因此重点是把本机收集结果转发出去而不需要再重复读文件。业务应用日志这一类一般是应用自己写文件的路径比如 /data/logs/portal/app.log。Rsyslog 要读这种文件必须用 imfile 模块主动轮询而且要注意文件轮转。Nginx 这类访问日志同样适合 imfile但它有自己特有的日志格式我建议在 Rsyslog 侧只负责整体搬运把原始内容传过去到平台解析阶段再按格式解析后续扩展也方便。哪些日志不该接一是重复的、粒度太细的内核调试信息比如某些设备的驱动疯狂输出内核日志接了只会挤占带宽二是临时文件、缓存日志比如应用启动时会写一个带 PID 的临时文件内容经常为空三是已经被其他 Agent 采集过的日志避免同一份日志在平台侧重复计费、重复索引。清单确认后还要给每条日志定一个 tag这个 tag 最终会变成平台侧一个检索字段tag 命名规范最好统一为“应用名_模块名”。2.2 端口与会话规划不要默认 514Rsyslog 默认端口是 514但这个端口有两个问题一是属于特权端口服务端进程要用 root 权限才能监听二是很多安全团队会对默认端口格外敏感扫描器第一个扫的就是它。所以这次我们把客户端到接收端的端口规划成 5514这个数字只是示意关键是明确端口用途接收端监听一个 TCP 端口用于可靠传输同时留一个 UDP 端口用于兼容那些不支持 TCP 的老设备。TLS 用同一个 TCP 端口在接收端配置里通过证书校验区分普通连接和 TLS 连接。如果服务端有多个接收节点还需要提前约定同一台主机的日志只会持久化到一个节点上防止重复写入。防火墙放通规则也要写成白名单客户端出方向只允许连接收服务器的 5514 端口的 TCP服务端入方向只允许平台网段的流量进入该端口。端口规划看似小事但实施后反复变更端口非常痛苦因为客户端配置分散在几十台机器里每改一次都要重新推送。2.3 日志格式模板从 RFC3164 到 RFC5424Rsyslog 默认转发时用的传统 syslog 格式RFC3164长这样PRIAug 10 09:00:01 hostname app[pid]: message。它的最大问题是时间没有年份日期只有三个字母缩写消息体里如果包含空格和冒号服务端解析时很容易产生歧义。这次我强烈建议用 RFC5424 格式它的头部包含版本、结构化字段消息体有明确的长度标记。Rsyslog 中可以通过模板定义生成 RFC5424核心是让消息带一个合适的 PRI 值和1的版本号。更重要的是模板里要嵌入自定义字段。比如我想让平台侧知道日志来自哪台机器、原始客户端 IP、应用 tag就可以在模板的消息体前插入一段 JSON 结构的元数据字段template(nameUnifiedLogFormat typestring string%PRI%1 %timestamp:::date-rfc3339% %HOSTNAME% %app-name% %procid% %msgid% [log6870 clientip\%fromhost-ip%\ tag\%syslogtag%\] %msg%\n)很多人会忽略%fromhost-ip%导致接收端看到的主机名不是真实客户端而是 NAT 或跳板机。把客户端 IP 直接放进日志头里后续做来源审计会省事很多。2.4 日志量与队列内存的粗算方法配置队列之前最好先粗算日志流量。公式很简单单台主机日均日志量除以 86400得到平均每秒行数再乘以平均每行字节数就是 bps。日均 10GB 日志的主机平均每秒约 120 条假设一条 800 字节但由于访问日志有明显波峰峰值往往是均值的 5 倍左右也就是约 600 条/秒瞬时带宽约 5Mbps。一百台这样的主机平台侧峰值入口流量可能在 500Mbps 甚至更高如果中间再叠加 TLS 加解密处理压力会进一步放大。队列参数也据此估算。客户端 rsyslog 默认使用内存队列队列大小设置成能缓存多少条消息比如 10000 条队列大小每条按 1KB 算占用约 10MB 内存。如果队列大小设置为 100000就是 100MB很多小主机受不了。我给客户端定的是内存队列 10000队列写满后 spill 到磁盘磁盘缓冲上限 1GB。这样既保证了突发消息不丢又不会把内存撑爆。服务端接收节点则需要更大队列因为它要处理多台主机的汇总流量磁盘队列文件建议单独挂一块硬盘避免和系统日志共用 IO。3. 客户端配置把日志干净地送出去3.1 模块与配置文件结构检查客户端配置的第一步不是写配置而是确认 Rsyslog 版本和模块。用rsyslogd -v查看版本低于 8.x 的版本我建议先升级因为 8.x 对配置语法和队列的改进很大。然后检查/etc/rsyslog.conf主文件确认加载了以下核心模块imuxsock读取本机 /dev/log 上的 syslog 消息imjournal读取 systemd journal 的日志如果使用 journaldimfile让我们能配置任意文本文件作为输入omfwd远程转发动作模块属于内置模块一般无需显式 load但需要确认可用。配置文件结构方面现在主流发行版的 rsyslog 都会include /etc/rsyslog.d/*.conf。我建议把所有自定义配置放到独立文件里比如/etc/rsyslog.d/shipper.conf不要动主文件里已经写好的规则。改完配置文件后先执行rsyslogd -N1做语法检查看到end of config file提示后再重启服务。注意语法检查只查配置语法不保证业务逻辑正确所以最终还是要用真实日志来验证。3.2 最小可用转发配置与参数解读下面这段配置是我们客户端实际用的核心片段去掉敏感信息后大概这样module(loadimuxsock SysSock.Useon) module(loadimjournal StateFile/var/lib/rsyslog/imjournal.state) template(nameUnifiedLogFormat typestring string%PRI%1 %timestamp:::date-rfc3339% %HOSTNAME% %app-name% %procid% %msgid% [log6870 clientip\%fromhost-ip%\ tag\%syslogtag%\] %msg%\n) ruleset(namesendToPlatform) { action(typeomfwd Target10.0.100.10 Port5514 Protocoltcp TemplateUnifiedLogFormat TCP_Framingoctet-counted Queue.FileNameq_forward Queue.MaxDiskSpace1g Queue.SaveOnShutdownon Queue.TypeLinkedList Queue.Size10000 ResendLastMSGOnReconnectoff) } syslog.* stop *.info;authpriv.none action(typeomfile file/var/log/messages)这段看起来简单但有三个关键参数要特别说明TCP_Framingoctet-counted每条 syslog 消息前加一个长度字段这样接收端可以正确切分消息避免半包和粘包问题。接收端需要配套支持两边必须一致。Queue.TypeLinkedList使用链表型内存队列适合突发写入任务执行速度相对较快。ResendLastMSGOnReconnectoff关闭断线重连后的消息重发否则消息量大的时候可能出现重复日志。如果要求严格不丢失需要换用 RELP 协议或让接收端做去重。3.3 应用日志接入用 imfile 维护文件读取状态系统日志能通过上面的配置直接转发但应用日志连的是自己的文本文件Rsyslog 默认不认。这时要用imfile模块。对每条应用日志文件添加一个 input 实例module(loadimfile Modepolling) input(typeimfile File/data/logs/app/portal.log Tagportal_biz Severityinfo Facilitylocal7 RulesetsendToPlatform ReadModetail MaxLinesPerPass1000 StateFileportal_biz.state)ReadModetail非常重要它让 imfile 从文件尾部开始读取而不是从头扫描整个历史文件。否则第一次启动时Rsyslog 可能会把几个 GB 的历史日志全部转发出去。StateFile用来记录当前文件读取位置的偏移量Rsyslog 会把它写到/var/lib/rsyslog/下只要文件没被改名或清空重启后可以继续从断点读取。MaxLinesPerPass限制每次读取的行数防止某一瞬间文件里堆积了几万行时Rsyslog 单次处理耗时过长阻塞其他动作。应用日志还有个容易忽略的细节如果应用自己按照日期滚动文件比如app.log.2025-01-01你只配置了app.log那么滚动后新文件会继续被读取旧文件不会重复读。但如果你需要采集历史归档文件要再配置额外的 input并小心处理重复消息。3.4 客户端自测重启与状态观察配置写完后我不会立刻重启 rsyslog 服务而是先做三件事执行rsyslogd -N1确认没有语法错误。用logger -p local7.info integration test message发送一条测试日志隔几秒查看队列文件是否增长。检查/var/log/messages是否正常写入确认本机日志功能没被“转发规则”误伤。重启服务后立刻观察/var/log/rsyslog/status或者用journalctl -u rsyslog查看进程日志看有没有队列初始化失败、模块加载失败的提示。如果测试日志没有出现优先检查接收端端口是否可达telnet 10.0.100.10 5514能通再看 Rsyslog 规则。很多“没日志”的问题根因是客户端配置里ruleset名称写错或者stop语句条件过宽把本不该转发的消息全部拦截了。4. 服务端接入与平台写入通道设计4.1 接收端 Rsyslog 配置服务端接收配置相对简单核心是加载 TCP 输入模块并监听指定端口module(loadimtcp MaxSessions5000) input(typeimtcp port5514 namesyslog_tcp rulesetreceiveAll)MaxSessions要按峰值客户端数量预留否则连接数一多新客户端连接会被拒绝。接收端还可以再加载imudp监听一个 UDP 端口专门接收老设备发来的不可靠传输但 UDP 和 TCP 的规则集最好分开避免把 UDP 收到的过期消息混入主链路。接收端的动作先落到本地文件也就是原始日志文件作为第一层安全网然后再异步转发给下游处理程序。这样可以做到“即使下游挂了原始日志也不丢”。4.2 日志路由规则按主机、设施、级别分发接收端收到日志后必须在 ruleset 里做路由拆分。我们可以按$hostname或$fromhost-ip来区分来源主机按$syslogfacility区分系统日志与应用日志按$syslogseverity-text区分错误级别。中心端不建议把所有来源写进同一个文件最好一个主机一个目录、一个目录下分系统日志与应用日志两个文件。路由配置示例ruleset(namereceiveAll) { if ($fromhost-ip 10.0.10.0/24) then { action(typeomfile file/data/loghub/net/10.0.0.0-24.log) stop } if ($syslogfacility local7) then { action(typeomfile file/data/loghub/app/app_log.log templateRawLog) stop } action(typeomfile file/data/loghub/others/other.log) }实际生产环境的路由规则可能多达几十条很容易写出互相覆盖的条件。我的经验是把“stop”语句放在每个分支尾部让规则“命中即短路”这样既清晰又高效。4.3 写入通道选型直连检索引擎还是走消息队列接收端把日志写到文件后还需要把它们送入统一日志平台。这里有三条路直接调用检索引擎的批量写入接口。适合日志量小、对实时性要求不高的场景。优点是链路短缺点是检索引擎一旦不稳定日志就会因为写入失败而堆积Rsyslog 动作队列被阻塞。先写入消息队列再由消费程序写入检索引擎。这是大规模日志平台最常用的做法。消息队列天然具备削峰填谷的能力接收集群和检索引擎解耦重建索引时消息不会丢。使用 Logstash 直接消费 Rsyslog 格式数据但 Logstash 自身也要维护我不建议把 Rsyslog 接收、解析、写入全塞给 Logstash 一个进程除非日志量确实很小。我这次采用的方案是接收端 rsyslog 写文件 - 轻量采集器采集该文件 - 消息队列 - 消费程序解析 - 检索引擎。你可能觉得绕了一圈但好处非常实际消息队列的消费端可以重复消费修复解析 bug 后不用等原始日志重新上传监控队列积压也很简单。4.4 字段规范与索引映射准备日志进入检索引擎之前要提前规划好字段。最基础的字段包括timestamp、hostname、facility、severity、tag、msg。日期字段在检索引擎里应显式声明为date类型否则同一字段里混入字符串后 mapping 就乱了。tag和hostname这类枚举值有限且用于聚合的字段推荐声明为keyword类型。最怕的是直接用默认动态映射把 hostname 也映射成 text导致聚合不准。消息体msg可以保存为 text 或 keyword。如果只需要关键字检索建议限制长度或设置ignore_above避免一条超长堆栈占用大量存储。平台侧最好配一个索引生命周期策略比如热阶段存 7 天、温阶段存 30 天、冷阶段存 90 天然后自动删除超过策略的索引。这个策略必须在接入前配好否则日志一旦大量进来索引管理会变成磁盘杀手。5. 上线前的五步验证与踩坑实录5.1 五步验证法从 logger 到检索界面我把上线前的验证分成五步每步都不能省略客户端生成测试消息logger -p local7.info integration test from host-a。客户端确认消息已发送查看 rsyslog 队列状态文件或抓包tcpdump -i eth0 port 5514。服务端确认消息已收到查看接收端对应日志文件或抓包确认端口有入流量。平台侧确认字段解析正确到查询界面按tag:integration搜索核对时间、主机名、消息体是否完整。端到端延迟确认从发送到可检索的时间最好在 5 秒以内超过 30 秒就要检查队列和消费端积压。每一步都要留下输出记录方便后续回滚。上线时我会按批次推送配置先推 5 台观察 1 小时再推 20 台最后全量切忌一次性把所有机器全部重配。5.2 坑 1时间显示偏移八小时第一批日志进平台后就发现查询界面的时间比真实时间早了 8 小时。根因很快定位Rsyslog 模板里用的%timestamp%默认是 UTC 时区而平台查询界面默认用本地时区显示。处理办法有两个要么在客户端模板里改用%timegenerated%并加上date-rfc3339要么让平台消费端把时间字段按 UTC 解析后再在展示层转换。我最后选择了后者因为日志传输链路里统一使用 UTC服务器本地时间不同也没关系展示层再做时区转换不会因为某台机器时区配错而出现时间交叉。这个坑提醒我接日志前一定要先约定时间规范并且用测试日志验证时间戳而不是等到数据量大了才发现。5.3 坑 2系统日志与 journald 重复入库有一台机器同时开了imjournal采集 journald 日志又通过 imfile 读取了/var/log/messages文件结果平台里同一份系统日志出现两次。原因是 journald 本身就包含了系统日志/var/log/messages 里的内容也是由 rsyslog 从 imjournal 写入文件的二者同源。解决方式很简单系统日志只保留一条链路。我选择用 imjournal 作为系统日志的来源把*.* action(typeomfile)写入 messages 的部分保留但不针对/var/log/messages再配置 imfile。如果平台侧仍然出现少量重复可以在规则集里对“主机名tag消息体时间戳”做一次聚合去重。重复日志在平台里很耗存储必须在一开始就堵住。5.4 坑 3大日志文件导致 Rsyslog 卡顿某台应用服务器的日志文件在高峰期每分钟增长上百 MB配置 imfile 之后发现 rsyslog 进程 CPU 占用 100%其他日志也延迟发送。排查后发现imfile 在ReadMode设置错误时会不断重复读取新行。即使设置正确高峰期一次读取数万行主循环被一个动作阻塞也容易让系统日志排队。处理办法是调小MaxLinesPerPass比如改成 2000把日志文件的 input 单独放到一个ruleset通过队列把它和系统日志的动作分成不同的任务流。还需要给 imfile 加上PollingInterval参数避免文件变更触发过于频繁。另外建议把大日志文件按天轮转减少单文件扫描开销。5.5 坑 4TLS 认证失败与配置语法隐蔽错误给少量公网主机加 TLS 时踩过的坑是证书链权限。Rsyslog 要求证书和私钥对当前用户可读如果私钥文件权限是 600 且属主是 root而 rsyslog 以非特权用户运行就会报权限错误。这个坑很隐蔽看起来像 TLS 握手失败实际是文件无法读取。另一个隐蔽错误是模板里的转义。引号必须写成\否则配置解析会直接报错。还有两个少见的坑同一条消息同时被多条规则输出到不同文件会产生复制规则里使用了stop但后边还有动作会让配置行为变得难以理解。遇到这种问题我的排查顺序是rsyslogd -N1查语法再看/var/log/messages里有没有 rsyslog 自身的报错最后抓包看消息有没有发出。6. 性能与稳定性调优从能跑到稳跑6.1 队列类型与大小Direct、LinkedList、FixedArrayRsyslog 的队列决定日志在内存和磁盘之间如何排队。常用三种Direct队列不缓冲消息到达后立即执行动作响应最快但网络抖动时容易丢消息。LinkedList队列内存粒度动态分配适合突发流量请求高峰时能吸收较大抖动。FixedArray预分配固定数组内存占用稳定但队列写满时行为不如 LinkedList 平滑。我通常给客户端用 LinkedList给接收端用 FixedArray如果日志量稳定或 LinkedList如果波动大。队列大小设置要根据消息量计算太小会频繁写磁盘太大会消耗内存。队列写满后的行为也要配置queue.discardmark默认是 90%超过这个水位会丢弃新消息queue.timeoutEnqueue控制在队列满时投递等待时间。生产环境建议开启queue.saveonshutdown确保服务重启时队列里的日志能落盘。6.2 worker 线程与 TLS 开销多核机器上的 Rsyslog 默认只有一个 worker 线程处理海量日志时会成为瓶颈。可以对队列设置workerThreads比如 4 核机器设置 4 个线程。但并不是线程越多越好动作如果集中写一个文件多线程反而会争抢文件锁性能下降。写不同文件时可以多线程写同一个文件保持单线程。TLS 的开销主要在握手和加解密普通日志量下不明显但流量大时会显著增加 CPU。所以要么让收到 TLS 日志后的处理动作尽量异步化要么在特定网段内使用明文 TCP 加内网安全策略公网链路才启用 TLS。6.3 日志量突增的限流与丢弃策略日志平台最怕的不是正常流量而是突增。某台应用异常时每秒可能产生几千甚至上万条错误日志如果客户端 rsyslog 不设限消息队列和检索引擎会被瞬间打爆。应对手段是在客户端加限流。Rsyslog 的 ruleset 可以配置速率窗口和突发数量比如每秒最多接收 1000 条日志突增时超出的消息直接丢弃或根据级别选择性丢弃。这样做的目的是保护整体平台可用性不能为了收集所有异常日志而拖垮整个集群。接收端对同一主机也应该加单主机限速防止某一个客户端异常影响其他主机。6.4 Failover 与高可用设计接收端如果只有一台客户端网络或服务端宕机都会导致日志丢失。Rsyslog 的omfwd支持配置多个 target并按顺序尝试连接action(typeomfwd target10.0.100.10 port5514 protocoltcp) action(typeomfwd target10.0.200.10 port5514 protocoltcp queue.filenameq_alt queue.typeLinkedList)这会让 Rsyslog 在主 target 连接失败时自动切换备 target切换期间日志落入磁盘队列。但要注意两个 target 都失败时队列可能持续增长磁盘空间会被耗完。因此还需要定期清理过期队列文件或接入消息中间件做高可用缓冲层。我们的实施中最终用两台接收节点做完全一样的接收配置前面放一个四层负载均衡客户端连负载均衡地址两台节点同时消费虽然重复写入风险增加了但高可用性大幅提升重复可以在平台侧去重。到这里本次 Linux 主机日志接入统一日志平台的实施过程基本写完了。回头看最重要的其实不是某一条配置而是把链路里各个环节的边界、格式、容量都预先对齐。按这个思路一步步落地接入过程的返工率会低很多平台侧的运维也会轻松不少。