用 journalctl 把 systemd 日志查清楚:从一次服务起不来说起
昨晚有台机器上的应用服务反复重启,systemctl status 只甩出几行截断的红字。真正能把时间线摊开的,是 journald 的查询入口 journalctl。这篇把我平时排查时真正会用到的几组命令整理下来,不讲手册目录,只讲「出了问题先敲什么」。
先确认 journald 还在记
journalctl 读的是 systemd-journald 落在磁盘上的二进制日志,不是 /var/log/messages 那种纯文本。先看服务本身是否健康、日志到底有没有持久化:
systemctl is-active systemd-journald
journalctl --disk-usage
cat /etc/systemd/journald.conf
如果 Storage= 是 volatile,日志只在 /run/log/journal,重启就没了。需要跨重启保留时,改成 persistent,并保证 /var/log/journal 存在且属主是 systemd-journal。改完执行:
sudo systemctl restart systemd-journald
磁盘被打满之前,用 SystemMaxUse= 给一个上限,比事后删文件稳妥。
一次启动看不清,就按启动次切开
服务「今早起来又挂了」这种描述,先别在全量日志里翻。--list-boots 列出每次启动的序号和起止时间,0 是当前这次,-1 是上一轮:
journalctl --list-boots
journalctl -b -1 -u myapp.service
-b 把范围锁在某一次启动里,-u 再锁到单个 unit。两者一起用,噪音会少一个数量级。
看某个服务时,我固定的四条
journalctl -u myapp.service -n 200 --no-pager
journalctl -u myapp.service -p err..alert --since "1 hour ago"
journalctl -u myapp.service -f
journalctl -u myapp.service -o json-pretty -n 20
含义分别是:最近 200 行、一小时内 error 及以上、持续跟踪、以及把结构化字段打出来。-o json-pretty 在查「这条日志到底是谁写的」时很有用,_PID、_COMM、SYSLOG_IDENTIFIER、CODE_FILE 都在里面。只想看某个进程号:
journalctl _PID=1234 --since "10 min ago"
优先级从低到高是 debug、info、notice、warning、err、crit、alert、emerg。-p warning 表示 warning 及更严重的;写成 -p err..alert 可以卡一个闭区间,避免 emerg 那种全系统广播把画面刷掉。
时间范围比行数更可靠
线上日志量大时,-n 很容易刚好错过故障点。用绝对时间或相对时间把窗口钉死:
journalctl -u myapp.service --since "2026-11-05 09:00:00" --until "2026-11-05 09:20:00"
journalctl -u nginx.service --since "30 min ago" --no-pager
时间按本地时区解释。机器如果是 UTC,而你按北京时间心算,会整整偏八小时。先 timedatectl 看一眼 Time zone 再下 --since。
内核日志和用户态日志不要混在一个窗口里
网卡掉线、OOM、磁盘 I/O 错误通常在内核环里。-k 等价于 -t kernel 这一路:
journalctl -k -b -p err --no-pager
journalctl -k --grep "Out of memory" --since "today"
--grep 是对 MESSAGE 字段做正则,不是对整行文本文件做 grep。它区分大小写,要忽略大小写时用 --grep 配合 -i 并不总是可用,稳妥写法是把模式写成明确的大小写,或者先 -o cat 导出再在管道里过滤。导出时记得 --no-pager,否则会进 less。
把「服务挂了」和「被人重启」分开
同样是 unit 进入 failed,原因可能是进程自己退出、可能是 OOM killer、也可能是有人 systemctl restart。看 unit 自己的状态变迁:
journalctl -u myapp.service --since "today" | grep -E "Started|Stopped|Failed|Main process exited|killed"
systemctl show myapp.service -p NRestarts -p ExecMainStatus -p Result -p ActiveState
NRestarts 在涨、Result=oom-kill,就别再改应用配置了,先看内存限制:MemoryMax=、cgroup 当前用量,以及同一时刻的 journalctl -k --grep "Killed process"。
权限和常见空结果
普通用户默认只能看自己产生的日志。空输出不一定是「没日志」,经常是没进 systemd-journal 或 adm 组:
id
sudo journalctl -u myapp.service -n 50 --no-pager
另外,-u 后面要写 unit 名而不是二进制名。服务叫 myapp.service,不要写成进程名 myappd,否则结果是空的,而且不报错。不确定名字时:
systemctl list-units --type=service --state=running
日志太多时怎么收
查完问题不代表要一直留着全量。除了 SystemMaxUse=,还可以:
sudo journalctl --vacuum-time=14d
sudo journalctl --vacuum-size=500M
vacuum 只删已归档的旧 journal 文件,不会动当前正在写的那一个。在生产上我更倾向先设 SystemMaxUse=,把 vacuum 留作一次性清理。
一个可复用的排查顺序
timedatectl确认时区,systemctl is-active systemd-journald确认日志服务。journalctl -u <unit> -b -p warning --no-pager看本次启动里的告警。- 还是看不懂,就
--since/--until把窗口缩到故障前后十分钟,加-o json-pretty看_PID和退出码。 - 怀疑是系统而不是应用,切到
journalctl -k -b -p err。 - 需要留档时,
journalctl -u <unit> --since today --no-pager > /tmp/<unit>-$(date +%F).log,不要把含令牌或连接串的行原样贴出去。
journalctl 的价值不在参数多,而在于它和 unit、启动次、优先级、时间窗口是对齐的。先把范围收窄,再读内容,比在几万行里用眼睛搜索快得多。
- 点赞
- 收藏
- 关注作者
评论(0)