暗色模式

Linux rsyslog 日志服务:从 facility 规则分流到远程日志归集

技术教程
2026-09-19
8
0
本文要点
  • Ubuntu 上日志是两段接力:应用写 /dev/log(已被 journald 接管,实际指向 /run/systemd/journal/dev-log),journald 再通过 /run/systemd/journal/syslog 这个 socket 转给 rsyslog,最后由 rsyslog 按规则写进 /var/log/*
  • 规则的核心语法只有 facility.severity 动作 一行:auth,authpriv.* /var/log/auth.log 就是「认证类日志,任意级别,写进 auth.log」;; 拼接多个条件,none 表示排除
  • & stop截断开关:命中上一条规则的消息执行完动作后不再往下走。实测 local0 加规则前 grep -c 在 syslog 里是 1,加规则后是 0,日志被完整改道
  • 规则生效顺序 = /etc/rsyslog.d/*.conf文件名字典序(本机 20 → 21 → 48 → 49 → 50),想抢在默认规则前拦下日志,文件名编号就要更小
  • 开远程接收端必须配 ruleset:把 input 收到的消息绑到独立规则集,否则「收进来的日志」会再次命中转发规则,自己和自己在网络上打转
  • ⚠️ 改配置前先跑 rsyslogd -N1 校验语法;本机的单元文件没有 ExecReload,实测 systemctl reload rsyslogJob type reload is not applicable,只能 restart
  • ⚠️ UDP(@)无连接无重传,跨网络会丢;重要日志用 TCP(@@)。514 端口不要暴露在公网——UDP 源地址可以伪造,等于给人一个反射放大器

Linux rsyslog 日志服务:从 facility 规则分流到远程日志归集

很多人对 Linux 日志的认知停在两步:出事了 journalctl -u xxx 查一下,或者 tail -f /var/log/syslog 看实时输出。但中间那一层——日志是被谁决定写进哪个文件的——常常是个黑盒:为什么认证日志去了 auth.log、内核日志去了 kern.log、而其他所有东西挤在 syslog 里?为什么有些日志查得到 journal 却在 /var/log 里找不到?

答案在 rsyslog。它是那台按 facility(设施)和 severity(级别)把日志分拣到不同目的地的分拣机,也是把日志从一台机器送到另一台机器的那双手。本文在一台 Ubuntu 22.04 云服务器(KVM,1 核、内存 1.9G,内核 5.15.0-30-generic)上把它的常用能力跑了一遍:读默认路由表、写自定义规则做定向落盘、开一个 UDP 接收端把日志转出去。文中输出的时间戳、条数、监听状态都是真实执行结果。

先搞清谁在管日志:journald 与 rsyslog 的分工

「systemd 时代还需要 rsyslog 吗」是个常见疑问。实际这台机器上两者是接力关系,各管一段:

应用/服务 → /dev/log → journald → /run/systemd/journal/syslog → rsyslogd → /var/log/*
             (已被 journald 接管)      (ForwardToSyslog)        (imuxsock)

这条链路的每一环都能在机器上查证:

ls -l /dev/log
systemctl cat syslog.socket | grep ListenDatagram
grep ForwardToSyslog /etc/systemd/journald.conf
grep -n 'module(load="imuxsock")' /etc/rsyslog.conf
lrwxrwxrwx 1 root root 28 Sep  9 13:56 /dev/log -> /run/systemd/journal/dev-log
ListenDatagram=/run/systemd/journal/syslog
#ForwardToSyslog=yes
13:module(load="imuxsock") # provides support for local system logging

也就是说:/dev/log 这个传统 syslog 接口现在归 journald 所有,应用照旧往里写,但数据先落到 journal;ForwardToSyslog=yes(注释掉即为默认值)让 journald 再把它转发到 syslog.socket 持有的 /run/systemd/journal/syslog;rsyslog 的 imuxsock 模块从那个 socket 读,按 /etc/rsyslog.d/*.conf 的规则落盘。

这个分工解释了两个日常现象:

  • journalctl 查得到的,/var/log 里不一定有——rsyslog 的规则可能压根没把它写进文件(比如很多服务日志默认只进 journal);
  • rsyslog 挂了,日志也不会彻底丢——journald 仍在采集,journalctl 照样能查(只是 /var/log/* 会停更)。

第一步:先看清现状,再动配置

不要上来就改配置。先看服务状态和默认路由表:

systemctl status rsyslog --no-pager | head -5
grep -vE '^\s*#|^\s*$' /etc/rsyslog.d/50-default.conf

systemctl status 显示 rsyslog 处于 active (running) 且由 syslog.socket 触发,下面是 50-default.conf 的六条有效规则

TriggeredBy: ● syslog.socket 这一行是前面那条接力链的直接证据——rsyslog 是被 socket 拉起来的。

下面六行就是这台机器的全部默认路由,逐行读一遍,rsyslog 的核心语法也就掌握了:

auth,authpriv.*            /var/log/auth.log
*.*;auth,authpriv.none        -/var/log/syslog
kern.*                -/var/log/kern.log
mail.*                -/var/log/mail.log
mail.err            /var/log/mail.err
*.emerg                :omusrmsg:*
  • auth,authpriv.* / kern.* / mail.*设施,设施.级别 是选择器。* 在级别位置表示「所有级别」,多个设施用逗号并列。所以认证、内核、邮件各有各的文件。
  • *.*;auth,authpriv.none; 用来并列/覆盖条件。前半句是「所有设施所有级别」,auth,authpriv.none 表示「但从这两个设施里排除(none)」。一行就完成了「除认证外的全部日志进 syslog」——这是 auth.logsyslog 不重复记录的关键。
  • - 前缀-/var/log/syslog):异步写。不等待 fsync 就返回,日志量大时能显著降低写入阻塞;代价是机器掉电时最后几条可能丢。auth.log 没有 -,属于同步写。
  • *.emerg :omusrmsg:*:级别是 emerg(最高级别)时,动作不是写文件而是 :omusrmsg:*——给所有登录用户发消息。这就是紧急日志会突然出现在你终端上的原因。

selector 之外:属性过滤器与 & stop

除了 设施.级别,rsyslog 还有一类按内容匹配的写法,本机的另两个配置文件用的就是它:

grep -vE '^\s*#|^\s*$' /etc/rsyslog.d/20-ufw.conf /etc/rsyslog.d/21-cloudinit.conf
/etc/rsyslog.d/20-ufw.conf::msg,contains,"[UFW " /var/log/ufw.log
/etc/rsyslog.d/21-cloudinit.conf::syslogtag, isequal, "[CLOUDINIT]" /var/log/cloud-init.log
/etc/rsyslog.d/21-cloudinit.conf:& stop
  • :msg,contains,"[UFW ":只要消息正文里含 [UFW 就命中,不管它是什么设施(防火墙日志的设施并不固定,按内容抓才可靠);
  • :syslogtag, isequal, "[CLOUDINIT]":按 tag 精确匹配;
  • & stop& 代表「上一条规则」,stop 是动作——命中并写完文件后,这条消息到此为止,不再参与后续任何规则。云初始化的日志因此不会再去污染 syslog

& stop 是整个 rsyslog 配置里最值得记住的一招:没有它,你的自定义规则只是「多写一份」;有了它,才是「改道」。

第二步:写一条规则,把 local0 单独落盘

local0~local7 是留给本地自定义应用使用的设施(logger -p local0.xxx 就能往里写),最适合做验证。规则文件放 /etc/rsyslog.d/编号决定顺序——这里用 49-,排在 50-default.conf 前面:

logger -p local0.notice 'demo: 规则生效前'
sleep 1
grep -c 'demo: 规则生效前' /var/log/syslog
cat > /etc/rsyslog.d/49-demo.conf <<'CONF'
local0.*    /var/log/demo-local0.log
& stop
CONF
systemctl restart rsyslog
logger -p local0.notice 'demo: 规则生效后'
sleep 1
grep -c 'demo: 规则生效后' /var/log/syslog
cat /var/log/demo-local0.log

规则生效前该消息在 syslog 里命中 1 条,加上 local0 规则与 & stop 后命中 0 条,消息只出现在新建的 demo-local0.log 里

输出里三行各是一句话:

  • 1:规则生效前,local0 的消息按默认规则 *.*;auth,authpriv.none 进了 /var/log/syslog
  • 0:规则生效后,同一条消息在 syslog一条都查不到——被 & stop 拦住了;
  • 最后一行:它完整地落在了新文件里,格式是 Sep 18 21:05:09 MFY001832765701 root: demo: 规则生效后(时间、主机名、tag、正文),说明主机名和 tag 都是 rsyslog 自动补的。

两条实操提醒:

  • sleep 1 不是凑数:syslog 用的是异步写(前面那个 - 前缀),logger 返回时数据可能还没落盘,紧接着 grep 会偶发查不到;
  • logger -p local0.notice-p设施.级别,也可以用 -t myapp 指定 tag,让日志在文件里更好认。

第三步:开一个 UDP 接收端,把日志转出去

多机环境下最常见的需求是「把几台机器的日志汇到一台」。接收端要显式加载 imudp 模块并开一个 input,同时用 ruleset 给它绑定一套独立规则:

cat > /etc/rsyslog.d/48-remote.conf <<'CONF'
module(load="imudp")
input(type="imudp" port="5140" address="127.0.0.1" ruleset="remote")
ruleset(name="remote") {
    action(type="omfile" file="/var/log/demo-remote.log")
}
local0.* @127.0.0.1:5140
CONF
systemctl restart rsyslog
logger -p local0.notice 'demo: 远程归集测试'
sleep 2
cat /var/log/demo-remote.log
ss -lunp | grep 5140

rsyslog 加载 imudp 监听 127.0.0.1:5140,本地转发的日志经 UDP 回到本机并落入 demo-remote.log,ss 显示 rsyslogd 持有该端口

这段演示把「发送端 + 接收端」压在了同一台机器上(127.0.0.1 收自己),但走的是真实的 UDP 网络路径,所以链路是完整的:

  • module(load="imudp") + input(type="imudp" ...):加载 UDP 输入模块并监听端口。默认配置文件里这两行是注释掉的(/etc/rsyslog.conf 第 17~22 行),要用得自己开;
  • address="127.0.0.1":只监听本机回环地址。这次演示刻意不监听 0.0.0.0——514/5140 这类端口暴露到公网等于开放一个日志注入点(UDP 源地址可伪造),内网归集时才绑定内网网卡;
  • ruleset="remote":把输入收到的消息交给名为 remote 的规则集处理,而这个规则集里只有「写文件」一个动作。这一步是防环的关键:如果不绑 ruleset,收进来的日志会继续走默认规则集,又命中 local0.* @127.0.0.1:5140 那条转发规则,自己把自己再发一遍,形成网络上打转的环路;
  • local0.* @127.0.0.1:5140@ 是 UDP 转发,@@ 是 TCP 转发。TCP 有连接和重传,跨公网或对日志完整性要求高时应该用 @@
  • ss -lunp | grep 5140:证明 rsyslogd 真的在监听——排障时先看这一行,比翻日志快得多。

一篇日志同时走了两条路(本地落盘 + UDP 转发)也顺带说明了一件事:没有 & stop 的规则只是「多写一份」,不是「改道」。这正是前一步和这一步的区别。

改配置的安全姿势与几个坑

改之前先校验语法。 rsyslogd -N1 会把配置完整解析一遍然后退出,不启动服务:

rsyslogd -N1
rsyslogd: version 8.2112.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: End of config validation run. Bye.

配置写错时它会直接指出行号。这一步不能省——rsyslog 起不来的时候,/var/log/* 会整体停更,排查起来比改配置本身麻烦得多。

这台机器上 systemctl reload 用不了。 实测:

systemctl reload rsyslog; echo "rc=$?"
Failed to reload rsyslog.service: Job type reload is not applicable for unit rsyslog.service.
rc=3

原因是 rsyslog.service 里没有定义 ExecReloadsystemctl cat rsyslog.service | grep -c ExecReload 返回 0)。所以改完配置只能 systemctl restart rsyslog。重启是秒级的,且 journald 侧采集不受影响——万一服务起不来,还能用 journalctl 把日志捞回来(journalctl 系统日志:从启动日志查询到按服务与时间过滤排查)。

顺序就是文件名顺序。 /etc/rsyslog.conf 里那行 $IncludeConfig /etc/rsyslog.d/*.conf 展开时按字典序,所以本机是 20-ufw21-cloudinit48-remote49-demo50-default。想抢在默认规则前拦下某类日志,把文件编号改小即可;反过来,如果你的规则总是不生效,先检查是不是被更靠前的 & stop 截断了。

别同时开 imuxsockimjournal 本机 /etc/rsyslog.conf 只加载了 imuxsock(外加收内核日志的 imklog)。网上不少教程会让你再加载 imjournal 直接读 journal——两个输入同时启用时,同一批日志会被采集两次,落盘结果是每条日志重复一遍。选一条链路就够了。

留意 $RepeatedMsgReduction on 这是本机默认开启的选项:短时间内重复出现的相同消息会被折叠成一行 last message repeated N times。它不是丢日志,但用脚本按行统计时会数不准,需要精确计数时可以关掉它。

日志文件本身会撑爆磁盘。 rsyslog 只管写,不管清理,轮转交给 Linux logrotate 日志轮转:从轮转策略到防止磁盘写满;真写满了,救火流程见 Linux 磁盘空间排查与清理:从 df 到 journald 的完整救火流程

收尾:把演示配置撤干净。 加过的接收端会一直占着端口,测试完记得删除配置文件并重启:

rm -f /etc/rsyslog.d/48-remote.conf /etc/rsyslog.d/49-demo.conf
rm -f /var/log/demo-remote.log /var/log/demo-local0.log
systemctl restart rsyslog

小结

把 rsyslog 的用法压成几条:

  • 链路:应用 → /dev/log(journald)→ ForwardToSyslogsyslog.socket → rsyslog imuxsock/var/log/*journalctl 查的是前半段,/var/log 是后半段的产物;
  • 规则设施.级别 动作 是骨架,, 并列设施、; 拼接条件、none 排除、- 异步写;
  • 改道:想让某类日志「只去新地方」,规则末尾必须跟 & stop,否则只是多写一份;
  • 顺序:规则按文件名排序执行,编号决定谁先谁后;
  • 归集:接收端 module(load="imudp") + input(... ruleset="xxx"),转发端 @(UDP)或 @@(TCP),并务必用 ruleset 把收到的日志关进独立规则集;
  • 安全:改配置先 rsyslogd -N1,本机只能 restart 不能 reload,514 端口永远不要暴露到公网。

最后一句实话:rsyslog 的价值不在于「能把日志写下来」,而在于「能决定日志去哪」。默认配置能跑,是因为它替你把常见日志分了类;一旦你要做集中归集、按业务分流、或者把审计日志单独隔离出来(配合 auditd 安全审计),就绕不开它的规则语法。好在核心只有一行 selector 加一个 & stop

发表评论

暂无评论,快来抢沙发吧!