Linux 日志实战:用 journalctl 精准检索与治理工业边缘日志

journalctl 是查询 systemd Journal 的标准客户端。对工业边缘设备而言,它不只是"看日志的命令":服务失败、内核错误、上一次启动现场、结构化业务字段、磁盘占用和日志保留策略,都可以围绕 Journal 建立一套可排查、可审计、可持续运维的机制。

但 journalctl 也有前提和边界。它适用于使用 systemd-journald 的 Linux 系统;如果 Journal 运行在 volatile 模式,日志只保存在 /run/log/journal,重启后就会丢失;如果权限不足,普通用户只能看到很小一部分日志。把这些边界搞清楚,比记住一堆参数更重要。

本文从实战角度梳理常用查询、过滤、输出格式、启动现场、结构化日志、持久化、容量清理、权限和常见坑,并给出适合工业边缘设备的日志治理模板。

一、先确认 Journal 的工作状态

排查前先确认三件事:systemd 是否在管理日志、Journal 当前能否持久化、当前用户是否有读取权限。

bash 复制代码
# 查看 journald 服务状态
systemctl status systemd-journald

# 查看当前配置来源
systemd-analyze cat-config systemd/journald.conf

# 查看磁盘占用
journalctl --disk-usage

# 查看启动记录
journalctl --list-boots

普通用户默认只能看到自己产生的日志。如果需要查询系统日志,通常有以下方式:

  • 使用 sudo journalctl;
  • 将运维账号加入 systemd-journal、adm 或发行版约定的日志读取组;
  • 通过日志平台或受控脚本暴露只读查询能力。

示例:

bash 复制代码
sudo usermod -aG systemd-journal ops

加入组后需要重新登录才会生效。不要为了看日志直接放宽 /var/log/journal 的全局读写权限。

二、基础查询:时间、尾部与实时跟踪

1. 查看全部日志

bash 复制代码
journalctl

默认进入分页器。脚本中建议显式关闭分页:

bash 复制代码
journalctl --no-pager

2. 时间过滤

bash 复制代码
# 今天的日志
journalctl --since today

# 最近 1 小时
journalctl --since "1 hour ago"

# 指定时间区间
journalctl --since "2025-01-01 00:00:00" \
           --until "2025-01-31 23:59:59"

# 使用 UTC 输出,避免跨时区误读
journalctl --since today --utc

--since 和 --until 也可分别写成 -S 和 -U。时间解析依赖本机时区配置。跨站点排障时,应统一记录时间戳来源,并在告警中明确 UTC 或本地时间。

3. 尾部与实时跟踪

bash 复制代码
# 最近 50 条
journalctl -n 50 --no-pager

# 逆序显示
journalctl -r --no-pager

# 类似 tail -f
journalctl -f

# 只跟踪某个服务
journalctl -u edge-gateway.service -f

生产环境中不要长期裸跑 journalctl -f。更稳妥的方式是按服务、优先级、字段过滤后跟踪,并把输出交给日志采集器。

三、按服务、内核和优先级过滤

1. 按服务单元过滤

bash 复制代码
journalctl -u myapp
journalctl -u myapp.service
journalctl -u myapp.service -f

# 多个 -u 是并列匹配,即任一单元匹配即可
journalctl -u myapp.service -u nginx.service

建议显式写 .service 后缀,尤其是服务名与其他 unit 类型可能冲突时。

2. 按内核日志过滤

bash 复制代码
journalctl -k
journalctl --dmesg

# 当前启动的内核错误
journalctl -k -p err -b

-k 查看的是内核消息。它不能替代 dmesg 的所有排查场景,例如某些早期启动消息、容器隔离环境或非 systemd 系统的内核日志,需要结合现场判断。

3. 按优先级过滤

优先级从高到低为:

text 复制代码
emerg > alert > crit > err > warning > notice > info > debug

常见用法:

bash 复制代码
# err 及更严重级别:emerg / alert / crit / err
journalctl -p err

# 只看 warning 到 err 这个范围
journalctl -p err..warning

# 只看 err 级别
journalctl -p err..err

# 最近 1 小时的错误
journalctl -p err --since "1 hour ago"

这里容易误读:单独使用 -p err 不是"只看 err",而是包含 err 以及更严重的 crit、alert、emerg。如果要精确匹配一个级别,需要写 err..err。

4. 按进程、用户和可执行文件过滤

bash 复制代码
# 按 PID 字段
journalctl _PID=12345

# 按 UID 字段
journalctl _UID=1000

# 按系统标识
journalctl -t myapp
journalctl SYSLOG_IDENTIFIER=myapp

# 按可执行文件路径匹配 _EXE
journalctl /usr/local/bin/myapp

# 按进程名匹配 _COMM
journalctl _COMM=myapp

注意:

  • /usr/local/bin/myapp 匹配的是 Journal 记录中的 _EXE 字段,只适合真实可执行文件路径;
  • Python、Shell、Node 等脚本的 _EXE 通常是对应解释器路径,不是脚本文件路径;
  • _COMM 是进程名,可能同名冲突;
  • 更推荐服务用 -u,业务日志用 SYSLOG_IDENTIFIER 或自定义结构化字段。

四、文本匹配与组合过滤

1. 使用 --grep

如果 systemd 编译启用了 PCRE2 支持,可以使用:

bash 复制代码
journalctl -u edge-gateway.service \
  --since "2 hours ago" \
  --grep 'timeout|connection refused'

# 忽略大小写
journalctl -u edge-gateway.service \
  --since "2 hours ago" \
  --case-sensitive=false \
  --grep 'device.*offline'

--grep 的能力依赖构建选项。如果提示不支持,需要退回普通文本管道:

bash 复制代码
journalctl -u edge-gateway.service \
  --since "2 hours ago" \
  --no-pager |
  grep -Ei 'timeout|connection refused'

2. 字段匹配的与或关系

同一个命令行中的多个字段匹配通常表示同时满足:

bash 复制代码
journalctl SYSLOG_IDENTIFIER=edge-gateway SITE_ID=SH-01

多个 -u 之间是并列匹配:

bash 复制代码
journalctl -u edge-gateway.service -u modbus-collector.service

复杂组合可以使用 + 表示两组条件之间的"或"关系,具体语法应以当前 systemd 版本的 systemd.journal-fields(7) 和 journalctl(1) 手册为准。排障时先用窄范围验证条件,再扩大时间窗口,比一次性写复杂表达式更可靠。

五、输出格式:给人看与给程序处理

bash 复制代码
# 默认格式
journalctl -u myapp.service

# 短格式
journalctl -u myapp.service -o short

# ISO 8601 时间
journalctl -u myapp.service -o short-iso

# 高精度 ISO 时间,适合性能和时序分析
journalctl -u myapp.service -o short-iso-precise

# 只输出 MESSAGE,适合管道处理
journalctl -u myapp.service -o cat

# JSON 行,适合日志采集和脚本处理
journalctl -u myapp.service -o json

# 缩进 JSON,适合人工检查字段
journalctl -u myapp.service -o json-pretty

# 查看一条日志的全部字段
journalctl -u myapp.service -o verbose

如果只需要部分字段,可以使用 --output-fields:

bash 复制代码
journalctl -u edge-gateway.service \
  --since "1 hour ago" \
  --output-fields=MESSAGE,PRIORITY,SYSLOG_IDENTIFIER \
  -o json-pretty

JSON 输出中的 __REALTIME_TIMESTAMP 是微秒时间戳,不是毫秒。下流水线或入库前要明确单位,避免时间相差 1000 倍。

六、启动现场与服务排障

1. 查看启动

bash 复制代码
# 本次启动
journalctl -b

# 上一次启动
journalctl -b -1

# 启动记录列表
journalctl --list-boots

-b -1 的前提是上一次启动日志已经被持久化。如果 Journal 是 volatile 模式,重启后没有历史启动可查。

2. 排查服务失败

bash 复制代码
# 服务状态与最近日志
systemctl status edge-gateway.service --no-pager
journalctl -u edge-gateway.service -n 100 --no-pager

# 服务错误与更严重级别
journalctl -u edge-gateway.service -p err --since today

# 服务启动前后的系统错误
journalctl -b -p err --no-pager

# 实时跟踪
journalctl -u edge-gateway.service -f

如果服务反复重启,还应查看:

bash 复制代码
systemctl list-units --state=failed --no-pager
systemctl list-timers --all --no-pager
journalctl -u edge-gateway.service --since "24 hours ago" --no-pager

3. 一次典型排障流程

bash 复制代码
# 1. 确认当前启动和失败单元
journalctl --list-boots
systemctl --failed --no-pager

# 2. 查看失败服务本次启动日志
journalctl -b -u edge-gateway.service -n 200 --no-pager

# 3. 收窄到错误与更严重级别
journalctl -b -u edge-gateway.service -p err --no-pager

# 4. 查看同一时间窗口的内核和系统错误
journalctl -b -p err --since "10 minutes before the failure" \
  --until "10 minutes after the failure" --no-pager

上面的 --since 示例应替换为真实时间字符串;journalctl 不会自动知道"failure"发生在什么时候。

七、结构化日志:不要把 JSON 字符串当成结构化字段

下面命令发送的是一个 JSON 字符串作为 MESSAGE:

bash 复制代码
logger -t myapp -p user.info \
  '{"event":"start","port":8080}'

查询时会得到:

bash 复制代码
journalctl -t myapp -o json-pretty

这个日志仍然可以直接查询到 JSON 文本,但 Journal 并不会自动把 event、port 变成独立字段。要让字段可索引、可过滤,应使用 Journal 字段或日志库的结构化能力。

1. 使用 logger --journald

bash 复制代码
printf '%s\n' \
  'MESSAGE=device online' \
  'SYSLOG_IDENTIFIER=edge-gateway' \
  'SITE_ID=SH-01' \
  'DEVICE_ID=dev-001' \
  'EVENT=device_online' |
  logger --journald

注意:

  • MESSAGE 是日志正文;
  • SYSLOG_IDENTIFIER 用于标识来源;
  • 自定义字段建议使用大写和下划线;
  • 以下划线开头的字段是 Journal 信任字段,不应由普通应用伪造;
  • 字段值中包含换行等特殊字符时,需要按 Journal 原生格式编码。

2. Python 发送结构化日志

python 复制代码
from systemd import journal

def log_device_event(
    event: str,
    device_id: str,
    site_id: str,
    *,
    priority: int = journal.LOG_INFO,
) -> None:
    journal.send(
        f"device event: {event}",
        PRIORITY=priority,
        SYSLOG_IDENTIFIER="edge-gateway",
        EVENT=event,
        DEVICE_ID=device_id,
        SITE_ID=site_id,
    )

log_device_event(
    event="device_online",
    device_id="dev-001",
    site_id="SH-01",
)

查询结构化字段:

bash 复制代码
journalctl -t edge-gateway \
  EVENT=device_online \
  SITE_ID=SH-01 \
  -o json-pretty

Python 示例依赖 python3-systemd 或对应的 systemd Python 包。

3. 在 systemd 服务中固定标识

ini 复制代码
[Service]
Type=simple
ExecStart=/usr/local/bin/edge-gateway
SyslogIdentifier=edge-gateway
StandardOutput=journal
StandardError=journal
LogExtraFields=SITE_ID=SH-01

这样应用即使没有输出完整上下文,运维侧也能通过 unit、identifier 和额外字段缩小范围。

八、日志持久化与容量治理

1. 为什么默认可能重启丢失

如果只存在 /run/log/journal,日志保存在内存文件系统中,重启后会丢失。要让日志跨重启保留,需要启用 /var/log/journal。

推荐使用 drop-in 配置,而不是直接修改主配置文件:

bash 复制代码
sudo mkdir -p /etc/systemd/journald.conf.d
sudoedit /etc/systemd/journald.conf.d/90-edge-retention.conf

示例配置:

ini 复制代码
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
SystemMaxFileSize=100M
MaxRetentionSec=30day
RateLimitIntervalSec=30s
RateLimitBurst=1000
ForwardToSyslog=no

说明:

  • Storage=persistent:要求日志落盘;
  • SystemMaxUse:Journal 最大可占用空间;
  • SystemKeepFree:至少给文件系统保留多少空闲空间;
  • SystemMaxFileSize:单个 journal 文件上限;
  • MaxRetentionSec:最长保留时间;
  • RateLimitIntervalSec 与 RateLimitBurst:控制异常刷日志速率;
  • ForwardToSyslog=no 只应在没有 rsyslog/syslog 链路时使用。

应用配置:

bash 复制代码
sudo systemctl restart systemd-journald

重启 journald 可能造成短暂日志写入中断,建议在维护窗口执行。部分发行版还需要确认 /var/log/journal 已创建且权限正确:

bash 复制代码
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

2. 查看与清理

bash 复制代码
# 查看磁盘占用
journalctl --disk-usage

# 将活动文件旋转为归档文件
sudo journalctl --rotate

# 按大小清理
sudo journalctl --vacuum-size=1G

# 按时间清理
sudo journalctl --vacuum-time=30d

# 按文件数清理
sudo journalctl --vacuum-files=10

# 将缓冲区日志刷盘
sudo journalctl --sync

关键细节:--vacuum-* 主要清理已归档 journal 文件,不一定会删除当前正在写入的 active 文件。因此清理后仍高于目标值并不一定是命令失败,可能需要等待滚动,或重新评估上限与磁盘容量。

3. 工业边缘建议

边缘设备磁盘空间差异很大,建议同时设置:

  • 最大占用;
  • 最小保留空闲空间;
  • 最长保留时间;
  • 单文件大小;
  • 日志速率限制;
  • 磁盘使用率告警;
  • 关键错误外发或集中采集。

如果设备只有 4GB 存储,SystemMaxUse=2G 可能仍然过大;如果设备没有持久盘,应明确接受 volatile 日志,并通过网络转发关键日志。

九、日志转发与平台集成

journalctl 负责查询和管理本地 Journal。工业现场通常还需要把关键日志送到集中平台。

常见方式:

方式 适用场景 注意点
ForwardToSyslog=yes 已有 rsyslog/syslog 体系 需要确保接收端和队列可靠
systemd-journal-remote 使用 systemd 原生远程 Journal 需要证书、权限和存储规划
Fluent Bit / Vector 边缘侧采集并转发到 Kafka、Loki、ES 等 需要处理断网缓存与背压
定时打包归档 离线排障和合规留存 需要加密、校验和清理策略

边缘设备网络不稳定时,转发层要明确:

  • 本地缓存多久;
  • 磁盘满时丢弃什么;
  • 重启后是否续传;
  • 重复日志如何去重;
  • 敏感字段如何脱敏;
  • 时间同步是否可靠。

不要只做"日志能发出去",还要保证断网期间本地仍有可查现场。

十、工程实践

1. 查询先窄后宽

推荐顺序:

text 复制代码
确定 boot / 时间窗口
→ 按 unit 或 SYSLOG_IDENTIFIER 过滤
→ 按 priority 过滤
→ 按结构化字段过滤
→ 必要时再 --grep

直接 journalctl | grep 会先扫大量日志,效率低,也容易把上下文过滤掉。

2. 业务日志结构化

建议固定这些字段:

字段 示例 说明
SYSLOG_IDENTIFIER edge-gateway 稳定来源标识
SITE_ID SH-01 站点
DEVICE_ID dev-001 设备
EVENT device_offline 业务事件名
TRACE_ID 7f2c... 跨系统追踪
PRIORITY 3 严重度

字段值要控制基数,不要把高频变化的大字符串、完整报文或用户敏感信息直接作为字段。

3. 保留现场与控制成本并存

建议分级保留:

  • 错误和告警:保留更久或外发;
  • 业务审计:按合规要求保留;
  • 一般运行日志:短周期保留;
  • 调试日志:默认关闭,按需开启。

4. 做时间同步

日志时间不可信,排障价值会大幅下降。工业边缘设备应检查:

bash 复制代码
timedatectl
systemctl status systemd-timesyncd
chronyc tracking 2>/dev/null || true

跨站点查询时建议统一使用 UTC 存储,展示层再按本地时区转换。

十一、常见坑与应对

坑 现象 应对
Journal 是 volatile 模式 重启后历史日志消失 启用 /var/log/journal 并设置 Storage=persistent
权限不足 只能看到少量日志 使用 sudo 或加入日志读取组
-p err 被误读 以为只看 err 记住它包含 err 及更严重级别
按脚本路径过滤无效 _EXE 是解释器路径 改用 unit、identifier 或结构化字段
logger 发 JSON 字符串 JSON 不是独立 Journal 字段 使用 --journald 或日志库结构化接口
日志无限增长 磁盘满导致服务异常 设置 MaxUse、KeepFree、Retention 并告警
vacuum 后占用没降 active 文件不能立即删除 先 rotate,并理解归档文件机制
刷日志触发限流 现场日志缺失 应用侧限速,journald 配置 RateLimit
直接 `journalctl grep` 扫描量过大且丢上下文
日志包含敏感信息 密钥或个人信息泄露 应用侧脱敏,采集侧二次过滤

十二、TL;DR

  • journalctl 是 systemd Journal 的标准查询工具;
  • 单独 -p err 包含 err 及更严重级别,精确匹配用 err..err;
  • 常用过滤维度:boot、时间、unit、kernel、priority、identifier、字段;
  • 输出给人看用 short-iso / short-iso-precise,给程序处理用 json;
  • JSON 字符串不是结构化 Journal 字段,使用 --journald 或应用日志库;
  • 重启后要保留日志,必须启用持久化;
  • vacuum 主要清归档文件,需配合 rotate 和容量策略;
  • 边缘设备应同时治理磁盘、权限、时间同步、速率限制和远程转发。

下一步建议

  1. 确认设备 Journal 存储模式与磁盘容量;
  2. 为核心服务统一 SyslogIdentifier 和结构化字段;
  3. 建立按服务、优先级和时间的排障命令模板;
  4. 配置持久化、容量上限和告警;
  5. 接入集中日志平台并验证断网续传能力。
相关推荐
Dory_Youth42 分钟前
电脑的搜索功能不能用,一搜就黑屏闪退
运维·笔记
Dxy12393102161 小时前
Linux 压缩解压相关命令介绍
linux·运维·服务器
eleven_4u1 小时前
QPS如何计算,如何部署架构
java·linux·运维·jvm·中间件
云飞云共享云桌面1 小时前
非标自动化工厂研发部门怎么实现一台服务器带10个SolidWorks设计并发
运维·服务器·人工智能·3d·自动化·电脑·制造
分布式存储与RustFS1 小时前
你的网站备份只存一块盘?把 1Panel 的备份目标换成 RustFS
运维·开源·备份·对象存储·1panel·s3·rustfs
遇见小修修1 小时前
打印机无法连接故障:操作误区汇总与排查教程
运维·服务器
东城居士2 小时前
理解Linux RTC驱动
linux·嵌入式系统
Elastic 中国社区官方博客2 小时前
使用 OpenTelemetry 在 Elastic 中实现 Claude 平台的人工智能 agent 可观测性
大数据·运维·服务器·人工智能·elasticsearch·信息可视化·全文检索
hzxxxz2 小时前
3个模块搭稳一条自动化链路:定时任务、幂等与对账
运维·自动化