Skip to content

11|日志管理

服务起不来、接口报 500、登录不了——出问题第一反应是看日志。但日志在哪?systemd 管的服务日志在 journald 里,传统系统日志在 /var/log/messages,Nginx 的访问日志在自己文件里。位置不同、看的方式也不同。

本篇讲清楚 Linux 上日志分几类、各自在哪看,以及怎么用 logrotate 防止日志把磁盘撑爆。

Linux 上的日志来源主要分三类:

来源内容查看方式
systemd journaldsystemd 管理的服务输出、内核消息、系统日志journalctl
rsyslog传统系统日志(/var/log/messages/var/log/secure 等)文本工具(catgrepless
应用文件日志应用自己写入的文件(如 /var/log/nginx/access.log文本工具

一、传统日志文件位置

绝大多数系统和服务的日志集中在 /var/log 下,但不同发行版的文件命名不一致,排查前需要先确认目标机器属于哪个系列:

文件发行版内容
/var/log/messagesRHEL 系通用系统日志
/var/log/syslogDebian / Ubuntu通用系统日志
/var/log/secureRHEL 系认证与安全相关(SSH 登录、sudo)
/var/log/auth.logDebian / Ubuntu认证与安全相关
/var/log/dmesg通用内核启动时的日志快照
/var/log/cron部分发行版cron 执行日志
/var/log/yum.log/var/log/dnf.logRHEL 系包管理操作日志
/var/log/apt/history.logDebian / Ubuntuapt 操作日志

按问题类型快速定位:

  • 登录失败、SSH 异常、sudo 失败 — 查 /var/log/secure/var/log/auth.log
  • 磁盘错误、网卡 down、内存 OOM — 查 dmesg
  • 服务异常退出 — 查 journalctl -u <服务名>
  • 定时任务未执行 — 查 /var/log/cron(或 journalctl -u cron)

二、journald 与 journalctl

systemd 自带的日志系统称为 journald,它收集以下来源的日志,统一存储在二进制格式的日志文件中:

  • systemd 管理的服务的 stdout / stderr
  • 内核消息(等同于 dmesg 来源)
  • 系统启动日志
  • syslog 兼容日志

由于存储格式不是纯文本,不能直接 cat,必须通过 journalctl 查询。

journalctl 常用参数

bash
journalctl                            # 查看全部日志(通常非常多,不直接这么用)
journalctl -u nginx                   # 只看指定 unit 的日志
journalctl -u nginx -f                # 持续跟踪(类似 tail -f)
journalctl -u nginx -n 100            # 最近 100 行
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-06-23 10:00" --until "2026-06-23 11:00"
journalctl -p err                     # 只看 error 级别及以上
journalctl -p warning -p err          # 只看 warning 和 error
journalctl -k                         # 内核日志(等同于 dmesg)
journalctl --no-pager                 # 不分页,适合脚本处理或重定向

日志级别

-p 参数支持的日志级别(从严重到低):

数字名称含义
0emerg系统不可用
1alert必须立即处理
2crit严重故障
3err错误
4warning警告
5notice注意
6info信息
7debug调试

-p err 等同于 -p 3,会返回 0-3 级别的日志(即 emerg、alert、crit、err)。

服务启动失败的标准排查路径

服务启动失败时,以下两条命令应当形成肌肉记忆:

bash
# 第 1 步:看当前状态和最后几行日志
systemctl status app --no-pager

# 第 2 步:看最近 100 行完整日志
journalctl -u app -n 100 --no-pager

从最近的日志开始,按需要逐步扩大时间范围。不要一上来就翻全量日志——一个运行了半年的服务,journal 中可能积累 GB 级日志,从头翻完全无法实现。

journald 的持久化陷阱

默认情况下,如果 /var/log/journal 目录不存在,journald 仅将日志存储在 /run/log/journal(基于 tmpfs,实际在内存中),重启后日志全部丢失

排查"重启前的日志找不到"这类问题时,根本原因通常就是这个。

开启持久化:

bash
# 创建持久化目录
mkdir -p /var/log/journal

# 重启 journald 让它感知到新目录
systemctl restart systemd-journald

# 验证
journalctl --disk-usage      # 应该显示在 /var/log/journal 下使用了多少空间

或在配置文件中显式开启:

bash
# /etc/systemd/journald.conf
[Journal]
Storage=persistent           # auto(默认)| volatile(内存)| persistent(磁盘)| none
SystemMaxUse=2G              # 限制总占用空间
SystemMaxFileSize=50M        # 单个日志文件大小
MaxRetentionSec=1month       # 最多保留多久

修改后重启 journald:

bash
systemctl restart systemd-journald

journald 磁盘空间管理

journald 默认会占用磁盘可用空间的 10%,可能在某些情况下占用较多空间:

bash
# 查看 journal 当前磁盘占用
journalctl --disk-usage

# 清理 30 天前的日志
journalctl --vacuum-time=30d

# 限制最大保留 1GB
journalctl --vacuum-size=1G

三、rsyslog

rsyslog 是传统的系统日志服务,负责按规则把不同来源的日志写入对应的文本文件(如 /var/log/messages/var/log/secure)。

journald 与 rsyslog 在现代系统中并存——journald 负责收集,然后转发给 rsyslog 归档为文本文件,两套日志系统都在运行。journald 的日志便于通过 journalctl 查询,rsyslog 写入的文本文件便于用传统的 grep/awk 工具处理,也便于被日志采集器(filebeat、promtail)读取。

配置文件

路径用途
/etc/rsyslog.conf主配置
/etc/rsyslog.d/*.conf附加配置

修改后需要重启:

bash
systemctl restart rsyslog

规则格式

rsyslog 的规则格式是 facility.level 目标:

conf
*.info;mail.none;authpriv.none;cron.none    /var/log/messages
authpriv.*                                  /var/log/secure
cron.*                                      /var/log/cron
*.emerg                                     :omusrmsg:*
字段含义
facility日志来源类别:authpriv(认证)、cron(定时任务)、mail(邮件)、daemon(守护进程)、kern(内核)、local0-7(自定义)
level记录的最低等级(与 journald 的级别相同)
目标日志写入的文件路径,或转发到远程

例如 authpriv.* 表示"认证相关的所有级别日志写入 /var/log/secure",*.info;mail.none 表示"所有 info 及以上级别日志写入 /var/log/messages,但邮件相关的不要"。

应用日志通常不需要动 rsyslog

普通应用服务不需要修改 rsyslog 配置——直接写自己的日志文件即可。只有以下场景才会修改 rsyslog:

  • 实现集中日志收集(将日志转发到远程日志服务器)
  • 自定义日志路径或日志格式
  • 调整系统日志的过滤规则

修改前务必备份,rsyslog 配置错误可能导致日志完全不被记录,排查异常时会陷入"什么日志都没有"的被动局面。

四、dmesg:内核日志

dmesg 查看内核环形缓冲区中的消息,内容包括:

  • 系统启动时的硬件探测
  • 驱动加载与卸载
  • 网络接口状态变化(link up/down)
  • 磁盘 I/O 错误
  • OOM killer 杀进程的记录
  • 内核 panic / oops 信息

排查硬件层、内核层问题时,dmesg 是第一选择

常用命令

bash
dmesg                        # 输出所有内核消息(秒数时间戳)
dmesg -T                     # 时间戳显示为人类可读格式
dmesg -T | tail -n 50        # 最后 50 行
dmesg -T -l err              # 只看 error 级别
dmesg -w                     # 持续监听新消息(类似 tail -f)

异常信息过滤

bash
dmesg -T | grep -i -E "error|fail|oom|reset"

这条命令快速捕获 dmesg 中的异常信号——磁盘 I/O 错误、网卡 link down、OOM 杀进程、设备复位等。

OOM 事件的交叉验证

dmesg 中可能出现 OOM killer 的记录:

[2026-06-23T10:25:30] Out of memory: Killed process 1234 (java) total-vm:8533124kB, anon-rss:6291456kB
[2026-06-23T10:25:30] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,...

发现 OOM 事件后,可以与服务日志做时间线交叉验证:

bash
# 在同一时间窗口查看服务日志
journalctl -u myapp.service --since "2026-06-23 10:25:00" --until "2026-06-23 10:26:00"

如果服务日志显示在同一时刻进程异常退出,基本可以确认是系统 OOM 杀掉了进程。这种交叉验证能避免误判——单看应用日志可能以为是应用自己 crash,看了 dmesg 才知道是内核主动杀的。

五、logrotate:日志轮转

日志文件如果不做管理会持续增长,最终撑爆磁盘。logrotate 解决的就是这个问题——它定期把旧日志改名、压缩、删除,保持日志目录的大小在受控范围内。

注意:logrotate 不会减少日志量本身,只是做轮转管理。如果应用日志写得非常频繁,需要先在应用层调整日志级别。

配置文件位置

路径用途
/etc/logrotate.conf全局配置
/etc/logrotate.d/*各服务的独立配置

logrotate 由 cron 触发(/etc/cron.daily/logrotate),每天检查一次。

典型轮转配置

logrotate
/var/log/myapp/*.log {
    daily                    # 每天轮转一次
    rotate 7                 # 保留最近 7 份
    missingok                # 文件不存在不报错
    notifempty               # 空文件不轮转
    compress                 # 压缩旧日志
    delaycompress            # 延迟压缩(保留最近一份不压缩,方便正在查的内容)
    copytruncate             # 复制后清空原文件
}

关键指令说明:

指令含义
daily / weekly / monthly轮转频率
size 100M按大小轮转(达到 100M 触发)
rotate N保留 N 份历史
missingok文件不存在不报错
notifempty空文件不轮转
compress压缩旧日志(默认 gzip)
delaycompress延迟一轮压缩,最近一份保持未压缩
dateext文件名使用日期后缀,而不是数字
copytruncate复制内容后清空原文件
postrotate / endscript轮转后执行脚本
create 644 user group创建新文件并指定权限

copytruncate 与 postrotate 的选择

轮转日志时,如何处理"正在写入的进程"是关键问题。两种方案:

方案 1:copytruncate

logrotate
copytruncate

工作原理:

  1. logrotate 把当前日志文件的内容复制到 xxx.log.1
  2. 清空原 xxx.log 文件(truncate 到 0 字节)
  3. 应用进程仍持有原 inode,继续向"已清空"的文件写入

优点:不需要应用支持任何信号或 reload。 缺点:复制和清空之间的瞬间,应用可能写入的少量日志会丢失。

方案 2:postrotate + reload 信号

logrotate
/var/log/nginx/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    postrotate
        systemctl reload nginx >/dev/null 2>&1 || true
    endscript
}

工作原理:

  1. logrotate 把 xxx.log 改名为 xxx.log.1
  2. 执行 postrotate 中的命令,通知应用重新打开日志文件
  3. 应用 reload 后打开新的同名日志文件 xxx.log,旧文件 inode 已不再被持有

优点:无任何日志丢失。 缺点:需要应用支持 reload(SIGHUP)信号触发重新打开日志文件。

支持 reload 的服务(Nginx、Apache、HAProxy 等)优先使用 postrotate,比 copytruncate 更可靠

测试 logrotate 配置

修改 logrotate 配置后,先用 -d 模拟运行验证,不要直接生效:

bash
# 第 1 步:debug 模式,只模拟不执行
logrotate -d /etc/logrotate.d/nginx

# 第 2 步:确认输出符合预期后,强制执行一次
logrotate -f /etc/logrotate.d/nginx

# 验证:看是否生成了 .1 后缀的归档文件
ls -lh /var/log/nginx/

postrotate 和删除动作的配置必须先模拟——logrotate 配置错误可能导致误删日志,且不可恢复。

常见错误配置

错误 1:同一日志文件被多个 logrotate 配置覆盖

bash
grep -l "/var/log/nginx" /etc/logrotate.d/*
# 如果输出多个文件,说明有重复定义,会导致轮转行为不确定

错误 2:文件权限错误,logrotate 无法操作

bash
# 确认 logrotate 可写日志目录
ls -ld /var/log/myapp

错误 3:compressnocompress 同时存在(后者覆盖前者)

六、日志排查的基本方法

按时间范围定位

时间范围过滤是定位故障最有效的方式——故障一定发生在某个时间点附近:

bash
# journald
journalctl --since "2026-06-23 10:00:00" --until "2026-06-23 11:00:00"

# 文本日志
awk '/2026-06-23 10:00:00/,/2026-06-23 11:00:00/' app.log
sed -n '/2026-06-23 10:00/,/2026-06-23 11:00/p' app.log

按关键字过滤

bash
grep -i "error" app.log
grep -i -E "error|exception|failed|timeout" app.log
grep -i -B 5 -A 10 "OutOfMemoryError" app.log    # 错误前后 5/10 行的上下文

关注日志增长速度

日志的突然暴涨通常是异常信号——某种异常被频繁触发,可能是应用 bug 或外部攻击:

bash
ls -lh /var/log/nginx/access.log    # 看单个文件大小
du -sh /var/log/*                   # 看各日志目录占用
tail -f /var/log/nginx/access.log   # 直接观察日志写入速度

日志分区独立挂载

一个重要的运维实践:将 /var/log 单独挂载在独立的分区或逻辑卷上

这样做的价值在于:当应用异常时,可能在短时间内狂写日志(例如某个错误循环每秒产生上千条日志),如果 /var/log 与根分区(/)在同一文件系统:

  • 日志写满 /var/log 同时也写满了根分区
  • 根分区写满后,sshd 等关键服务无法写入临时文件,可能影响登录
  • 系统整体陷入半瘫痪状态,排查动作受阻

/var/log 独立挂载后,即便日志写满,最多是日志分区被占满,根分区仍有空间,系统其他功能正常运作,登录、查日志、定位问题都不受影响。配置方式:

bash
# 假设新加一块盘 /dev/sdb1 用作日志分区
mkfs.xfs /dev/sdb1

# 添加到 fstab
echo "UUID=$(blkid -s UUID -o value /dev/sdb1) /var/log xfs defaults 0 0" >> /etc/fstab

# 迁移现有日志(需要短暂停服)
systemctl stop rsyslog systemd-journald
mv /var/log /var/log.bak
mkdir /var/log
mount /var/log
cp -a /var/log.bak/* /var/log/
systemctl start rsyslog systemd-journald

物理服务器、关键业务节点强烈建议这样规划。云主机通过云盘扩容更便利,但仍然推荐分区独立。