不是所有人一开始就意识到日志要集中管理的。我见过太多团队,排查线上问题的时候,打开五六台服务器,在各自的/var/log/messages里grep一个关键词,翻半天才拼出完整的时间线。那次凌晨三点的故障,让我下定决心把 Rsyslog 日志服务器配置搬到生产环境。这篇文章就是我完整做下来之后的实战记录,从协议选型到踩坑排查,都给读者交代清楚。
我并不是要把所有日志都塞进一套简化版的采集链路完事,而是要搭建一个能接得住百万行日志、能加密传输、能方便扩展的集中式日志平台底座。如果你负责三五台服务器、正准备把分散的系统日志统一收拢,或者想在自己的环境里搭一套轻量日志中台又不想上来就引入重框架,这篇会比较对胃口。
1. 集中式日志到底解决了什么问题
1.1 一次凌晨三点的“找日志”经历
事情发生在某次线上告警之后。某套业务系统半夜出现批量请求超时,我带着另外一名同事分别登录两台应用服务器看日志,又让负责人去翻数据库那台的系统日志。三个人、三台机器,用手在终端里滚动输出,再用grep反复匹配。运气差的时候,还要去翻/var/log/syslog、/var/log/messages、/var/log/secure好几种文件,才能把一条链路串起来。
这种模式最大的问题不是“麻烦”,而是“慢了”。故障期间每一分钟都是钱,登录跳板机再逐台切换所花的时间,往往比定位问题本身还长。而且系统日志和应用日志是分开的,应用日志里不会自动带上内核的网络报错,系统日志里也不会自动记录应用重启的调用链。两边一脱节,排查就变成了猜。
那之后我形成了三个硬性需求:日志必须集中在一台服务器上;日志必须带上精确到毫秒的时间戳;日志必须能按主机名、程序名、时间范围快速过滤。这三个需求,正好都是 Rsyslog 的核心能力。
1.2 rsyslog 为什么值得选
当时我其实也对比过其他方案。完整版的日志收集平台往往自带完整的采集链路、索引和检索界面,功能很全,但部署起来也确实重。对于还未成规模的团队,一开始就上全家桶,运维成本可能比日志本身的问题还大。
Rsyslog 的优势在于轻量、稳定、无处不在。绝大多数 Linux 发行版都默认安装并启用了它,系统里的内核日志、认证日志、cron 任务日志本来就是通过它写入本地文件的。我们要做的并不是再引入一个全新组件,而是把原本就存在的日志守护进程配置成“转发”模式。
它性能也不错,原生就支持多线程、TLS 加密、宽松的正则过滤、队列缓冲。官方宣称单台能处理每秒几十万条日志,实际生产环境里我们跑到每秒几万条时 CPU 占用也不高,这对于中小规模的集中式日志场景完全够用了。
还有一个很实际的理由:Rsyslog 的配置是纯文本的,改动之后不用重启守护进程就能reload,线上调整非常方便。不像某些全家桶组件,改个采集规则都要走一遍部署流水线。
1.3 集中式日志系统的整体架构
我最终搭出来的架构很简单,但分工很明确。
- 客户端服务器:只管产生日志,通过 Rsyslog 将系统日志实时转发到日志服务器。
- 日志服务器:开两个端口分别接收 TCP 和 UDP 流量,再通过一套模板将不同来源的日志落盘到各自目录,按日期切分。
- 可视化检索层:日志落到服务器之后,日常排查用
grep和journalctl,后续再接入检索组件做全文索引。
客户端只发,服务端只收,中间不经过任何消息队列。有人可能会问,为什么不加一层消息队列缓冲?对于绝大多数系统日志场景,这个复杂度是不必要的。系统日志本身有本地文件做兜底,就算转发链路短时抖动,也不会导致数据彻底丢失,Rsyslog 内部还有队列机制可以在网络恢复后补发。等日志规模真正大到单机处理不动,再演进也不迟。
2. 动手前要先想明白的三件事
2.1 传输协议:TCP、UDP 还是 RELP
配置日志转发,第一个决策是走哪种传输层协议。我见过不少教程直接让人用 UDP,因为配置最简单。但 UDP 是“发出去就不管”的,网络拥塞或者服务端处理不过来,日志就无声无息地丢了。对安全审计日志来说,这不可接受。
TCP 是默认选择。它可靠、有序,和你浏览网页用的协议一样,底层保证数据不丢不重。代价是如果日志量特别大,TCP 的长连接可能会因为服务端消费不及时而产生背压,但这通常说明服务端配置需要调优,而不是协议的问题。
比 TCP 更进一步的是 RELP(Reliable Event Logging Protocol)。这是 Rsyslog 官方专门为日志可靠传输设计的协议,支持应用层确认和重传,即使连接中断,未确认的日志也会在客户端队列里缓存。代价是服务端需要额外加载模块并启用独立的监听端口。
实际场景里,我的建议是:内网默认用 TCP,跨机房或者专线质量不稳定的时候用 RELP。UDP 可以留着,只用来接收某些网络设备(比如交换机、防火墙)发来的 syslog,因为很多硬件设备根本不支持 TCP 转发。
| 协议 | 可靠性 | 配置复杂度 | 适用场景 |
|---|---|---|---|
| UDP | 低 | 低 | 网络设备上报、允许丢少量日志 |
| TCP | 高 | 中 | 内网服务器日志转发,最常用 |
| RELP | 很高 | 较高 | 跨机房/不稳定链路,日志需严格可靠 |
2.2 要不要上 TLS 加密
这个问题经常被忽略。内部日志一般都包含 IP、用户名、命令记录、业务路径这些敏感信息,如果明文传输,任何能抓到内网报文的人都能直接看到。之前在内网做过一次抓包验证,用tcpdump在服务端抓客户端发来的日志,当时我用的是明文 TCP,抓包文件里直接能看到用户的sudo命令记录和目录访问路径,这放在合规环境里就是安全事故。
TLS 配置并不复杂。服务端生成自签名证书(或者用内部 CA 签发),客户端配置StreamDriver"ptcp"和证书文件,就能建立加密通道。代价是每个客户端都要放一份证书文件,配置量会翻倍。但考虑到日志的敏感性,这步我强烈建议不要省。
也有折中方案:内网如果本身就是隔离网段,没有外部设备接入,很多人确实是明文跑。但哪怕从“以后有可能要过合规检查”这个角度出发,我也建议一开始就配置好 TLS,省得到时候几千台机器回头改配置。
2.3 时间同步这个不起眼的坑
日志集中之后,最让人头疼的一个问题不是「日志丢了」,而是「日志时间对不上」。三台服务器各差三十秒,出事情的时候按时间排序,顺序根本排不出来。
解决方式就是在所有服务器上配置 NTP 时间同步,而且客户端和服务端都要配。一般发行版都自带 chrony 或 systemd-timesyncd,只需要确认服务在运行、指向同一个 NTP 源即可。
另外要强调:Rsyslog 默认用的是系统本地时间。如果服务器时区设得不一样,即使 NTP 同步了,日志里的时间也会各差几个小时。最稳妥的做法是让所有服务器都用 UTC,或者统一改成同一个时区。这个一定要在搭建前就定好规范,后面改时会非常被动。
3. 服务端完整落地步骤
3.1 安装与基础配置
先说明一下环境:日志服务器使用某 Linux 发行版,Rsyslog 版本为 8.x。这个版本自带 mmnormalize 和高级队列配置,在配置上灵活不少。
# 检查版本 rsyslogd -v # 如果未安装 yum install rsyslog -y # CentOS/RHEL 系 apt install rsyslog -y # Debian/Ubuntu 系安装后默认配置在/etc/rsyslog.conf,主配置文件用$IncludeConfig引用了/etc/rsyslog.d/*.conf。我们新增的配置放在独立的文件里,不要去改主配置,方便维护也方便回滚。
然后确保服务端能够接收外部连接。这一步很关键,默认 Rsyslog 只监听本地 socket,不处理网络请求。必须在配置中显式开启网络输入模块。
3.2 目录规划与模板设计
日志服务器上我规划了一套这样的目录结构:
/var/log/remote/ ├── web01/ │ └── 2026-01-15/ ├── db01/ │ └── 2026-01-15/ └── network_devices/ └── 2026-01-15/按主机名分目录,再按日期切分,这样单日日志量可控,排查时也能直接进入对应日期。Rsyslog 实现起来并不需要写脚本,靠模板就能搞定。
这里我单独写了一个模板,用来控制日志文件的落盘路径和命名规则:
template(name="DynFileByHost" type="string" string="/var/log/remote/%hostname%/%timestamps::date-pgsql%.log")这个模板的含义:用日志消息里的主机名作为一级目录,用接收日期作为二级目录加文件名。%hostname%是客户端 Rsyslog 上报时带的标记,%timestamps::date-pgsql%会输出2026-01-15这样的日期格式。
3.3 配置文件完整示例与参数解释
服务端配置我分成了两个文件。第一个是/etc/rsyslog.d/10-server.conf,负责加载模块和开启监听端口:
module(load="imuxsock") module(load="imjournal") module(load="imtcp" MaxSessions="500") module(load="imudp") module(load="builtin:omfile" Template="RSYSLOG_TraditionalFileFormat") input(type="imtcp" port="514") input(type="imudp" port="514")注意:这里我同时开启了 TCP 514 和 UDP 514。上面提过,UDP 主要是给网络设备用的,TCP 才是主力。MaxSessions设置为 500,防止客户端连接数过多把文件描述符耗尽。
第二个是/etc/rsyslog.d/11-server-template.conf,定义动态文件名模板和最终执行动作:
template(name="DynFileByHost" type="string" string="/var/log/remote/%hostname%/%timestamps::date-pgsql%.log") ruleset(name="remoteHandler"){ action(type="omfile" template="DynFileByHost" dirCreateMode="0755" fileCreateMode="0644" queue.type="linkedList" queue.size="50000") }queue.type="linkedList"表示启用内存队列,允许积压 5 万条日志。这样做的好处是,当磁盘写入暂时跟不上时,日志会先排队缓冲,而不是直接丢弃。
主配置里把这套 ruleset 绑定到 TCP 输入端口上。只有网络进来的日志走这个规则,本地系统日志仍然走默认规则,两者互不干扰。
3.4 启动验证与收日志测试
配置完成之后,用以下命令检查语法并重启服务:
rsyslogd -N1 systemctl restart rsyslog ss -lnut | grep 514rsyslogd -N1是语法预检,不通过的话服务不会启动。ss命令确认 514 端口处于监听状态,才能继续下一步测试。
测试客户端那边先手动执行一次日志发送:
logger -n <服务器IP> -T -P 514 "test message from web01"-T表示走 TCP。在服务端查看/var/log/remote/web01/<日期>.log是否生成了对应文件,有的话说明链路是通的。第一次搭建时建议立刻做这个测试,否则后续排查会很难定位是“配置问题”还是“网络问题”。
4. 客户端接入的三种核心方式
4.1 最常用的系统日志订阅转发
客户端配置就简单多了。我在/etc/rsyslog.d/10-forward.conf里写了这样的规则:
module(load="imuxsock") module(load="imjournal") # 让所有本地产生的日志都走远程转发 *.info;mail.none;authpriv.none;cron.none @@<日志服务器IP>:514@@两个@就是走 TCP,一个@是 UDP。这里我排除了邮件、认证和 cron 的日志,这些日志敏感性高但量大,单独留在本地更合适。如果希望把所有日志全量送过去,可以直接用*.* @@<服务器IP>:514,不过后面会收到大量重复噪音。
客户端配置之后同样执行:
rsyslogd -N1 systemctl restart rsyslog然后就看服务端有没有动态生成该客户端的目录文件。这一步基本能验证配置是否生效。
4.2 跨主机转发与自定义模板
默认转发的日志格式是标准 syslog 格式:时间、主机名、程序名、进程号、消息体。这个格式对多数排查够用,但如果想带上更多上下文信息,可以自定义模板。
我在客户端配置过这样一套模板,把所有源 IP、日志 tag、原始内容都带上了:
template(name="LongFormat" type="string" string="%fromhost-ip% %programname% %syslogtag% %msg%\n") *.* @@<日志服务器IP>:514;LongFormat%fromhost-ip%是这台客户端自己的地址,%programname%是产生日志的程序名,%syslogtag%通常包含进程 ID。这样在集中日志里定位问题时,能直接看到是哪个程序、哪个进程报的错,不需要再猜。
4.3 搞定本地文件监控场景
系统日志走的是 imjournal 模块,但有些业务日志并不走 syslog 接口,直接写在自己的文件里,比如某应用的access.log。Rsyslog 可以通过imfile模块监控文件增量并转发。
我配置监控一个业务日志文件的规则:
module(load="imfile") input(type="imfile" File="/data/logs/business/access.log" Tag="business_access" Severity="info" Facility="local7") local7.* @@<日志服务器IP>:514Tag会作为日志来源标识带到服务端,服务端就能按这个 tag 生成独立文件。imfile有个细节:它从文件末尾开始读取,所以配置完成后不会补发历史日志,只会转发新写入的内容。如果你要补数据,得手动把文件复制到服务端,或者调整startMsg.regex配合重新读取。
4.4 过滤器与日志降噪
日志集中之后,噪音问题会特别突出。比如 cron 每分钟的打卡日志、systemd 定期刷新的状态消息、内核的nf_conntrack超时提示,这些日志对故障排查几乎没有帮助,却会挤占磁盘和带宽。
我在客户端加了一条过滤规则,正则匹配到就丢弃,不占用传输带宽:
if ($programname == 'crond') then { if ($msg contains 'pam_unix(cron:session)') then stop }如果想更精细地处理,可以走 Rsyslog 的rainerscript条件语法,支持 ==、contains、startswith、正则匹配等操作符。我一般把过滤规则放在转发动作的前面,这样过滤之后剩余日志再走@@传输,磁盘占用能直接降一半以上。
5. 高概率踩坑的几个问题和排查链路
5.1 客户端上报了但服务端没收到——从网络到配置的完整排查
这是最常遇到的情况,也是最容易让人摸不着头脑的情况。我自己曾在配置完服务端和客户端之后,发现服务端目录一直不更新,折腾了十几分钟才发现问题出在防火墙。
完整的排查链路应该是这样的:
- 先确认客户端本地的 Rsyslog 是否正常生成日志:
tail -f /var/log/messages,如果本地都没有日志,转发配置就没意义。 - 测试网络连通性:客户端上
telnet <服务器IP> 514或者nc -vz <服务器IP> 514。不通就检查防火墙和安全组规则,放行 TCP/UDP 514。 - 确认服务端端口确实在监听:
ss -lnut | grep 514。监听端口没起来,那要先看rsyslogd -N1的语法检查有没有提示。 - 在服务端临时开启调试抓包:
tcpdump -i eth0 port 514 -n,看有没有来自客户端 IP 的流量。如果报文到了但目录没生成,问题出在服务端配置的模板或 ruleset 绑定上。 - 最后检查服务端日志本身:
journalctl -u rsyslog -f,很多 Rsyslog 自身错误会记录在这里。
我之前出过一个问题,就是配置文件里有个细微的拼写错误:dirCreateMode写成了dirCreateMode="0755"但前面的 template 传参错误,导致模板解析不通过,语法预检却通过了,服务没能正常加载 ruleset。这种问题不抓包确实不好定位。
5.2 日志积压导致 rsyslog “罢工”
另一个高频问题是大批量日志涌入时,动作队列处理不过来,导致进程卡顿甚至重启。第一次实跑的时候,我把开发环境里一批日志量巨大的测试服务器的所有日志都转发了过来,结果服务端 CPU 冲到 80%,日志文件写入严重延迟。
排查后发现核心原因是队列配置不够宽。默认动作队列虽然自带,但容量和并发都有限。我在服务端最后调整为:
action(type="omfile" template="DynFileByHost" queue.type="linkedList" queue.size="100000" queue.filename="omfile_remote" queue.maxdiskspace="2g" queue.saveonshutdown="on")这里加了磁盘辅助队列。内存队列满了之后,超出的日志会暂时落盘到队列文件,而不是直接丢弃。queue.saveonshutdown="on"保证 rsyslog 重启时未处理完的日志不会丢。这样调整后,即使短时间日志量激增,整体也能平滑处理,不会再出现进程卡死的情况。
不过要注意,磁盘辅助队列本质是降级方案,不能长期依赖。如果持续出现队列堆积,说明发送端的日志量已经超过服务端的处理能力,这时候要提高日志过滤级别,而不是无限调大队列上限。
5.3 多行日志和特殊字符的问题
有些程序会把完整调用栈换行写入 syslog,比如 Java 的异常堆栈。Rsyslog 默认一行一传,到了服务端,一条完整堆栈会被拆成七八条,而且每一行都加上同样的主机名前缀。排查问题时,原始调用栈很难拼回去。
处理这个问题,我在客户端用了mmnormalize配合regex解析,把换行符替换成占位符再转发,服务端收到后恢复成多行。这个配置并不复杂:
module(load="mmnormalize") action(type="mmnormalize" rule="rule=:%msg:R, .*$%" )其实更准确地说,对于多行日志,我的建议是不要强行依赖 Rsyslog 的 syslog 协议去还原。如果业务程序日志确实需要完整调用栈,应该让程序自己把日志写成结构化 JSON 文件,再结合 Filebeat 这类带多行合并能力的工具采集。Rsyslog 可以处理 90% 左右的系统日志场景,剩下的交给更专业的采集工具,没必要硬刚。
另一个让我印象深刻的坑是日志内容里带有%符号。Rsyslog 模板里%是特殊字符,报文里的百分号如果不处理,模板解析会出问题。我踩过一次之后,遇到业务方日志里有百分号的情况,都会提前让客户端把内容包一层%msg%的转义,或者在过滤阶段就用replace()函数把%替换为%%。
5.4 服务重启后日志丢失
原理上系统日志本来就写本地文件,为什么重启会丢日志?我的理解是:Rsyslog 的转发队列本身是内存态的,默认queue.saveonshutdown为 off。如果客户端在转发过程中重启,内存队列里尚未发出的日志就会全部消失。这个问题主要影响的是大量高频日志,少日志场景几乎察觉不到。
解法是在客户端也加上磁盘队列配置,把内存队列落地到/var/log/rsyslog/queue:
action(type="omfwd" target="<日志服务器IP>" port="514" protocol="tcp" queue.type="linkedList" queue.filename="forward_udpqueue" queue.saveonshutdown="on")加上之后,客户端重启时未发完的日志会先进磁盘队列,启动后重新发送。这样虽然可能在服务端造成几秒钟的重排,但至少不会出现数据丢失。这个配置在低版本 Rsyslog 上写法可能不同,我用的 8.x 是直接支持这种写法的。
6. 从“能收日志”到“好用”的进阶操作
6.1 队列和线程调优
Rsyslog 默认的队列模型已经不错,但按我的经验,有目的性地调两三个参数就能提升明显。
首先要区分两个队列:主消息队列和动作队列。主消息队列在前面用queue.size控制,动作队列则由具体action()里配置。如果日志量很大并且希望充分利用多核,优先设置:
global(queue.type="linkedList" queue.size="100000") main_queue(queue.workerThreads="4" queue.timeoutShutdown="120")queue.workerThreads控制处理线程数。日志量不大时没必要去调,默认 1 个线程就够用;日志量大且是多核 CPU 时,调到 4 个左右能显著提高写盘吞吐。这里有一个经验:线程数不是越多越好,因为会引入锁竞争,4 到 8 个已经是很激进的配置。
其次要注意queue.timeoutEnqueue这个参数。如果队列满了,timeoutEnqueue越小,意味着日志越快被丢弃。线上一般设置成 100 毫秒,让生产环境优先保证“程序不卡”,而不是“日志必须全部收走”。具体取舍要看日志业务重要性,安全审计日志建议设大一些,普通业务日志设小一些。
6.2 日志轮转与空间管理
日志集中之后,最大的问题很快变成“磁盘满了怎么办”。系统日志不比业务日志,有些程序特别能刷,一天几个 GB 是常事。
我在服务端配置了 logrotate 来处理日志文件轮转。在/etc/logrotate.d/rsyslog-remote里写了这样的配置:
/var/log/remote/*/*.log { daily rotate 30 compress delaycompress postrotate /usr/bin/systemctl restart rsyslog 2>/dev/null || true endscript }这样保证:
- 日志每天切割一次,保留 30 天,超过自动删除。
- 切割后的历史日志压缩存放,能省很大空间。
- 每次切割后重启 rsyslog,让它重新打开新的日志文件。不过实际上 Rsyslog 支持
copytruncate模式,我后来把postrotate改成copytruncate,这样不用重启进程就能轮转,不影响日志采集的连续性。
考虑到有些客户端会发送不同日期的时间戳,服务端落盘时用的文件是按接收日期定义的,logrotate 按文件名前缀处理,是没问题的。
6.3 日志平台联动
集中式日志系统只是个底座,真正的价值是“可以检索”。我后面搭建了一套检索组件,把 Rsyslog 收集的日志索引到 Kafka,再由检索组件消费到 Lucene 索引里。Rsyslog 原生支持这种联动:
module(load="omkafka") action(type="omkafka" topic="syslog" broker="<Kafka节点1>:9092,<Kafka节点2>:9092" template="RSYSLOG_SyslogProtocol23Format")如果还没有引入消息队列,Rsyslog 也能直接通过omelasticsearch插件对接 Elasticsearch。我把一套演示环境的日志直接写入过 ES,效果很好,但生产环境我更推荐经过 Kafka 做削峰填谷,防止 ES 写入抖动时反压到日志服务器。
6.4 日志归档与服务安全
最后简单说说归档和服务安全。
日志最终是要归档的。我设计了一个定时任务:每天把 30 天前的日志压缩打包,然后推到对象存储或者备份服务器。归档策略其实和业务强相关,金融、医疗这类行业可能有很硬的保留时长要求,普通业务保留 90 天足够了。
服务安全上有两个小细节值得注意:
一是给 Rsyslog 创建专用的系统用户,不要把整个服务器 root 权限暴露给日志目录。日志文件默认权限0644本身没问题,但当运行日志服务的进程异常时,权限过大容易产生风险。
二是尽量限制 Rsyslog 服务端的监听来源。能通过防火墙规则做到“只允许办公网和内网网段访问 514 端口”的就不要开放到所有地址。Rsyslog 本身没有太多内置的安全防护,它不是一个堡垒机,所有的安全边界应该交给防火墙和网络策略完成。
个人体会是:Rsyslog 这个组件越用越顺手,但它不是那种配置完就能一劳永逸的东西。真正稳定跑起来,靠的是平时对日志格式、队列参数的持续观察和调整。我踩过的坑基本都集中在“减噪”和“防丢”这两件事上,如果你也正打算搭集中式日志,不妨一开始就把这两个问题当成核心来设计,后面会省很多事。