📺 B站博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev


把 Linux 日志真正看懂:journalctl、dmesg 与系统日志的实战排障指南

很多人会用 journalctl -xe,也知道 dmesg 能看内核日志,但一到线上故障、启动失败、服务拉不起、驱动探测异常,往往还是“日志看了很多,结论却下不来”。根本原因不是命令不会,而是没有把 Linux 日志体系当成一个完整链路来理解:谁产生日志、谁收集日志、谁存储日志、谁负责查询、出了问题该先看哪一层,再看哪一层。systemd-journald 负责收集并存储结构化日志;journalctl 负责查询这些日志;dmesg 面向的是内核日志环形缓冲区;而传统的 /var/log/messages/var/log/syslog/var/log/auth.log 是否存在、内容是否完整,还取决于发行版和是否启用了额外的 syslog 守护进程。(man7.org)

这篇文章不讲“玄学排障”,而是按工程化方式把日志体系拆开讲透:先把日志链路讲明白,再讲 journalctl 的过滤思路,再讲 dmesg 的边界,最后给出实战排障流程、常见误区和源码位置。你看完之后,应该能建立一个比较稳的判断框架:服务问题优先看 unit 维度,系统问题优先看 boot 维度,驱动/硬件问题优先看 kernel 维度,日志收集问题要回到 journald 配置与存储层。 (man7.org)


在这里插入图片描述

一、先把 Linux 日志体系捋顺:不是一个命令,而是一条链

1. 内核日志链路:printk → kernel log buffer → /dev/kmsgdmesg / journald

Linux 内核侧最基础的日志接口是 printk()。内核官方文档明确说明,printk() 是内核最基本的打印与调试手段,所有 printk() 消息都会进入内核日志缓冲区,这个缓冲区会通过 /dev/kmsg 暴露给用户态,通常读取它的方式就是 dmesg。这也是为什么驱动 probe 失败、时钟/中断异常、设备树问题、文件系统 mount 失败、OOM、panic 等问题,第一反应总是要看内核日志。(Linux Kernel Documentation)

这条链路的关键点有两个。第一,内核日志是环形缓冲区,旧消息会被新消息覆盖,所以机器跑得久、日志多、打印级别高时,早期内核信息可能已经被冲掉。第二,dmesg 看的本质是这个 ring buffer,本身并不等于“全系统日志”,更不等于“带历史归档的日志仓库”。dmesg 的职责偏“当前内核视角”,而不是“跨多次重启的系统审计视角”。(man7.org)

2. 用户态日志链路:stdout/stderr、syslog、native journal API → journald → journal files

systemd-journald 官方文档说得很清楚:它会收集并存储多种来源的日志,包括内核日志(kmsg)传统 syslog 消息native Journal API 的结构化消息systemd service 单元的标准输出/标准错误,以及 audit 记录。这意味着,现代 Linux 系统上非常多的服务日志,其实未必先落到 /var/log/messages,而是先进入 journal。(man7.org)

这也是为什么现在排查 nginx.servicedocker.servicessh.serviceNetworkManager.service、自定义守护进程时,journalctl -u xxx.service 的命中率通常更高。因为很多服务就是直接把 stdout/stderr 接给了 journald,或者走 syslog API 被 journald 接住,再由 journal 做结构化索引。journal 的优势不只是“能显示日志”,更重要的是:它给每条日志自动附带元数据,比如 _SYSTEMD_UNIT_PID_BOOT_IDPRIORITY_UID 等,后续就能精准过滤,而不是像 grep 纯文本那样靠猜。(man7.org)

3. 传统 /var/log/*.log 还重要吗?重要,但要知道它不是唯一真相

很多人熟悉的是 /var/log/messages/var/log/syslog/var/log/auth.log/var/log/kern.log。这些文件仍然重要,但它们是不是存在、记录是否完整、是否与 journal 同步,要看你的发行版和是否启用了 rsyslog/syslog-ng 之类的传统 syslog 服务。journald.conf 说明里明确提到:journald 可以选择是否把日志转发到传统 syslog、kmsg、console、wall 或 socket;而且如果启用了 ForwardToSyslog 但没有进程读那个 socket,转发也不会产生实际效果。默认情况下,并不是所有转发目标都开启。(man7.org)

所以工程上最稳的心法是:把 journal 当成第一现场,把传统文本日志当成兼容层或二级落地层。 尤其在 systemd 主导的发行版里,先用 journalctl 建立全局判断,再决定是否去翻 /var/log 下的文件,效率会高很多。(man7.org)


二、journalctl 的本质:不是“看日志”,而是“按字段查日志”

journalctl 官方手册最关键的一句话是:如果传入一个或多个匹配项,格式是 FIELD=VALUE,那么输出会按这些结构化字段过滤。比如 _SYSTEMD_UNIT=httpd.service。这说明 journalctl 不是简单的“cat 某个日志文件”,而是面向结构化日志记录进行查询。(man7.org)

这背后是很多人真正缺的认知:
在 journal 里,一条日志不仅只有 MESSAGE=,还可能带有:

  • PRIORITY=:0 到 7,对应 emerg 到 debug。(man7.org)
  • _PID=_UID=_GID=:日志来源进程和用户身份。(man7.org)
  • _SYSTEMD_UNIT=:来自哪个 service / socket / mount / target。(man7.org)
  • _BOOT_ID=:属于哪一次启动。journalctl -b 就是基于它做过滤。(man7.org)

理解这一点之后,你查日志的方式就会从“我记得报错里好像有 timeout 这个单词”升级成“我先圈定 boot、再圈定 unit、再圈定优先级、最后按时间窗口缩小范围”。这才是能稳定复盘故障的用法。(man7.org)


三、最重要的命令,不是多,而是要形成套路

下面这组命令,建议你真正记熟。不是因为它们多高级,而是因为它们覆盖了 80% 的排障场景。

1. 看全局日志

journalctl

不带参数时,journalctl 会显示调用者可访问的 journal 内容,并按时间从旧到新输出。适合第一次接手机器时“先摸全局状态”。(man7.org)

但我更建议平时直接配合时间或条数使用:

journalctl -n 200
journalctl --since "2026-04-03 10:00:00"
journalctl --since "1 hour ago"
journalctl --since today

--since / --until 支持绝对时间、today / yesterday / now,以及相对时间。对线上问题特别实用,因为你通常知道“故障大概几点发生”。(man7.org)

2. 看某个服务

journalctl -u nginx.service
journalctl -u ssh.service -n 100
journalctl -u docker.service --since "30 min ago"
journalctl -u myapp.service -f

-u/--unit 会按 unit 过滤,而且不只是简单匹配 _SYSTEMD_UNIT=UNIT,它还会把 systemd 自身关于这个 unit 的消息和相关 coredump 消息一起纳入视图,所以排查 service 非常高效。-f 则类似 tail -f,持续跟踪新日志。(man7.org)

3. 按启动批次看

journalctl -b
journalctl -b -1
journalctl -b -2
journalctl -b -1 -p err..alert

-b 会基于 _BOOT_ID 过滤日志。当前启动看 -b,上一次启动看 -b -1。这在机器“这次启动正常、上次启动异常”时非常有价值。尤其你怀疑重启、死机、驱动失败、挂载失败、fsck、网络初始化失败时,先看 -b -1 往往比全局 grep 更快。(man7.org)

4. 只看内核日志

journalctl -k
journalctl -k -b
journalctl -k -b -1
journalctl -k -p warning..alert

-k/--dmesg 的含义不是“调用 dmesg 命令”,而是在 journal 里只显示 _TRANSPORT=kernel 的消息;如果没显式指定 boot,还会默认看当前启动。它的优势在于:它仍然在 journal 框架下,所以可以继续叠加 -b-p--since 等过滤条件。(man7.org)

5. 按严重级别筛

journalctl -p err
journalctl -p warning..err
journalctl -b -p err..alert

-p/--priority 支持单个级别,也支持范围,例如 err..alert。这比直接 grep “error” 可靠得多,因为它走的是 syslog 语义的优先级。PRIORITY 字段取值从 0 到 7,对应 emerg、alert、crit、err、warning、notice、info、debug。(man7.org)

6. 看完整字段,而不是只看一行文本

journalctl -u myapp.service -o verbose
journalctl -u myapp.service -o json-pretty
journalctl -k -o short-full

-o/--output 可以控制输出格式。short 是默认格式,长得像传统 syslog;short-full 时间更完整;verbose 会打印结构化字段;JSON 输出适合后续程序处理。真到疑难问题时,别死盯 MESSAGE=,把字段拉全,你会看到 _PID_SYSTEMD_UNIT_BOOT_ID_CMDLINE 等更多信息。(man7.org)


四、dmesg 到底该怎么用,它和 journalctl -k 有什么区别?

1. dmesg 的职责很纯:读内核 ring buffer

dmesg 的手册定义很直接:它用于检查或控制 kernel ring buffer,默认动作就是显示该缓冲区中的所有消息。也就是说,它首先是内核日志读取工具,而不是 systemd 日志工具。(man7.org)

最常用的几个命令:

dmesg
dmesg -T
dmesg -w
dmesg -x
  • -T/--ctime:把时间戳转成人类可读时间。(man7.org)
  • -w/--follow:持续等待并打印新消息,适合热插拔、网卡 flap、驱动 probe。它要求系统可读 /dev/kmsg。(man7.org)
  • -x/--decode:把 facility 和 level 解码成人类可读前缀。(man7.org)

2. dmesgjournalctl -k 的核心区别

很多人误以为二者完全等价。其实不完全一样:

  • dmesg 面向当前内核 ring buffer。(man7.org)
  • journalctl -k 面向journal 里被标记为 kernel transport 的日志,因此可以结合 boot、时间、优先级、字段等做更复杂的查询。(man7.org)

工程上怎么选?
正在观察内核实时行为,比如插网线、加载驱动、反复 probe、USB 设备上下线,用 dmesg -w
想做复盘、跨重启对比、叠加 boot 和 severity 过滤,用 journalctl -k -b -1 -p warning..alert
这两个命令不是替代关系,而是一个偏实时,一个偏检索。(man7.org)


五、最实用的四类排障打法

场景一:服务启动失败

假设你写了一个自定义服务 myapp.service,执行 systemctl start myapp 失败。很多人第一反应是 systemctl status,这没错,但我更推荐下面的组合:

systemctl status myapp.service
journalctl -u myapp.service -b -n 100
journalctl -u myapp.service --since "10 min ago" -o short-full
journalctl -u myapp.service -p err..alert

为什么这套组合有效?因为 service 的 stdout/stderr 本来就会被 journald 收集,而 -u 会直接把 unit 相关消息聚到一起。你很容易分辨是ExecStart 路径错了、权限不够、环境变量没带、依赖服务没起来、端口被占、还是程序自身 panicsystemd.service(5) 说明 service 单元是 systemd 管理进程状态的基本对象,而 journalctl -u 正好围绕这个对象查日志。(man7.org)

示例:

journalctl -u myapp.service -b -n 20

可能看到:

Apr 03 10:15:21 host systemd[1]: Started myapp.service.
Apr 03 10:15:21 host myapp[18342]: failed to load config /etc/myapp/config.yaml
Apr 03 10:15:21 host myapp[18342]: permission denied
Apr 03 10:15:21 host systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Apr 03 10:15:21 host systemd[1]: myapp.service: Failed with result 'exit-code'.

这时你不需要先改程序,先确认配置文件权限、SELinux/AppArmor、工作目录、User=Group=、相对路径,就已经能解决一大半问题。

场景二:机器上次启动失败、这次又恢复了

这种问题最适合用 boot 维度切。命令如下:

journalctl -b -1 -n 300
journalctl -b -1 -p err..alert
journalctl -k -b -1
journalctl -k -b -1 -p warning..alert

这组命令能快速把“上一次启动”的系统消息和内核消息抽出来看。你会很容易定位到是 fsck 卡住、某块盘超时、network-online.target 等待失败、GPU 驱动卡住、文件系统恢复、还是某个 service 反复 crash 触发看门狗。-b 对生产环境特别重要,因为很多故障不是“现在还在发生”,而是“机器昨晚 03:12 重启过一次”。(man7.org)

场景三:怀疑是驱动、硬件、设备树、固件问题

这是 dmesgjournalctl -k 联合作战的典型场景:

dmesg -w
journalctl -k -b -p warning..alert
journalctl -k --since "today"

如果你在做嵌入式板卡、驱动开发、外设 bring-up,这个模式要形成肌肉记忆。因为 probe 失败、I2C timeout、regulator enable 失败、MMC 超时、中断没起来、固件加载失败,第一现场几乎都在 kernel log buffer。内核官方文档明确指出 printk() 是内核最基本的调试方式,消息进入内核日志缓冲区;而 dmesg 正是读它的常用工具。(Linux Kernel Documentation)

假设插入一个有问题的网卡驱动模块,dmesg -w 可能看到:

[  215.884231] mynic 0000:03:00.0: firmware load failed
[  215.884520] mynic 0000:03:00.0: probe failed with error -2

这时候你再去查 /lib/firmware、内核配置、设备树节点、PCI BAR、DMA 区域,方向就很清楚了。

场景四:系统慢、磁盘满、日志太多,怀疑是 journal 本身占空间

这个时候不要直接 rm -rf /var/log/journal/*。先查,再清,再验证:

journalctl --disk-usage
journalctl --rotate
journalctl --vacuum-time=7days
journalctl --vacuum-size=1G
journalctl --verify

--disk-usage 查看当前 journal 占用;--vacuum-* 清理归档日志;--rotate 先把活跃文件切成归档,再做清理更有效;--verify 检查 journal 文件内部一致性。这些能力都是 journalctl 自带的,没必要每次都粗暴删目录。(man7.org)


六、为什么很多人“看了 journalctl 还是不会排障”?

因为他们把 journalctl 当成了“高级版 cat”,没有建立分层排障顺序。我建议你按下面这个固定顺序来:

第一步:先判断问题属于哪一层

  • 服务起不来:先 journalctl -u xxx.service
  • 系统启动异常:先 journalctl -b / journalctl -b -1
  • 硬件/驱动异常:先 journalctl -kdmesg
  • 只知道故障时间:先 journalctl --since ... --until ...

这一步的意义是先切分搜索空间,不要一上来就全局 grep。因为 journal 最大的价值就在于字段过滤,而不是“日志很多”。(man7.org)

第二步:再加严重级别

journalctl -u myapp.service -p err..alert
journalctl -b -1 -p warning..alert
journalctl -k -p err..alert

这样做的收益是先抓高信号日志,把 notice/info/debug 先压下去。PRIORITY 的 0…7 语义和传统 syslog 兼容,适合做第一轮聚焦。(man7.org)

第三步:最后再看完整字段

当你看到一条“看似普通”的报错,但还不能确定谁打的、属于哪个进程、是不是当前 boot 的时候,别犹豫,直接上:

journalctl -u myapp.service -n 20 -o verbose

结构化字段往往能把“模糊判断”变成“确定判断”。比如你会发现日志根本不是 myapp 打的,而是 systemd[1] 打的;或者 _PID 对不上当前进程;或者 _BOOT_ID 不是你以为的这次启动。(man7.org)


七、持久化、转发、落盘:日志不是查出来的,是先被正确保存下来的

很多人只关心“怎么查”,却忽略“有没有存住”。journald.conf 明确说明:storage 的行为和 /var/log/journal 是否存在有直接关系。Storage=auto 时,如果 /var/log/journal 存在,就按 persistent 处理;否则走 volatile,主要写到 /run/log/journal。而 /run 是易失的,重启就没。也就是说,你以为的“日志丢了”,很多时候不是 journalctl 查不到,而是机器根本没做持久化。 (man7.org)

实战上我建议这样做:

  1. 明确你的环境是否需要跨重启保留日志。
  2. 需要的话,确保 /var/log/journal 存在,并在 journald.conf 中使用持久化策略。
  3. 如果你还要兼容传统日志采集链路,再考虑 ForwardToSyslog=。但要记住,开启转发并不代表一定有人在读;若没有 syslog 进程接收,转发没有实际效果。(man7.org)

这部分属于“日志治理”,不是小事。线上故障复盘最怕的不是日志太多,而是关键时刻没有日志


八、别只会用命令,源码位置也要心里有数

你说“代码位置不要来虚的”,那我就把真正值得记的 upstream 位置列出来。下面这些位置,不是摆样子,真出深水区问题时有用。

1. systemd 侧

journalctl 命令本身在 upstream systemd 仓库的 src/journal/journalctl.c。这就是 CLI 查询逻辑的主战场。(GitHub)

systemd-journald 守护进程入口在 src/journal/journald.c。从这个文件能看到 journald 主程序入口以及其依赖的 journald 管理模块。(GitHub)

journal 配置相关代码在 src/journal/journald-config.c,这对应你在 journald.conf 里配置存储、限额、转发等行为时,systemd 内部是怎么解析的。(GitHub)

2. 内核侧

内核日志基础说明文档在 Documentation/core-api/printk-basics.rst,这是理解 printk、日志级别、ring buffer 的第一手材料。(GitHub)

核心 printk 相关实现位于 kernel/printk/printk.c。从代码里甚至能看到 vmcore 场景下导出符号以便 crash/makedumpfile 提取 dmesg 的注释,说明 printk 日志对故障分析有多核心。(GitHub)

/proc/kmsg 的 procfs 入口代码在 fs/proc/kmsg.c。虽然现代用户态更多通过 /dev/kmsgdmesg 工具交互,但知道这个位置,对理解内核日志导出路径很有帮助。(GitHub)

你会发现,只要把这些路径记住,很多“日志为什么这样显示”“为何某些消息进了 journal 却没进 syslog”“为何某些内核消息 dmesg 里有但历史查不到”之类的问题,就不再停留在经验层,而能回到实现层。


九、一个真正能落地的日志排障流程

我把自己比较推荐的一套流程写成“固定动作”,你可以直接照着练。

1. 接到故障,先定位层级

# 服务类
journalctl -u xxx.service -b -n 100

# 启动类
journalctl -b -1 -n 200

# 内核类
journalctl -k -b -n 200
dmesg -T | tail -n 100

2. 看不到重点,就缩时间窗口

journalctl --since "2026-04-03 09:55:00" --until "2026-04-03 10:05:00"
journalctl -u xxx.service --since "10 min ago"

3. 日志太多,就先压级别

journalctl -p err..alert
journalctl -k -p warning..alert

4. 还不清楚是谁打的,就看结构化字段

journalctl -u xxx.service -n 50 -o verbose

5. 怀疑日志丢失,就查存储与空间

journalctl --disk-usage
journalctl --verify

这套动作看似朴素,但比“上来就 grep error”强得多。它的核心是:先分层,再分 boot,再分时间,再分优先级,最后看字段。


十、几个特别常见的误区,必须纠正

误区一:journalctl -xe 就是万能排障命令

不是。它只能给你最近一段时间的扩展信息,但如果你不知道自己在查哪个 unit、哪次 boot、哪个时间窗,那它仍然会把你淹没在信息里。journalctl 最强的地方不是 -xe,而是 -u-b-k-p--since 这些“缩小范围”的能力。(man7.org)

误区二:dmesg 就是 Linux 全部日志

不是。dmesg 是内核 ring buffer 视角,不是 journald 的完整历史视角,更不是传统 syslog 文本文件的集合。它非常重要,但它只覆盖“内核这层”。(man7.org)

误区三:/var/log/messages 没有,就说明系统没日志

也不是。现代 systemd 系统里,大量日志首先进入 journal。传统文本日志是否存在,取决于是否启用了相应 syslog 守护进程、以及 journald 是否向其转发。(man7.org)

误区四:日志查不到,说明没打出来

还有一种可能是没有持久化。如果系统只写 /run/log/journal,重启后你当然查不到上一次的日志。Storage=auto/var/log/journal 是否存在直接相关。(man7.org)


十一、最后做一个体系化总结

如果让我把整篇文章压缩成一句话,那就是:

把 journal 当成日志数据库,把 journalctl 当成结构化查询器,把 dmesg 当成内核即时窗口,把 /var/log/*.log 当成兼容落地层。

再展开一点,就是下面这四条:

第一,systemd-journald 负责收集,来源包括 kernel、syslog、native journal API、service stdout/stderr、audit,所以它天然比“某一个文本文件”更接近系统真实状态。(man7.org)

第二,journalctl 的核心不是“看”,而是“按字段查”。-u 查 unit,-b 查 boot,-k 查 kernel transport,-p 查优先级,--since/--until 查时间窗,-o verbose/json 查结构化字段。(man7.org)

第三,dmesg 只解决内核日志问题,但在驱动、硬件、启动、OOM、probe、panic 这类场景里,它仍然是第一现场,因为 printk() 消息进入的就是 kernel log buffer,而 dmesg 正是读取它的标准工具。(Linux Kernel Documentation)

第四,排障不是堆命令,而是走顺序:先分层,再分 boot,再分时间,再分优先级,最后看结构化字段。 你把这个动作练熟,Linux 日志就不再是“玄学”,而会变成一套很稳定的工程手法。(man7.org)



📺 B站博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐