2.7 日志与故障排查

预计阅读时间:17 分钟

📖 目录

学习目标

学完本章后,你将能够:

  • 理解 Linux 日志体系(syslog 与 journald)的工作原理
  • 熟练使用 journalctl 过滤和查询系统日志
  • 配置 logrotate 管理日志轮转与归档
  • 运用结构化方法论排查常见系统故障
  • 使用 dmesgstracelsof 等工具做深度诊断

核心知识

  • 系统日志(System Log)——操作系统和应用程序记录的事件消息,用于监控、审计和故障排查
  • syslog 协议——标准日志协议(RFC 5424),定义日志的设施(facility)和级别(severity),传统上由 rsyslog 或 syslog-ng 实现
  • journald——systemd 的日志守护进程,将日志以二进制格式集中存储在 /var/log/journal/
  • 日志级别(Log Level)——从 emerg(0,紧急)到 debug(7,调试)的 8 级严重程度分级
  • 日志轮转(Log Rotation)——通过 logrotate 按大小/时间切割、压缩、删除旧日志文件
  • 内核环缓冲区(Kernel Ring Buffer)——内核维护的环形日志缓冲区,用 dmesg 查看
  • 故障排查方法论——复现→隔离→假设→验证→记录的结构化问题解决流程

知识关联

原理讲解

📝 为什么 Linux 要设计 journald 和 rsyslog 双系统?

rsyslog 从 1980 年代的 syslog 演化而来,是 Unix 日志的事实标准——几乎所有应用(Nginx、MySQL、OpenSSH)都往 /var/log/ 写文本日志。journald 是 2010 年随 systemd 引入的新一代日志系统。两者共存而非替代,是因为各自解决不同的问题:rsyslog 的优势是兼容性——几十年积累的工具链(logrotate、grep、awk)和运维经验都基于文本日志;journald 的优势是结构化——自动收集进程的 stdout/stderr、内核消息、审计事件,提供按时间/服务/优先级/PID 的高效过滤。在 Ubuntu/Debian 中,journald 是"收集者"(所有日志先进 journald),rsyslog 是"分发者"(从 journald 读取后写入传统文本文件)。这种分工让新旧工具都能正常工作。

📝 日志轮转如何防止磁盘被撑满?

日志文件如果不管,会无限制增长——一个高流量 Web 服务器的 access.log 每天可能增长数 GB。logrotate 的工作原理:① 由 cron 或 systemd timer 每天(或每周)触发一次;② 读取配置文件(如 /etc/logrotate.d/nginx),判断是否满足轮转条件(时间周期或文件大小);③ 将当前日志文件重命名(如 access.log → access.log-20260730);④ 发送 USR1 信号给应用进程(如 nginx),让它重新打开日志文件(新文件句柄指向新文件);⑤ 按配置压缩旧日志、删除超过保留份数的最旧归档。关键细节:delaycompress 让上一次轮转的文件延迟到下一次才压缩——因为应用可能还在往那个文件写入(通过旧的文件描述符)。

📝 日志级别的设计逻辑

8 个日志级别(emerg 到 debug)不是随意划分的,每个级别对应不同的操作响应:emerg/alert 需要立即人工干预(系统可能不可用);crit/err 需要当天处理(服务功能受损);warning 需要关注但不紧急(潜在风险);notice/info 是正常的运行记录(用于审计和回溯);debug 只在排查问题时临时开启(正常运行时关闭以减少开销)。日志级别的数值设计(0-7 越小越严重)让过滤变得高效——journalctl -p err 只返回 err(3) 及以上级别(数值 ≤ 3),一条命令就能从海量日志中提取真正需要关注的错误。

Linux 日志架构的两条路径

现代 Linux 系统运行两套日志系统:journaldrsyslog,它们各自独立工作又相互配合。

特性journaldrsyslog
存储格式二进制(结构化、带元数据)纯文本(每行一条记录)
默认路径/var/log/journal//var/log/syslog, auth.log
查询工具journalctlgrep, less, tail
优势结构化字段过滤、自动收集元数据人类可读、传统工具兼容
持久化默认仅在 /run/log/journal(内存),需创建目录才持久始终写入磁盘文件

在 Ubuntu/Debian 等发行版中,journald 收集所有日志,rsyslog 作为前端从 journald 读取并写入传统文本文件。两个系统共存,互为补充。

日志级别详解

每条日志消息都有一个严重级别,从 0(最严重)到 7(最不严重):

级别数值含义典型场景
emerg0系统不可用内核 panic、系统崩溃
alert1必须立即处理数据库损坏、磁盘即将写满
crit2临界状态硬件错误、文件系统故障
err3错误服务启动失败、配置错误
warning4警告磁盘使用超 80%、重试操作
notice5正常但重要服务重启、配置重载
info6信息请求到达、连接建立
debug7调试开发阶段详细追踪

日志轮转原理

日志文件若不管理会无限制增长,最终占满磁盘。logrotate 通过定时任务(通常由 cron 或 systemd timer 触发)自动处理:按配置条件(文件大小或时间周期)切割当前日志,压缩旧日志,保留指定份数,删除最旧的归档。

故障排查方法论

高效的故障排查不是碰运气,而是工程化的流程:复现问题 → 收集信息 → 提出假设 → 验证假设 → 实施修复 → 确认解决 → 记录归档。每一次只改变一个变量、记录所有操作,是避免引入新问题的关键。

journald 持久化与配额机制

journald 默认把日志写入内存目录 /run/log/journal/(tmpfs),容量受 RuntimeMaxUse 限制,重启即丢失。创建 /var/log/journal/ 目录后 journald 自动切换为持久化模式,将日志以二进制 journal 文件写入磁盘。两种模式由 Storage= 指令显式控制:persistent 优先写磁盘(目录不存在则退回内存)、volatile 强制只用内存、auto(默认)由目录是否存在决定。持久化模式下仍要设置 SystemMaxUse 等配额——否则日志会悄悄吃掉整个磁盘分区。

配置项默认值作用
Storage=auto日志存储模式(persistent/volatile/auto/none)
SystemMaxUse=磁盘的 10%持久化日志的总磁盘上限
SystemKeepFree=磁盘的 15%为其他数据保留的最小空间
SystemMaxFileSize=无限制单个 journal 文件大小上限(达到即滚动新文件)
RuntimeMaxUse=内存的 10%内存模式下的容量上限
MaxRetentionSec=无限制日志最长保留时间
Compress=yes压缩旧日志(默认 xz)

配额不是"硬上限"——日志达到 SystemMaxUse 后 journald 会立即删除最旧的日志来维持配额,所以误以为"有配额就安全"的人可能丢失关键日志。合理组合:SystemMaxUse=1G + MaxRetentionSec=30day,先到者触发清理。

示例代码

📝 与 2.5:时间管理与日志 的分工 2.5:时间管理与日志「时间管理与日志」已经系统讲过 journalctllogrotate 的基础用法。本章不再重复概念讲解,直接从故障排查实战角度过一遍核心命令,然后深入持久化配置、rsyslog 规则和完整排查案例。

1. journalctl 基础用法

# 查看最近 30 条日志
journalctl -n 30

# 实时跟踪新日志(类似 tail -f)
journalctl -f

# 查看今天的日志
journalctl --since today

# 查看指定时间范围
journalctl --since "2026-07-28 09:00" --until "2026-07-28 18:00"

# 查看本次启动的日志
journalctl -b

# 查看上次启动的日志(用于排查重启前崩溃)
journalctl -b -1

# 查看指定服务的日志
journalctl -u nginx.service

# 只看错误及以上级别的日志
journalctl -p err -b

# 查看内核日志
journalctl -k

# 查看日志占用磁盘空间
journalctl --disk-usage

# 以 JSON 格式输出(带完整元数据)
journalctl -u ssh.service -o json-pretty | head -20

2. logrotate 配置

# /etc/logrotate.d/nginx — Nginx 日志轮转规则
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

# 手动测试轮转配置
sudo logrotate -d /etc/logrotate.d/nginx

# 强制手动执行一次
sudo logrotate -f /etc/logrotate.d/nginx

3. dmesg 查看内核日志

# 查看所有内核消息
dmesg

# 实时跟踪内核消息
dmesg -w

# 查看最后 10 条内核消息
dmesg | tail -10

# 只看错误和警告
dmesg --level=err,warn

# 清空内核日志缓冲区(慎用,排故时别做)
# dmesg -c

# 人类可读时间戳
dmesg -T

# 查看磁盘相关消息
dmesg | grep -i "sda\|nvme"

4. 深度排查工具

# 查看进程打开的文件描述符
lsof -p 1234

# 查看端口监听状态
ss -tlnp

# 跟踪系统调用(排查启动失败)
strace -f -e trace=open,openat,stat,read,write -p 1234

# 跟踪特定进程的写操作
strace -e trace=write -p $(pgrep -x mysqld) 2>&1 | head -20

# 系统活动报告(需安装 sysstat)
sar -u 1 3    # CPU 使用率
sar -r 1 3    # 内存使用率
sar -b 1 3    # 磁盘 I/O

# 分析启动耗时
systemd-analyze blame | head -10

5. 结构化排查案例:Nginx 502

# 1. 确认现象
curl -I http://localhost:8080
# 输出: HTTP/1.1 502 Bad Gateway

# 2. 检查 Nginx 日志
sudo tail -50 /var/log/nginx/error.log
# 输出: connect() failed (111: Connection refused)

# 3. 检查后端服务
systemctl status php8.1-fpm
# 输出: Process: 1234 ExecStart=... (code=exited, status=0/SUCCESS)

# 4. 看 journald 中 php-fpm 的日志
journalctl -u php8.1-fpm -n 20 --no-pager
# 发现: php-fpm listen 的 socket 文件被删除

# 5. 修复并验证
sudo systemctl restart php8.1-fpm
curl -I http://localhost:8080
# 输出: HTTP/1.1 200 OK

6. journalctl 过滤组合实战

# 只看 nginx 服务的错误级别日志(本次启动)
journalctl -u nginx.service -p err -b --no-pager

# 指定时间段内 SSH 的认证日志
journalctl -u ssh.service --since "2026-07-29 00:00" --until "2026-07-30 00:00" -p notice

# 时间可用相对表达式:过去 2 小时、昨天、上个月
journalctl -u php8.1-fpm --since "2 hours ago"
journalctl --since yesterday --until today

# 组合 -u 与 -g(正则过滤消息内容)
journalctl -u nginx.service -g "upstream|worker_connections"

# 只看某个 PID 的日志(跟踪崩溃进程)
journalctl _PID=1234

# 只看某个用户的会话日志
journalctl _UID=1000 -n 20

# 排除某服务:先 -u 过滤再管道反选
journalctl -u nginx.service --no-pager | grep -v "healthcheck"

# 导出为文本交给其他工具分析
journalctl -u nginx.service -b > /tmp/nginx.log

7. journald 持久化与磁盘配额

# 开启持久化:创建目录后重启 journald 即可
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald

# /etc/systemd/journald.conf — 生产推荐配置
[Journal]
Storage=persistent            # auto|persistent|volatile|none
Compress=yes                  # 压缩旧日志
SystemMaxUse=1G               # 日志最多占 1G
SystemKeepFree=2G             # 为其他数据保留至少 2G
SystemMaxFileSize=128M        # 单个 journal 文件上限
MaxRetentionSec=30day         # 最多保留 30 天
RuntimeMaxUse=100M            # 内存模式(/run)上限

# 修改后生效
sudo systemctl restart systemd-journald

# 按大小 / 时间 / 文件数清理
journalctl --vacuum-size=500M
journalctl --vacuum-time=7d
journalctl --vacuum-files=10

8. rsyslog 配置与 facility/priority

rsyslog 用"facility.priority"两级模式匹配日志来源,配置规则形如 facility.priority 目标文件。facility 标识日志来自哪个子系统:

facility数值来源
kern0内核
user1用户进程
mail2邮件系统
daemon3各种守护进程
auth / authpriv4 / 10认证与安全(sudo、sshd)
cron9计划任务
local0 ~ local716 ~ 23留给自定义应用
# /etc/rsyslog.d/50-default.conf 片段 — 规则写在 selector 中
# facility.priority 目标文件
auth,authpriv.*                 /var/log/auth.log
*.*;auth,authpriv.none          /var/log/syslog
cron.*                          /var/log/cron.log

# 自定义规则:local5 的 info 及以上写入独立文件
# 应用里用 logger -p local5.info 写入
local5.*                        /var/log/myapp.log

# 只收 err 及以上级别(priority 数值越小越严重)
*.err                           /var/log/err.log

# 排除某些 facility
mail.none;news.none             /var/log/messages

# rsyslog 会执行所有匹配的规则("首个匹配即停止"是 syslog-ng 的语义),顺序仍需注意
sudo systemctl restart rsyslog

# 用 logger 测试自定义 facility
logger -p local5.info "test message from application"
tail -1 /var/log/myapp.log

9. logrotate 配置字段详解

# /etc/logrotate.d/mysql — 完整字段演示
/var/log/mysql/*.log {
    weekly                        # 轮转周期:daily|weekly|monthly
    rotate 12                     # 保留 12 份归档
    compress                      # gzip 压缩旧日志
    delaycompress                 # 延迟到下次轮转才压缩(配合应用重写)
    missingok                     # 文件缺失不报错
    notifempty                    # 空文件不轮转
    minsize 100M                  # 同时满足大小才轮转(与周期取或)
    maxsize 500M                  # 达到大小立即轮转(不等周期)
    maxage 90                     # 超过 90 天的归档删除
    dateext                       # 归档名带日期:mysql.log-20260730
    dateformat -%Y%m%d            # 日期格式
    create 640 mysql mysql        # 轮转后按权限重建空文件
    sharedscripts                 # 所有日志轮转完后只跑一次脚本
    prerotate
        test -d /backup || exit 0 # 轮转前检查
    endscript
    postrotate
        [ -f /var/run/mysqld/mysqld.pid ] \
            && kill -USR1 $(cat /var/run/mysqld/mysqld.pid)
    endscript
}

# 查看哪些文件将被轮转(不执行)
sudo logrotate -d /etc/logrotate.conf

# 强制执行所有规则
sudo logrotate -f /etc/logrotate.conf

# 查看最近一次轮转记录
cat /var/lib/logrotate/status | grep mysql

10. 日志分析实战:三类典型场景

# 场景一:定位数据库慢查询
# 慢查询日志 + journald 交叉验证
grep -i "slow" /var/log/mysql/mysql-slow.log | tail -20
journalctl -u mysql -g "Query_time" --since "1 hour ago"

# 场景二:检测 SSH 登录爆破
# auth.log 中同一 IP 大量 Failed password 即爆破特征
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' \
    | sort | uniq -c | sort -rn | head -10

# 失败超过 5 次的 IP 直接封禁(nftables 联动)
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' \
    | sort | uniq -c | awk '$1 > 5 {print $2}' \
    | xargs -I{} nft add rule inet filter input ip saddr {} drop

# 场景三:磁盘写满的前兆
# 大量 ENOSPC / "No space left" 出现时磁盘往往已经满了,
# 更好的做法是提前监控日志增长率
journalctl --disk-usage
du -sh /var/log/* 2>/dev/null | sort -rh | head -10

# 间隔 10 分钟对比两次大小,揪出异常增长的日志
du -b /var/log/app/*.log | sort -rn | head -5
sleep 600
du -b /var/log/app/*.log | sort -rn | head -5

11. 日志安全与集中收集

# 日志防篡改:journald 的 FSS(Forward Secure Sealing)前向安全密封
# 每 15 分钟生成一个密封种子,修改历史日志后校验会失败
sudo journalctl --setup-keys --interval=15m
sudo systemctl restart systemd-journald

# 验证日志完整性
journalctl --verify

# 关键日志文件设 append-only(连 root 也不能覆盖,只允许追加)
sudo chattr +a /var/log/auth.log
# 解锁:sudo chattr -a /var/log/auth.log(轮转前需要)

# 集中收集:rsyslog 作为接收端
# 服务端 /etc/rsyslog.conf 开启 TCP 514 监听
# module(load="imtcp")
# input(type="imtcp" port="514")
# template(name="remote" type="string" string="/var/log/central/%HOSTNAME%.log")
# *.*  ?remote

# 客户端 /etc/rsyslog.d/forward.conf
# *.*  @@central-server:514   # @@ 表示 TCP,@ 是 UDP

# 远程日志与本地分离:被攻破的机器无法清除远端证据

常见错误

错误表现根因正确做法
journalctl: command not found系统未使用 systemd(例如旧版 Ubuntu 或容器镜像)确认系统是否使用 systemd:ps -p 1 -o comm=;非 systemd 系统直接读 /var/log/ 文本文件
journalctl 无输出,但系统运行正常journald 日志存储在内存中且已滚动覆盖(/run/log/journal 容量有限)创建持久化日志目录:sudo mkdir -p /var/log/journal && sudo systemctl restart systemd-journald
logrotate 未按预期轮转配置语法错误或缺失 postrotate 脚本先用 sudo logrotate -d 做 dry-run 验证,确认配置路径和时间条件正确
磁盘空间被日志占满日志轮转保留过多或未启用压缩设置 rotate 上限(通常 7-30 天),启用 compress,可用 journalctl --vacuum-size=500M 立即清理
排查时乱改配置导致问题扩大未记录原始状态,一次改了多个变量排查前备份配置(cp /etc/nginx/nginx.conf{,.bak}),每次只改一处并验证效果

最佳实践

实践原理示例
启用 journald 持久化保留重启前后的日志,便于排查启动失败sudo mkdir -p /var/log/journal + 重启服务
日志限容防止日志写满磁盘导致系统异常journalctl --vacuum-size=500MSystemMaxUse=500M 写入 /etc/systemd/journald.conf
统一时间戳跨服务器关联日志时避免时区混乱服务器统一使用 UTC 时间,应用日志写入前转成 UTC
结构化的排查流程减少人为疏漏,便于协作和追溯按"现象→日志→隔离→假设→验证→修复→记录"七步走
生产环境保留 30 天日志满足合规要求同时不过度占用磁盘rotate 30 + compress + delaycompress
logger 在自己的脚本中写日志统一纳入系统日志体系,便于集中管理logger -t my_script -p user.info "backup completed"

练习题

  1. (概念)Linux 日志系统中 err 级别的数值是多少?它与 warning 哪个更严重?
  2. (概念)journalctl -b -1journalctl -b 的区别是什么?什么场景下需要用前者?
  3. (实操)使用 journalctl 查看 SSH 服务 (ssh.service) 最近 10 条认证失败的日志记录。命令应包含服务名过滤、最近条数限制和错误级别过滤。
  4. (实操)为 /var/log/myapp/*.log 编写一个 logrotate 配置:每天轮转、保留 7 份、压缩旧日志、轮转后执行 systemctl reload myapp。先用 logrotate -d 验证语法。
  5. (🔍 挑战)模拟一个服务启动失败场景:在一个容器或虚拟机中故意破坏 Nginx 配置(如改错 listen 端口),然后按结构化排查流程记录完整的排查过程(现象→日志→根因→修复→验证)。
点击查看答案
  1. (概念)err 级别的数值是 3。err(3)比 warning(4)更严重——数值越小越严重。
  2. (概念)journalctl -b 查看当前这次启动的日志;journalctl -b -1 查看上次启动的日志。当系统重启后需要排查"上次关机前发生了什么故障"时使用后者。
  3. (实操)journalctl -u ssh.service -n 10 -p err。输出应显示 SSH 服务最近 10 条级别为 err 的日志。常见误区:未用 -p err 过滤会导致结果包含 info 级别消息,难以聚焦错误信息。
  4. (实操)配置示例:/etc/logrotate.d/myapp 文件中写入 /var/log/myapp/*.log { daily rotate 7 compress postrotate systemctl reload myapp endscript }。使用 logrotate -d /etc/logrotate.d/myapp 进行 dry-run 验证,注意 -d 不会实际执行轮转,但会输出详细的调试信息。常见错误:postrotate 脚本路径错误或忘记 endscript 关键字。
  5. (🔍 挑战)步骤:① 修改 Nginx 配置中 listen 80listen 99999;② nginx -t 会报错;③ journalctl -u nginx -p err 查看具体错误信息;④ 修正配置;⑤ nginx -t && systemctl reload nginx 验证。关键知识点:配置修改前先备份,-t 测试不要跳过,reload 而非 restart 保证不中断连接。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释日志级别(emerg 到 debug)的分级逻辑尝试向他人讲解
命令操作能不查文档使用 journalctl 按时间范围、服务名、优先级查询日志在终端实际执行
原理掌握能说出 logrotate 日志轮转的延迟压缩(delaycompress)机制画出轮转流程图
故障排查能独立使用 strace 和 lsof 诊断进程级问题模拟故障并排查
最佳实践能说明为什么故障排查应该遵循结构化方法论而不是盲目试错对比不同排查方式

本章总结

日志是系统管理员的眼睛。现代 Linux 通过 journald + rsyslog 双通道收集日志:journald 提供结构化二进制存储和强大的 journalctl 过滤能力,rsyslog 维持传统文本文件兼容性。logrotate 保证日志文件不会无限制增长。故障排查的核心是结构化方法论——复现、隔离、假设、验证,而非盲目试错。

速查表

命令/配置用途
journalctl -u <service> -n 50 -p err查看某服务最近 50 条错误日志
journalctl -b -1查看上次启动的日志(排查重启前崩溃)
journalctl --since "1 hour ago"查看过去 1 小时的日志
journalctl -f实时跟踪新日志
journalctl -k查看内核日志
journalctl --vacuum-size=500M将日志占用压缩到 500MB 以内
dmesg -T以可读时间显示内核消息
logrotate -d /etc/logrotate.d/nginx测试 logrotate 配置(dry-run)
strace -p <PID>跟踪进程的系统调用
lsof -p <PID>列出进程打开的文件描述符

学习路径建议

延伸阅读

常见问题

journalctl 日志占满磁盘怎么办?
journald 默认限制日志占用不超过根分区 10% 或 4GB。可以手动清理:journalctl --vacuum-size=500M(保留最近 500MB),或 journalctl --vacuum-time=7d(保留 7 天)。在 /etc/systemd/journald.conf 中设置 SystemMaxUse=500M 持久限制。
logrotate 不生效怎么排查?
先手动测试:logrotate -vf /etc/logrotate.d/nginx 看看是否报错。检查日志文件所有权是否匹配配置中的 create 指令。常见问题:日志文件所有者错误导致 logrotate 无法创建新日志,或 postrotate 脚本中 reload/restart 命令路径不正确。
dmesg 日志有红色错误但系统正常?
dmesg 中的很多"错误"其实是内核的正常提示。例如 USB 设备断开时会有 usb disconnect 消息,某些驱动加载时会输出 ACPI Error 但自动修复。关注真正的问题:I/O error(磁盘故障)、Out of memory(OOM-Killer)、soft lockup(CPU 问题)。
↑ 回到顶部