重启后崩溃日志全没了:一次 Linux journalctl 日志排查复盘

半夜服务崩溃,同事重启后复盘,journalctl 翻不到崩溃前的日志。排查梳理:journald 与 /var/log 两套体系、journalctl 按服务/级别/时间/开机精确查询、Storage=auto 为何让日志只活在内存、用 Storage=persistent 持久化、--vacuum 清理与 SystemMaxUse 限容,以及一套日志排查纪律。

2024 年一台服务器半夜服务崩溃,值班同事重启后服务恢复了。第二天我想复盘——崩溃前到底发生了什么?我登上机器敲 journalctl -u myapp,想翻昨晚的日志,结果只看到重启之后的几行,昨晚崩溃前那段,一行都没有。我以为是自己命令写错了,换了好几种写法,日志就是不出来。后来才搞明白:这台机器的 systemd 日志,默认是存在内存里的——机器一重启,内存清空,昨晚那段最关键的崩溃现场,跟着关机一起灰飞烟灭了。我们居然在不知不觉中,跑了一台"日志会随重启蒸发"的服务器。这件事逼着我把 journalctl 和 systemd 日志这套东西彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,systemd 管理的自研服务
事故现象:
- 半夜服务崩溃,同事重启,服务恢复
- 次日复盘,journalctl 翻不到崩溃【前】的日志
- 只看得到重启【之后】的日志

现场排查:
# 1. 看 journal 的存储情况
$ journalctl --disk-usage
Archived and active journals take up 0B in the file system.
# ★ 第一个异样:磁盘上 journal 占用是 0B
#   —— 说明 journal 根本没往磁盘上写

# 2. 看 journal 数据到底存在哪
$ ls /var/log/journal/
ls: cannot access /var/log/journal/: No such file or directory
$ ls /run/log/journal/
abc123.../                 # ★ journal 实际存在 /run 下面
# /run 是 tmpfs —— 内存文件系统!重启即清空

# 3. 确认存储模式
$ cat /etc/systemd/journald.conf | grep -i storage
#Storage=auto               # 是注释掉的默认值 auto

根因(后来想清楚的):
journald 的 Storage 默认是 auto:
  - 如果 /var/log/journal/ 目录【存在】 -> 日志写磁盘(持久)
  - 如果该目录【不存在】 -> 日志只写 /run(内存,volatile)
我们这台机器从来没建过 /var/log/journal/ 目录,
于是 journal 一直只写在内存里。
机器一重启,/run 的 tmpfs 被清空,
昨晚崩溃前的全部日志,随之消失。
日志没"丢",是它从一开始就没被【持久化】保存。

修复 1:journald 是什么,和 /var/log 的区别

# === 传统日志 vs systemd 日志,两套体系并存 ===
# 传统:各服务自己往 /var/log/ 下写文本文件
#   /var/log/messages  /var/log/secure  /var/log/nginx/...
#   —— 纯文本,用 cat/grep/tail 看
# systemd:journald 统一收集,存成【二进制】结构化日志
#   —— 必须用 journalctl 来查,不能直接 cat

# === journald 收集了哪些来源的日志 ===
# - 所有 systemd 服务的标准输出/标准错误
# - 内核日志(等价于 dmesg)
# - 系统启动早期的日志
# - 通过 syslog 接口写来的日志
# 一句话:journald 是 systemd 系统的"日志总线"。

# === 二进制存储带来的好处 ===
# 每条日志不只有文本,还自带结构化字段:
#   _SYSTEMD_UNIT(哪个服务)、_PID、_UID、PRIORITY(级别)、
#   _BOOT_ID(哪次开机)、时间戳……
# 所以 journalctl 能按服务、按级别、按时间、按本次开机
# 精确地筛 —— 这是 grep 一堆文本文件做不到的。

# === 看 journald 服务本身 ===
$ systemctl status systemd-journald

# === 最基础的一条:看全部日志 ===
$ journalctl                  # 从最早到最新,全部(用 less 翻页)
$ journalctl -e               # 直接跳到【最末尾】(最新的)
$ journalctl -n 50            # 只看最近 50 行
$ journalctl -r               # 倒序(最新的在最上面)
$ journalctl --no-pager       # 不用分页器,直接全部输出(脚本里用)

修复 2:journalctl 的基本查询

# === 按服务(unit)筛 —— 排查最常用 ===
$ journalctl -u myapp                  # 只看 myapp 这个服务的日志
$ journalctl -u myapp -u nginx         # 同时看多个服务
$ journalctl -u myapp -n 100           # myapp 最近 100 行
$ journalctl -u myapp -f               # ★ 实时跟踪(等价 tail -f)

# === 按"本次/某次开机"筛 ===
$ journalctl -b                        # 只看【本次开机】以来的日志
$ journalctl -b -1                     # 看【上一次】开机的日志
$ journalctl -b -2                     # 上上次
$ journalctl --list-boots              # 列出所有有记录的开机
# ★ 排查"重启前发生了什么",-b -1 是关键 ——
#   但它只在日志【持久化】了才有用(见修复 3)。

# === 按时间筛 ===
$ journalctl --since "2024-05-14 00:00:00"
$ journalctl --since "1 hour ago"
$ journalctl --since today
$ journalctl --since "09:00" --until "10:00"
$ journalctl -u myapp --since "10 min ago"

# === 按日志级别筛(PRIORITY)===
# 级别:emerg 0 / alert 1 / crit 2 / err 3 / warning 4 /
#       notice 5 / info 6 / debug 7
$ journalctl -p err                    # 只看 err 及更严重的
$ journalctl -p warning -b             # 本次开机的 warning 及以上
$ journalctl -p err..crit              # 指定一个级别区间

# === 按内核 / 进程筛 ===
$ journalctl -k                        # 只看内核日志(= dmesg)
$ journalctl _PID=3721                 # 按进程 PID
$ journalctl /usr/bin/sshd             # 按可执行文件路径

# === 组合:排查一个服务昨晚的报错 ===
$ journalctl -u myapp -p err --since "2024-05-13 22:00" \
             --until "2024-05-14 02:00" --no-pager
# 服务 + 级别 + 时间窗口,精确锁定 —— 这就是 journalctl 的威力。

# === 输出格式 ===
$ journalctl -u myapp -o verbose       # 显示每条日志的全部字段
$ journalctl -u myapp -o json-pretty   # JSON 格式(喂给程序)
$ journalctl -u myapp -o cat           # 只要消息正文,不要时间前缀

修复 3:让日志持久化——这次的治本之策

# === 这次的根因:journal 没持久化,重启即丢 ===
# journald 的存储模式由 journald.conf 的 Storage 决定:
#   Storage=volatile   只存内存(/run),重启全丢
#   Storage=persistent 存磁盘(/var/log/journal),重启保留
#   Storage=auto       ★ 默认:目录在就持久,目录不在就只存内存
#   Storage=none       完全不存

# === ★ 最简单的持久化办法:把目录建出来 ===
$ mkdir -p /var/log/journal
$ systemd-tmpfiles --create --prefix /var/log/journal
$ systemctl restart systemd-journald
# Storage=auto 看到 /var/log/journal/ 存在了,
# 就会自动开始往磁盘写 —— 之后的日志重启也还在。

# === 或者显式声明 persistent(更明确,推荐)===
$ vim /etc/systemd/journald.conf
[Journal]
Storage=persistent
$ systemctl restart systemd-journald

# === 验证持久化生效了 ===
$ journalctl --disk-usage
Archived and active journals take up 128.0M in the file system.
# ★ 不再是 0B,而是真实占用了磁盘 = 在往磁盘写了
$ ls /var/log/journal/
abc123.../                 # 目录里有数据了

# === 持久化之后,排查重启前问题才有意义 ===
$ journalctl -b -1 -u myapp -p err
# 现在 -b -1(上次开机)能真正翻出崩溃前的日志了。

# === 为什么这是"治本" ===
# 服务器最该留住的,恰恰是"出事那一刻"的日志,
# 而出事往往伴随重启。日志不持久化,
# 等于专门在最需要它的时候把它弄丢 ——
# 给服务器配 journal 持久化,是基本功,不是可选项。

修复 4:journal 占满磁盘的排查与清理

# === 持久化是把双刃剑:配不好,journal 会把磁盘吃满 ===

# === 先看 journal 占了多少 ===
$ journalctl --disk-usage
Archived and active journals take up 4.2G in the file system.
# 几个 G 甚至几十 G 都见过 —— journal 不限量就会一直涨。

# === 按需清理:vacuum 三种方式 ===
# 1. 按【时间】清:只保留最近 N 天/周
$ journalctl --vacuum-time=2weeks      # 只留最近两周
$ journalctl --vacuum-time=7d          # 只留 7 天

# 2. 按【总大小】清:压到 N 以内
$ journalctl --vacuum-size=500M        # 把 journal 压到 500M 以内

# 3. 按【文件数】清
$ journalctl --vacuum-files=10         # 最多保留 10 个 journal 文件

# === 清理前后对比 ===
$ journalctl --disk-usage              # 清理前:4.2G
$ journalctl --vacuum-size=500M
Deleted archived journal .../system@xxx.journal (256.0M).
...
$ journalctl --disk-usage              # 清理后:498M

# === 校验 journal 文件有没有损坏 ===
$ journalctl --verify
# 异常断电后,journal 文件可能损坏,verify 能查出来。

# === ★ 别直接 rm /var/log/journal 下的文件 ===
# 手动 rm 可能删到 journald 正在写的活动文件,导致它行为异常。
# 永远用 journalctl --vacuum-* 来清,这是它认可的清理方式。

# === 排查"是不是某个服务在疯狂刷日志" ===
$ journalctl --since "1 hour ago" --no-pager | \
    awk '{print $5}' | sort | uniq -c | sort -rn | head
# 统计最近一小时各来源的日志条数,
# 揪出"日志刷屏大户" —— 往往是某个服务在报错循环。

修复 5:给 journal 配上容量上限

# === 治本:在 journald.conf 里设好上限,别等它撑满 ===
$ vim /etc/systemd/journald.conf

[Journal]
Storage=persistent          # 持久化(修复 3)

# === 容量上限(针对 /var/log/journal 持久日志)===
SystemMaxUse=1G             # journal 总共最多用 1G 磁盘
SystemKeepFree=2G           # 给磁盘至少留 2G 空闲(优先保证这条)
SystemMaxFileSize=128M      # 单个 journal 文件最大 128M
MaxRetentionSec=2week       # 日志最多保留两周,超期自动删

# === 针对内存日志(/run)的上限 ===
RuntimeMaxUse=200M          # 内存里的 journal 最多 200M

# === 限制单服务刷日志的速率(防日志风暴)===
RateLimitIntervalSec=30s
RateLimitBurst=10000        # 30 秒内单服务最多 1 万条,超了丢弃并提示
# 某个服务疯狂报错时,这个能防止它把磁盘瞬间写爆。

# === 改完配置生效 ===
$ systemctl restart systemd-journald

# === 验证配置被正确读取 ===
$ journalctl --disk-usage           # 看占用是否被压在上限内

# === 各参数的优先级心智模型 ===
# journald 会同时尊重 SystemMaxUse 和 SystemKeepFree,
# 取"更严格"的那个。比如磁盘快满时,SystemKeepFree
# 会强制它腾出空间,哪怕还没到 SystemMaxUse。
# ★ 给 journal 设上限,是部署任何服务器都该做的一步 ——
#   它默认会"尽量多存",不主动限制就等于放任它涨。

# === 顺带:传统 /var/log 文本日志靠 logrotate 控制 ===
$ cat /etc/logrotate.conf
# journald 管 journal,logrotate 管传统文本日志,
# 两套都要配好,服务器的磁盘才不会被日志慢慢吃光。

修复 6:日志排查纪律

# === 这次事故暴露的日志管理问题,定几条纪律 ===

# === 1. ★ 新服务器第一件事:确认 journal 已持久化 ===
$ journalctl --disk-usage          # 是 0B 就是没持久化!
$ ls /var/log/journal/ 2>/dev/null # 目录不存在 = 日志只在内存
# 不持久化的 journal,在最关键的"崩溃+重启"时刻必然丢日志。

# === 2. 持久化的同时,必须配容量上限 ===
# Storage=persistent 但不设 SystemMaxUse / SystemKeepFree,
# 迟早把磁盘撑满。持久化和限容,是必须成对做的两件事。

# === 3. 排查问题,善用 journalctl 的精确筛选 ===
# 别一上来 journalctl 全量翻 —— 用 -u 锁服务、
# -p err 锁级别、--since 锁时间窗,三个一叠,直达现场。

# === 4. 排查"重启前"的问题,用 -b -1 ===
$ journalctl -b -1 -p err          # 上次开机的错误日志
# 但前提是日志已持久化,否则 -b -1 是空的。

# === 5. 清理 journal 只用 vacuum,不要手动 rm ===
$ journalctl --vacuum-time=2weeks
# rm journal 文件可能破坏 journald 的状态。

# === 6. 排查脚本里用 --no-pager ===
$ journalctl -u myapp -n 50 --no-pager
# 不加 --no-pager,journalctl 会调 less,在脚本/管道里会卡住。

# === 7. 定期巡检日志占用 ===
$ journalctl --disk-usage
$ du -sh /var/log/*                # 传统日志也一起看
$ journalctl --verify              # 顺手检查 journal 没损坏
# 把"journal 持久化 + 设上限 + 定期巡检"做成上线 checklist。

命令速查

需求                        命令
=============================================================
看某服务日志                journalctl -u 服务名
实时跟踪某服务               journalctl -u 服务名 -f
看本次开机日志              journalctl -b
看上次开机日志(查崩溃前)  journalctl -b -1
按时间筛                    journalctl --since "1 hour ago"
只看错误级别                journalctl -p err
看内核日志                  journalctl -k
看 journal 占多少磁盘       journalctl --disk-usage
按时间清理 journal          journalctl --vacuum-time=2weeks
按大小清理 journal          journalctl --vacuum-size=500M
校验 journal 是否损坏       journalctl --verify
持久化日志                  建 /var/log/journal + Storage=persistent

口诀:新机先查 --disk-usage 是不是 0B -> 持久化必配容量上限
      -> 排查用 -u + -p + --since 三连筛 -> 清理只用 vacuum

避坑清单

  1. journald 默认 Storage=auto,/var/log/journal 不存在时日志只写内存
  2. 日志只在内存里时,机器一重启崩溃前的日志全部丢失
  3. journalctl --disk-usage 显示 0B 就说明日志没有持久化到磁盘
  4. 持久化:建 /var/log/journal 目录或在 journald.conf 设 Storage=persistent
  5. 持久化必须同时配 SystemMaxUse/SystemKeepFree,否则会把磁盘撑满
  6. 排查重启前的问题用 journalctl -b -1,但前提是日志已持久化
  7. 排查善用 -u 锁服务、-p 锁级别、--since 锁时间窗三者叠加
  8. 清理 journal 只用 --vacuum-* 系列,别手动 rm journal 文件
  9. 脚本里调 journalctl 要加 --no-pager,否则会卡在分页器
  10. journald 管结构化日志,传统文本日志靠 logrotate,两套都要配

总结

这次复盘失败的复盘——想查崩溃现场,却发现现场早已不存在——给我上了非常深刻的一课。出事之后我最大的震动,不是"我命令写错了",而是一个更可怕的事实:我们这台跑着线上服务的服务器,自始至终处在一个"日志会随着每次重启而蒸发"的状态,而我们对此一无所知。复盘到根上,我才真正搞懂了 systemd 的日志系统 journald 的存储逻辑。journald 收集了系统里几乎所有的日志,但这些日志存到哪里去,是由它配置里一个叫 Storage 的选项决定的,而这个选项的默认值是 autoauto 这个词听起来很省心,但它的实际行为暗藏玄机:journald 会去看 /var/log/journal/ 这个目录存不存在,如果存在,它就把日志写进磁盘,实现持久化;如果不存在,它就只把日志写进 /run 下面。而 /run 是一个 tmpfs,也就是一个建立在内存上的文件系统——这意味着,只要这个目录从来没被创建过,日志就一直只活在内存里,机器一旦重启,内存被清空,所有日志就跟着关机一起消失得无影无踪。我们这台机器,恰恰就从来没有人去创建过 /var/log/journal/ 这个目录,于是它就一直、一直在用着这个"内存模式"。半夜服务崩溃,同事重启机器让服务恢复——这个操作本身完全正确,但正是这次重启,把崩溃前那段最宝贵的日志,连同内存一起抹掉了。日志没有"丢失",它是从一开始就没有被真正地"保存"下来。理解了这一点,这次事故真正的治本之策也就清楚了:必须让 journal 持久化。最简单的办法,就是把 /var/log/journal/ 这个目录手动创建出来,auto 模式一看到这个目录存在了,就会自动开始往磁盘写;更明确的做法,是直接在 journald.conf 里把 Storage 显式地写成 persistent,把意图清清楚楚地表达出来,不再依赖那个会"看情况"的 auto。但这次排查还让我意识到了硬币的另一面——持久化绝不是一配了之的事。一旦让 journal 开始往磁盘写,如果不给它设一个容量的上限,它会本着"尽量多存"的本能一直涨下去,几个 G、几十个 G,最终把磁盘悄无声息地撑满,引发一场新的事故。所以持久化和限容,是必须成对去做的两件事:在打开 Storage=persistent 的同时,一定要配上 SystemMaxUse 来限制 journal 占用的总磁盘量,配上 SystemKeepFree 来保证磁盘始终留有足够的空闲,再配上 MaxRetentionSec 让超期的日志自动被清理掉。这次事故之后,我给自己立下了一条会一直执行下去的纪律:每接手或新部署一台服务器,我做的第一件和日志相关的事,就是敲一下 journalctl --disk-usage——如果它显示的是 0B,那就是一个明确无误的危险信号,说明这台机器的日志根本没有落地,我必须立刻为它配置持久化和容量上限。因为我现在彻底明白了一个朴素而残酷的道理:服务器最需要日志的时刻,恰恰是它出事的时刻,而出事又常常伴随着重启;一套不持久化的日志系统,等于专门挑在你最需要它的那一刻,把证据销毁。这次从一段查不到的崩溃日志出发,我最大的收获,是不再把"日志会被记下来"当成一件理所当然、自动发生的事——日志能不能在重启后幸存、会不会把磁盘吃满,全都取决于我有没有为它做好那几项本不该被忽略的配置。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

chmod 777 埋下的木马:一次 Linux 文件权限排查复盘

2026-5-20 18:14:28

Linux教程

三个月备份全部落空:一次 Linux crontab 定时任务排查复盘

2026-5-20 18:21:49

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索