2.7 日志与故障排查
预计阅读时间:17 分钟
📖 目录
学习目标
学完本章后,你将能够:
- 理解 Linux 日志体系(syslog 与 journald)的工作原理
- 熟练使用
journalctl过滤和查询系统日志 - 配置
logrotate管理日志轮转与归档 - 运用结构化方法论排查常见系统故障
- 使用
dmesg、strace、lsof等工具做深度诊断
核心知识
- 系统日志(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查看 - 故障排查方法论——复现→隔离→假设→验证→记录的结构化问题解决流程
知识关联
- 前置知识:熟悉 1.9:重定向与管道 管道与重定向、2.4:进程管理 进程管理
- 后续影响:日志分析是 4.8:集中式日志管理 集中式日志管理和 4.14:运维故障案例集 运维故障案例集的基础
- 配套技术:
systemd服务管理(3.3:实战:搭建个人网站)、strace系统调用跟踪 - 相关章节:2.5:时间管理与日志 的时间管理和 journalctl 基础是本章的前置;4.6:Linux 性能调优 的日志管理与轮转是 logrotate 的深入
原理讲解
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 系统运行两套日志系统:journald 和 rsyslog,它们各自独立工作又相互配合。
| 特性 | journald | rsyslog |
|---|---|---|
| 存储格式 | 二进制(结构化、带元数据) | 纯文本(每行一条记录) |
| 默认路径 | /var/log/journal/ | /var/log/syslog, auth.log 等 |
| 查询工具 | journalctl | grep, less, tail |
| 优势 | 结构化字段过滤、自动收集元数据 | 人类可读、传统工具兼容 |
| 持久化 | 默认仅在 /run/log/journal(内存),需创建目录才持久 | 始终写入磁盘文件 |
在 Ubuntu/Debian 等发行版中,journald 收集所有日志,rsyslog 作为前端从 journald 读取并写入传统文本文件。两个系统共存,互为补充。
日志级别详解
每条日志消息都有一个严重级别,从 0(最严重)到 7(最不严重):
| 级别 | 数值 | 含义 | 典型场景 |
|---|---|---|---|
emerg | 0 | 系统不可用 | 内核 panic、系统崩溃 |
alert | 1 | 必须立即处理 | 数据库损坏、磁盘即将写满 |
crit | 2 | 临界状态 | 硬件错误、文件系统故障 |
err | 3 | 错误 | 服务启动失败、配置错误 |
warning | 4 | 警告 | 磁盘使用超 80%、重试操作 |
notice | 5 | 正常但重要 | 服务重启、配置重载 |
info | 6 | 信息 | 请求到达、连接建立 |
debug | 7 | 调试 | 开发阶段详细追踪 |
日志轮转原理
日志文件若不管理会无限制增长,最终占满磁盘。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,先到者触发清理。
示例代码
journalctl 与 logrotate 的基础用法。本章不再重复概念讲解,直接从故障排查实战角度过一遍核心命令,然后深入持久化配置、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 | 数值 | 来源 |
|---|---|---|
kern | 0 | 内核 |
user | 1 | 用户进程 |
mail | 2 | 邮件系统 |
daemon | 3 | 各种守护进程 |
auth / authpriv | 4 / 10 | 认证与安全(sudo、sshd) |
cron | 9 | 计划任务 |
local0 ~ local7 | 16 ~ 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=500M 或 SystemMaxUse=500M 写入 /etc/systemd/journald.conf |
| 统一时间戳 | 跨服务器关联日志时避免时区混乱 | 服务器统一使用 UTC 时间,应用日志写入前转成 UTC |
| 结构化的排查流程 | 减少人为疏漏,便于协作和追溯 | 按"现象→日志→隔离→假设→验证→修复→记录"七步走 |
| 生产环境保留 30 天日志 | 满足合规要求同时不过度占用磁盘 | rotate 30 + compress + delaycompress |
用 logger 在自己的脚本中写日志 | 统一纳入系统日志体系,便于集中管理 | logger -t my_script -p user.info "backup completed" |
练习题
- (概念)Linux 日志系统中
err级别的数值是多少?它与warning哪个更严重? - (概念)
journalctl -b -1和journalctl -b的区别是什么?什么场景下需要用前者? - (实操)使用
journalctl查看 SSH 服务 (ssh.service) 最近 10 条认证失败的日志记录。命令应包含服务名过滤、最近条数限制和错误级别过滤。 - (实操)为
/var/log/myapp/*.log编写一个logrotate配置:每天轮转、保留 7 份、压缩旧日志、轮转后执行systemctl reload myapp。先用logrotate -d验证语法。 - (🔍 挑战)模拟一个服务启动失败场景:在一个容器或虚拟机中故意破坏 Nginx 配置(如改错
listen端口),然后按结构化排查流程记录完整的排查过程(现象→日志→根因→修复→验证)。
点击查看答案
- (概念)
err级别的数值是 3。err(3)比warning(4)更严重——数值越小越严重。 - (概念)
journalctl -b查看当前这次启动的日志;journalctl -b -1查看上次启动的日志。当系统重启后需要排查"上次关机前发生了什么故障"时使用后者。 - (实操)
journalctl -u ssh.service -n 10 -p err。输出应显示 SSH 服务最近 10 条级别为 err 的日志。常见误区:未用-p err过滤会导致结果包含 info 级别消息,难以聚焦错误信息。 - (实操)配置示例:
/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关键字。 - (🔍 挑战)步骤:① 修改 Nginx 配置中
listen 80为listen 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> | 列出进程打开的文件描述符 |
学习路径建议
- 学完本章后,建议继续学习 4.8:集中式日志管理 集中式日志管理(ELK/Loki 等日志平台)
- 进阶阅读 4.14:运维故障案例集 运维故障案例集,学习真实排故案例
- 安全审计方向可读 5.13:auditd 审计 auditd 审计系统
延伸阅读
- journalctl 官方手册
- logrotate 手册
- dmesg 手册
- 推荐书籍:《鸟哥的 Linux 私房菜——服务器架设篇》故障排查章节
- 社区资源:ServerFault 系统管理员问答社区