
排障的时候最怕什么最怕日志里什么都查不到。我遇到过好几次一个服务明明挂了/var/log/messages翻到底都找不到对应的报错最后排查了一圈才发现程序根本没走 syslog或者日志级别写得太低被过滤掉了。从那以后我每接手一台 Linux 服务器第一件事就是把 syslog 这套东西彻底理顺。syslog 是 Linux 上最基础的日志框架绝大多数服务内核、网络、cron、认证、以及各类应用都会通过它输出运行状态。学会配置它、用好它等于给你的服务器装了一双看得见问题的眼睛。这篇文章我不会堆砌手册内容只讲我在实际生产环境中用到的东西机制是什么、规则怎么配、远程日志怎么收、日志轮转怎么做、以及那些不出问题你不会发现的坑。1. 先理解机制syslog 不是一个文件而是一条链路很多刚接触 Linux 的人会以为 syslog 就是/var/log/messages这个文件其实不对。syslog 是一条完整的处理链路程序产生日志 → 交给系统日志守护进程 → 守护进程根据规则分发到本地文件/远程服务器/终端。真正干活的是后台守护进程在主流发行版上通常叫 rsyslog也有用 syslog-ng 的早年则是 syslogd。文件只是链路的终点之一。1.1 facility 和 severity认出日志的身份和级别开始配置之前必须先认识两个核心概念facility设施和 severity严重级。它们两个组合在一起决定了这条日志会被送到哪里去。可以把 facility 理解为哪个模块产生了这条日志。常见的 facility 有kern内核产生的日志user用户进程mail邮件系统daemon各种系统守护进程auth/authpriv认证和安全相关authpriv权限更敏感syslogsyslog 自身产生的日志lpr打印系统cron定时任务local0~local7留给用户自定义的 8 个通道severity 则是这条日志有多严重。从高到低排列是emerg0系统不可用通常广播给所有用户alert1必须立即处理crit2严重问题比如硬件故障err3错误warning4警告notice5正常但值得注意info6一般信息debug7调试信息这里有一个很多人会踩的误区数字越小级别越高。配置里写*.info表示所有 info 及以上即数字 6的日志而不是只有 info 这一条更不是info 以下的。我见过有人把规则写成*.info之后抱怨 debug 日志出不来——那是当然的debug 的数字比 info 大默认就被过滤掉了。1.2 从 syslogd 到 rsyslog 再到 systemd-journald搞清楚历史背景有助于理解现在的配置为什么长这样。最早的 syslogd 功能很单一只能往本地文件和远程 UDP 514 端口转发没有可靠的传输也没有过滤能力。后来 rsyslog 成为主流它支持模块化扩展、TLS 加密、队列机制、复杂的模板语法。到现在CentOS/RHEL 系的默认日志守护进程就是 rsyslog而 Debian/Ubuntu 系同样是 rsyslog 配合 systemd-journald 一起工作。Ubuntu 系还有一个特殊性/var/log/syslog和/var/log/auth.log这些传统文件是由 rsyslog 写的但journalctl查的是 systemd-journald 自己采集的数据。也就是说同样的日志事件journald 和 rsyslog 各自存了一份。如果你改了 rsyslog 的配置却用journalctl去验证会发现没生效这并不是配置有问题而是验证工具选错了。排查的时候看一眼日志到底落在哪个文件再决定用哪条命令去查。1.3 配置文件加载顺序改完之后别敲错 reload 命令rsyslog 的主配置在/etc/rsyslog.confDebian/Ubuntu 和 CentOS 的默认内容略有差异。主配置通常会 include/etc/rsyslog.d/目录下的所有.conf文件自定义规则放这个目录是标准做法不要直接改主文件升级时容易被覆盖。加载顺序很重要rsyslog 按字母顺序读取/etc/rsyslog.d/下的文件最后匹配生效是核心逻辑。如果你在50-default.conf里已经写了*.* /var/log/messages又在后面的文件里写了kern.* /var/log/kernel.log那内核日志会同时被写到两个文件里——这不算错但如果你本意是想让内核日志只进 kernel.log那就要注意规则顺序或者用if条件做排除。改完配置后重载命令是systemctl restart rsyslog或systemctl reload rsyslog。这里有个小讲究reload是发 SIGHUP 信号让 rsyslog 重新读配置正在传输的日志可能丢restart会完整重启进程连接会断开重建。高频变更的场景下我更倾向 restart省得出现配置加载了一半的诡异状态。2. 规则配置一条规则的语法决定了日志的流向rsyslog 最核心的就是规则行rules格式是选择器 动作。选择器用来筛选日志动作是筛选通过后的处理方式。掌握了选择器和动作90% 的配置都能看懂。2.1 选择器语法facility.priority 和它的简化写法最基本的规则长这样kern.err /var/log/kernel_error.log意思是内核的错误级别及以上日志写入/var/log/kernel_error.log。facility部分可以写多个用逗号隔开kern,mail.err /var/log/mail_kernel_err.logpriority部分也有修饰符这是最容易搞混的地方.errerr 及更高级别等于数字 3.err仅 err 这一个级别.!err除了 err 之外的所有级别.none这个 facility 的日志一律不收举一个生产环境常用的示例。很多程序会把自身日志打到local0到local7因为这是留给第三方应用自定义的通道。你写了一个脚本在出故障时给 local3 发 alert# 脚本里这样写 logger -p local3.alert database connection failedrsyslog 里就可以这样接住它local3.alert /var/log/myapp/alert.log优先级部分还支持多个级别逗号分隔表示同时收这些级别*.info;mail.none;authpriv.none /var/log/messages这条就是 CentOS 默认配置里的经典写法所有 info 及以上的日志都收但排除 mail 和 authpriv 这两个 facility。为什么要排除因为邮件和认证日志有自己的专属文件避免在 messages 里重复堆叠。2.2 模板template日志写进文件后长什么样很多人配完 rsyslog 发现文件里每行日志格式和我们预想的不一样原因就是模板没设置。模板就是定义我往文件里写的时候写成什么格式。默认模板格式大致是Feb 20 14:30:22 hostname systemd[1]: Started Daily apt upgrade.要是你想把程序的完整标签打出来比如让每条日志带有原始 facility 和 priority方便后面写解析脚本可以自定义模板$template MyFormat,%TIMESTAMP% %HOSTNAME% %syslogtag% %msg% [facility%syslogfacility-text%, severity%syslogseverity-text%]\n然后在规则里指定使用这个模板*.* /var/log/custom.log;MyFormat我先坦白一个建议这个功能在生产环境里我用的很克制因为一旦用了自定义模板后面接日志采集器比如 Loki、ELK时解析规则就得跟着模板走格式变一次解析逻辑改一次。除非团队里明确有人负责维护这套格式否则默认格式就很够用。2.3 完整配置示例按程序分目录落盘下面给一个可以直接抄作业的完整示例目标是把公司内部一个 Java 应用日志走 local1和数据库备份脚本走 local2产生的日志分别落到独立目录其他日志维持默认不动。新建/etc/rsyslog.d/myapp.conf# Java 应用日志每天一个文件保留 14 天 local1.info /var/log/myapp/app.log # 数据库备份脚本日志 local2.info /var/log/myapp/backup.log这个看起来很简单过程中有两个实际问题第一/var/log/myapp目录必须提前建好rsyslog 不会为你创建不存在的目录。如果不建日志会直接丢弃而且 rsyslog 自己会在/var/log/syslog或 messages 里记录tried to write to a nonexistent directory之类的报错。第二日志文件名的日期动态生成在 rsyslog 里要用模板实现并不能简单地写app-%Y%m%d.log。模板写法是在 action 中通过模板实现$template DynFile,/var/log/myapp/app-%$YEAR%%$MONTH%%$DAY%.log local1.info ?DynFile注意这里 action 的地方写的是?模板名而不是文件路径。这个功能生产环境挺常用配合 logrotate 的 daily 轮转可以做到双重保障。但同样文件一旦多了注意不要占满磁盘空间。2.4 验证配置正确性的工具rsyslog 自带语法检查改完配置最好的验证方式不是直接重启服务而是先跑语法检查rsyslogd -N 1这个命令会告诉你配置里有没有语法错误。输出OK就说明加载没问题。然后可以用logger命令手动制造一条测试日志logger -p local1.info this is a test log from logger command接着去对应的日志文件里 tail 一下确认是否落盘。这套语法检查 logger 测试的组合是我每次配完 syslog 必然执行的流程能省掉后面大量排查时间。再补充一个小心得很多程序运行时用到的用户权限很低可能根本没有写/var/log/myapp的权限但由于 rsyslog 守护进程是以 root 运行的所以实际落盘动作是 root 做的app 进程只需要通过 syslog 协议把日志发出去即可。这也是集中收日志的好处之一日志写不写得进文件和业务进程权限无关。3. 远程日志收集从单机到集中管理的实战部署日志一旦散在多台机器上排查问题就会变成灾难。三台机器还能挨个登录三十台呢你不可能每台都开一个终端去 tail。所以我一直建议服务器数量超过五台就应该把远程日志收集做起来。syslog 本身就是干这个的不用额外装 agent一行配置的事。3.1 服务端开启 UDP/TCP 514 接收模式接收端中央日志服务器需要在 rsyslog 配置中开启 imudp 或 imtcp 模块这决定了客户端日志通过什么协议传过来。UDP 模式简单高效但丢包不重传。TCP 模式可靠日志不会丢但占用更多连接资源。我的建议是内网推荐 TCP跨公网传输一定要上 TCP 加密用 imtls 或 stunnelUDP 只适合可以接受少量丢包的网络环境。服务端配置/etc/rsyslog.confmodule(loadimtcp) input(typeimtcp port514)如果还要接收 UDP再加一行module(loadimudp) input(typeimudp port514)CentOS 默认 rsyslog 就已经加载了这两个模块但要用 TCP 常常需要在防火墙放行 514/tcp。防火墙没有放行是远程收日志最常踩的坑后面我会详细说排查链路。默认情况下收到远程日志后 rsyslog 会把它写进本地文件但所有主机混在一起不好管。按主机名分目录是常见做法$template RemoteLogs,/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log *.* ?RemoteLogs这样每台客户端的日志都有了独立目录再配合日志分析平台对接运维效率会明显提升。3.2 客户端一行配置把日志转发出去客户端配置简单很多在/etc/rsyslog.d/remote.conf里写*.* 192.168.1.100:514一个表示 UDP两个表示 TCP*.* 192.168.1.100:514这一行会把所有日志实时转发到中央服务器。这里有一个很实际的纠结点转发之后本机还留不留日志留的好处中央服务器挂了本地还有一手日志可用不留的好处省磁盘日志只存一份。我的建议是留因为磁盘是最便宜的排障资源宁可多占一点空间也不要等中央服务器出问题的时候裸奔。具体做法是留默认的本机写入规则再追加一条转发规则两者并存。需要注意一个细节如果配置了本地写文件又配置了全部转发同一条日志往往两边都会出现用 MQ 思维理解就是同一个消息被两个消费者消费这完全正常。你要是介意可以在转发规则后用stop语句终止匹配。比如让本地只写/var/log/messages其他一律转发*.* /var/log/messages kern.* 192.168.1.100:514 stop stop表示本条匹配的日志不再往下处理这个语法在老的 rsyslog 版本里常见新版可以用stop指令代替。3.3 测试收发的完整流程从客户端到服务端逐个验证配置完成后推荐按这个链路做全链路测试第一步服务端语法检查rsyslogd -N 1第二步服务端检查端口监听状态ss -lntup | grep 514要能看到tcp :514和udp :514的 LISTEN 状态。第三步客户端手动造日志logger -p user.info remote test from $(hostname) logger -p user.err remote error test from $(hostname)第四步服务端查看是否落盘tail -f /var/log/remote/客户端主机名/$(date %F).log注意客户端主机名解析。如果收不到先确认客户端配置里写的是服务端 IP 还是不存在的域名。3.4 时间同步是怎么影响日志排障的远程日志收集最大的副作用是把所有机器的日志堆到一起但它们的系统时间如果不一致日志按时间排序时就会乱掉。排查的顺序也会完全错乱看起来像先发生后续再发生前因。解决方案就是要保证全部机器 NTP 时间同步。在 CentOS 系用 chronyUbuntu 系用 systemd-timesyncd 或 chrony。这个没有什么复杂的确认每个节点/etc/chrony.conf指向同一个时间源然后timedatectl set-ntp true即可。每次部署完 syslog 远程收集我都会顺手检查一遍所有节点的时间偏差超过 1 秒的机器直接告警。时间不对还另有一个麻烦rsyslog 收到日志时默认用的是日志里自带的事件时间即客户端产生日志那一刻的时间如果客户端时钟是错的中央服务器上的文件里那条日志时间也是错的。即便你误以为服务端用了本地接收时间实际上并不是。4. 日志轮转日志不轮转迟早爆盘日志一直在写不轮转再大的磁盘都会被吃满。Linux 上处理这个问题的主角是 logrotate一个几乎不需要额外服务的日志管理工具。它由 cron 定时触发根据配置文件决定哪些日志该轮转、轮转后保留几个文件、要不要压缩。4.1 logrotate 的工作机制谁在什么时候触发它CentOS 系在/etc/cron.daily/logrotate里定义了每日执行Ubuntu 系则由 systemd timer 触发。默认情况下一天跑一次。所谓轮转简单说就是把当前日志改名再让程序重新创建新的空日志文件改名的旧文件按数量或时间保留超出保留策略的就删除或压缩归档。最核心的配置文件是/etc/logrotate.conf里面的全局默认值是几乎所有日志的兜底策略。各软件的轮转规则通常写在/etc/logrotate.d/下面的单独文件里系统日志如/var/log/messages则默认写在/etc/logrotate.d/syslog。一个典型配置长这样/var/log/messages { rotate 4 weekly compress delaycompress missingok notifempty create 0600 root root postrotate /usr/bin/systemctl restart rsyslog /dev/null 21 || true endscript }逐项解释一下rotate 4保留 4 个旧文件第 5 个就会被删weekly每周轮转一次。也可以写daily或size 100M达到 100M 才转compress旧文件用 gzip 压缩注意delaycompress表示这次刚转出的文件先不压等下次轮转再压。为什么要 delaycompress因为有些程序这时还在往旧文件写数据延迟压缩能最大程度保留刚轮转文件的现场数据missingok文件不存在不报错notifempty文件为空就不轮转create 0600 root root轮转后重新创建新文件指定权限和属主postrotate/endscript轮转之后执行的命令。对 rsyslog 来说由于文件名变了需要重启或发信号让它重新打开新文件否则日志还会写进旧文件的 inode导致新文件空空如也这个 postrotate 很有讲究。有些用户配置了轮转但发现轮转后新日志还是写在旧文件里问题就在于没有通知 rsyslog 重新打开文件。对于 rsyslog 来说还可以这样写postrotate /usr/bin/systemctl kill -s HUP rsyslog /dev/null 21 || true endscript和直接 restart 的区别在于kill -s HUP只是发 SIGHUP 让 rsyslog 重新打开日志文件不会中断正在写入的其他文件影响更小。4.2 自定义日志的轮转别忘了专门写规则之前我们在/var/log/myapp/底下放了应用日志这些日志不会自动继承/var/log/messages的轮转策略需要自己写规则。下面是我常用的配置/var/log/myapp/*.log { daily rotate 14 compress delaycompress missingok notifempty copytruncate }这里注意copytruncate的作用先复制一份当前日志然后把原文件清空。对于 rsyslog 管理的日志可以用 HUP 信号配合但如果应用是自己写文件的没有重新打开文件的能力copytruncate就很好用因为它不需要应用配合。我遇到过很多次配置了 logrotate 但日志文件越来越大的情况最后都是因为自定义日志文件的属主不是 root而 logrotate 的 create 又创建不了新文件轮转直接失败。可以先手动执行轮转观察报错logrotate -d /etc/logrotate.d/myapp-d是 debug 模式只演练不实转。确认无误后再执行logrotate -vf /etc/logrotate.d/myapp-f强制轮转。这个验证步骤建议每次写完轮转配置都做一遍。4.3 轮转失败导致日志不更新的排查思路日志文件不更新、磁盘空间异常增长第一步看 logrotate 日志cat /var/log/logrotate.log这个文件记录了每次轮转的执行结果报错一般都在里面。第二步看 SELinux 或 AppArmor 的拒绝记录因为/var/log下新增目录的写权限在强制访问控制下可能被拦截。第三步看定时任务有没有被执行systemctl list-timers | grep logrotate。还有一个隐蔽问题/var/log所在分区如果用了任何特殊挂载参数比如noexec不影响写但nosuid也不影响基本没问题真正影响的是磁盘满了或者 inode 耗尽了df -h和df -i都看一下。很多时候你以为 logrotate 有问题其实是磁盘已经写满了。5. 我的排错链路日志收不到、时间错乱、权限报错最后一个章节我把几个高频故障的完整排查链路写出来。这些坑在网上资料里很少被系统地梳理但生产环境里每天都在发生。5.1 远程日志收不到的逐步定位方法客户端配了转发服务端开了接收但服务端目录里就是没有新日志。这个问题的排查顺序我建议按网络栈从下往上查第一步验证网络连通性。在客户端执行nc -vz 192.168.1.100 514如果 TCP 端口连不上检查服务端防火墙。CentOS 系尤其要确认 firewalld 是否挡了端口firewall-cmd --list-all firewall-cmd --add-port514/tcp --permanent firewall-cmd --reloadUbuntu 系用 ufw同样看 514 端口放行情况。第二步验证 rsyslog 是否在监听ss -lntup | grep 514如果没监听回到服务端配置文件确认 module(loadimtcp) 和 input(typeimtcp port514) 都写对了再注意 rsyslog.conf 是否 include 了你新建的配置文件。有些版本默认 include 的目录是/etc/rsyslog.d/*.conf正常没问题。第三步客户端手动发一条日志服务端用 tcpdump 抓包看能不能收到包tcpdump -i any port 514 -n -XX如果收不到包问题在客户端的路由、防火墙或配置上如果能收到包但没有落盘问题在 rsyslog 的规则解析上。第四步检查服务端AllowSender或类似配置。rsyslog 本身默认接收所有来源的日志但如果你为了安全加了过滤要注意写清楚规则否则别的 IP 的日志被拦掉你还没有直接的报错。我会先临时去掉过滤规则再重测一次确认是不是过滤导致的。5.2 日志时间戳不对的两种场景我在 3.4 节说过时间同步但时间不对还分两种截然不同的情况。第一种情况日志里能看到客户端发来的原始时间戳是错的。这说明客户端当时确实时间没同步上。没什么好说的修客户端时钟。但注意一点如果客户端已经把错误时间写进日志并转发到服务端服务端这边是没有办法纠正这条日志的时间的。想靠服务端加时区偏移硬改只会让问题更乱。第二种情况更隐蔽rsyslog 模板时间戳用的是服务器本地时区如果你把所有机器的时区设置不一致日志汇总后在你眼里就是乱的。统一时区这件事经常被忽略。我一般在部署规范里明确要求所有服务器使用 UTC 或同一时区然后 rsyslog 模板里统一用%timegenerated%本地接收时间 %timereported%日志自带时间排障时先看这两者的差异能快速判断是客户端时间问题还是服务端解析问题。5.3 队列积压与网络闪断下的日志丢失远程转发用的是 TCP 时最怕网络闪断。rsyslog 在配置里可以设置队列和超时参数例如module(loadimtcp MaxSessions1000) action(typeomfwd target192.168.1.100 port514 protocoltcp queue.typelinkedlist queue.size10000)这告诉 rsyslog 当远程服务器不可达时先把日志暂存在本地内存队列里队列满再转换到磁盘队列等网络恢复后继续发送。这批配置的意义在于用有限的磁盘空间换取日志不丢。我踩过一个具体场景中央服务器为了升级重启客户端机器批量积压了十几万条日志队列上限太小导致大量日志被直接丢弃。调大队列并设置queue.discardmark之后同样场景下丢日志的比例明显下降。但也要知道队列越大启动时占用的资源越多不能盲目往大了配。按单条日志约 200 字节估算1 万条大概 2MB可以先设定 queue.size 为 50000并根据实际内存情况调整。5.4 权限问题日志文件权限为什么老被重置rsyslog 落盘时会遵守$FileOwner、$FileGroup和$FileCreateMode指令。默认情况下创建的文件通常是 root 用户、600 权限。如果你希望运维组的用户也能直接 tail 日志可以配置$FileOwner root $FileGroup adm $FileCreateMode 0640改了之后注意这个指令只对新建文件生效已有的文件权限不会被刷新。这会导致一种很正常的困惑——配置改了老文件权限没变就以为没生效。处理方法简单粗暴删除旧文件让下次写入重新创建或者手动chown/chmod一次性处理。还有一个常见问题日志轮转后新文件的属主和权限是按默认配置创建的如果和程序预期的不同比如程序想直接写自己的日志文件却无权限程序就可能在内部报错。处理方式是给该日志单独写 logrotate 规则并在规则里显式指定create 0640 user group保证每次轮转后文件权限都一致。6. 一个贴近生产的综合配置把日志按天分目录并压缩归档单点配置讲完给一个可以直接组合使用的综合示例。假设你的需求是把所有本地和远程日志按天存储保留 30 天日志超过 100M 当天立即轮转远程日志和本地日志分离存放。服务端/etc/rsyslog.d/remote.confmodule(loadimtcp) input(typeimtcp port514) $template RemoteByDay,/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log :source, startswith, 192.168. ?RemoteByDay注意这里我用:source, startswith, 192.168.做条件匹配只有内网 IP 的日志才走远程模板避免把 rsyslog 本机日志也写进去。这个语法可能在部分旧版本上不支持可以用if $fromhost-ip startswith 192.168. then替代。本地/etc/rsyslog.d/local.conf$template LocalByDay,/var/log/local/%$YEAR%-%$MONTH%-%$DAY%.log *.* ?LocalByDay对应 logrotate 配置/etc/logrotate.d/syslog-remote/var/log/remote/*/*.log { daily rotate 30 compress delaycompress missingok notifempty dateext postrotate /usr/bin/systemctl kill -s HUP rsyslog /dev/null 21 || true endscript }dateext会在旧文件后面加上日期后缀比如log-20250220而不是默认的log.1对后面的归类和清理都比较友好。但要注意如果你已经在 rsyslog 模板里按天命名了文件logrotate 的dateext又加一遍日期会造成文件名和日期重复搞清逻辑再配。这套配置跑下来日志运维会轻松很多。每台机器、每天一份压缩好的日志想查什么直接按日期戳进对应目录不用在 hundreds of 文件里 grep 到怀疑人生。