在脚本中使用 journalctl 查询 journald
按单元、优先级和时间筛选 systemd 日志条目,实现自动化事件初步分析。
在脚本中使用 journalctl 查询 journald 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
为什么使用 journald 进行事件初步排查?
运行 systemd 的现代 Linux 系统会将所有日志输出集中到日志中 — 这是由 systemd-journald 管理的结构化二进制日志存储。与 /var/log 中的普通文本文件不同,每条日志记录都带有丰富的元数据:单元名称、优先级、PID、UID、时间戳等。
在自动化事件初步排查脚本中,这些元数据可以让您:
- 无需串联
grep,即可将日志筛选到单个服务 - 将查询限定在精确的时间窗口内(最近 15 分钟、从部署开始)
- 仅输出关键或错误消息,忽略噪声
- 将结构化输出直接送入告警流水线
提供这些功能的工具是 journalctl。本课程将教您如何在 BASH 脚本中以编程方式驱动它。
journalctl 的基本调用
最简单的 journalctl 形式会转储整个日志。在脚本中,您几乎从不需要这样做 — 至少始终添加一个筛选条件。下面是最常组合使用的选项:
-u <unit>— 按 systemd 单元筛选(例如nginx.service)-p <priority>— 按 syslog 优先级筛选(0=emerg … 7=debug)--since/--until— 时间窗口-n <N>— 最后 N 行--no-pager— 禁用交互式分页(脚本中必不可少)-o <format>— 输出格式(short、json、cat等)
在非交互式脚本中始终传入 --no-pager,这样 journalctl 就不会尝试调用 less 而导致挂起。
#!/usr/bin/env bash
# Print the last 20 lines of the nginx service journal
journalctl --no-pager -u nginx.service -n 20按 systemd 单元筛选
-u 选项接受任何有效的单元名称。您可以多次提供该选项来组合多个单元;当一个应用程序分布在多个服务中时(例如一个 API 及其数据库辅助服务),这会很有用。
单元名称遵循 <name>.service、<name>.socket、<name>.timer 等模式。支持通配:-u 'myapp*' 可以匹配 myapp-api.service、myapp-worker.service 等名称。
在事件初步排查脚本中,您通常会将单元名称作为参数传入,从而使筛选条件保持动态。
#!/usr/bin/env bash
# Usage: ./unit_logs.sh nginx.service
UNIT="${1:?Usage: $0 <unit>}"
echo "=== Journal for ${UNIT} (last 50 lines) ==="
journalctl --no-pager -u "${UNIT}" -n 50
# Combine two related units
echo "=== Combined: api + worker ==="
journalctl --no-pager -u myapp-api.service -u myapp-worker.service -n 30优先级级别与 -p 标志
-p 标志对应标准 syslog 优先级级别:
0— emerg1— alert2— crit3— err4— warning5— notice6— info7— debug
您可以指定单个级别(-p err)以仅查看该级别,也可以指定范围(-p emerg..err)以捕获从紧急到错误的所有消息——这是自动告警中最常用的选择。
除了数值,还可以使用命名别名(err、warning、crit)。
#!/usr/bin/env bash
# Extract only error-level and above entries for sshd
journalctl --no-pager \
-u sshd.service \
-p emerg..err \
--since "1 hour ago"
# Exit non-zero if any errors were found (useful in CI health checks)
ERROR_COUNT=$(journalctl --no-pager -u sshd.service -p emerg..err \
--since "1 hour ago" --output=cat | wc -l)
if [[ "${ERROR_COUNT}" -gt 0 ]]; then
echo "[ALERT] ${ERROR_COUNT} error(s) detected in sshd" >&2
exit 1
fi使用 --since 和 --until 按时间窗口过滤
时间过滤器是按故障时间窗口查询的基础。journalctl 接受灵活的、易于人类阅读的时间戳:
- 相对时间:
"10 minutes ago"、"2 hours ago"、"yesterday" - 绝对时间:
"2026-06-11 14:00:00" - 特殊关键字:
today、yesterday、-1h(简写)
在部署脚本中,一种常见模式是在部署前立即记录时间戳,然后从该时间点开始查询日志,以检测此版本引入的回归问题。
#!/usr/bin/env bash
# Record deploy start time, then check logs afterwards
DEPLOY_START=$(date +"%Y-%m-%d %H:%M:%S")
echo "Deploying at ${DEPLOY_START}..."
# ... your deploy steps here ...
sleep 2 # simulate deploy
echo "=== Journal since deploy start ==="
journalctl --no-pager \
-u myapp.service \
--since "${DEPLOY_START}" \
-p emerg..warning使用 JSON 格式输出结构化数据
对于机器可读的管道,请传入 -o json(每行一个 JSON 对象,NDJSON)或 -o json-pretty(格式化输出)。每个对象都包含日志的所有字段:
MESSAGE— 日志文本PRIORITY— 数值优先级(0–7)_SYSTEMD_UNIT— 产生该日志的单元__REALTIME_TIMESTAMP— 自纪元以来的微秒数_PID、_UID、_HOSTNAME— 进程元数据
您可以将此 NDJSON 流通过管道传给 jq,为下游告警系统(例如 PagerDuty、Slack webhook 或 SIEM 数据采集器)提取、过滤或重新格式化字段。
#!/usr/bin/env bash
# Extract error messages as a clean list for a Slack notification
MESSAGES=$(journalctl --no-pager \
-u nginx.service \
-p emerg..err \
--since "30 minutes ago" \
-o json \
| jq -r '.MESSAGE' \
| sort -u)
if [[ -n "${MESSAGES}" ]]; then
echo "Errors detected:"
echo "${MESSAGES}"
fi实时跟踪日志
-f 标志让 journalctl 实时跟踪日志,类似于对日志文件执行 tail -f。结合单元和优先级过滤器后,它就能成为一个有针对性的实时监视器。
在脚本化管道中,更实用的模式是基于游标的轮询:保存当前日志游标,然后在每次轮询时传入 --after-cursor=<cursor>,只读取自上次检查以来的新条目。这样可以避免重复处理旧行。
使用 --show-cursor -n 0 获取最新游标,并从输出中解析 -- cursor: 行。
#!/usr/bin/env bash
# Cursor-based polling: read only new journal entries each run
CURSOR_FILE="/tmp/triage_cursor"
if [[ -f "${CURSOR_FILE}" ]]; then
CURSOR=$(cat "${CURSOR_FILE}")
NEW_ENTRIES=$(journalctl --no-pager \
-u myapp.service \
-p emerg..err \
--after-cursor="${CURSOR}" \
-o json)
else
# First run: look back 5 minutes
NEW_ENTRIES=$(journalctl --no-pager \
-u myapp.service \
-p emerg..err \
--since "5 minutes ago" \
-o json)
fi
# Save updated cursor for next poll
journalctl --no-pager -n 0 --show-cursor 2>&1 \
| grep '^-- cursor:' \
| sed 's/-- cursor: //' \
> "${CURSOR_FILE}"
echo "${NEW_ENTRIES}" | jq -r '.MESSAGE // empty'使用 -b 查询指定启动会话
-b 标志将查询限定到特定的启动会话。发生崩溃或意外重启后,这对于获取上一次启动的日志而不是当前启动的日志非常重要。
-b 0— 当前启动(默认)-b -1— 上一次启动-b -2— 上上次启动--list-boots— 显示所有已记录的启动会话及其时间戳
事后分析脚本通常会转储上一次启动(-b -1)中的关键日志,以诊断系统崩溃或服务启动失败的原因。
#!/usr/bin/env bash
# Post-mortem: collect critical logs from the previous boot
echo "=== Previous boot sessions ==="
journalctl --list-boots
echo ""
echo "=== Critical entries from previous boot ==="
journalctl --no-pager \
-b -1 \
-p emerg..crit \
-o short-iso在 journalctl 中使用 grep 与原生匹配
您可以在所有标志之后传入原始的 grep 模式,但 journalctl 还支持使用 FIELD=value 语法进行原生字段匹配。原生匹配会针对结构化元数据进行判断,比使用 grep 对文本进行后处理快得多。
一些常用的匹配方式:
_PID=1234— 来自特定进程的日志_COMM=python3— 来自所有名称为python3的进程的日志SYSLOG_IDENTIFIER=myapp— 带有自定义标识符标签的日志
多个 FIELD=value 参数之间采用 AND 关系;在它们之间加入 + 则会创建 OR 关系。当结构化元数据不足以满足需求时,请使用 -g <regex> 进行全文 grep。
#!/usr/bin/env bash
# Native field match: errors from the postgres process only
journalctl --no-pager \
_COMM=postgres \
-p emerg..err \
--since "1 hour ago"
# Full-text grep for a specific error string
journalctl --no-pager \
-u postgresql.service \
--since "1 hour ago" \
-g "FATAL|PANIC" \
--output=cat构建可复用的故障分诊函数
掌握各个标志后,将它们组合成可复用的 Bash 函数,可以让您的故障分诊脚本保持简洁一致。设计良好的函数应当:
- 接受单元、优先级范围和时间窗口作为参数
- 省略参数时采用安全且低噪声的默认值
- 发现错误时返回非零退出代码(以便与 CI 管道集成)
- 将发现结果同时写入标准输出和带时间戳的日志文件,以便进行审计
#!/usr/bin/env bash
# triage.sh — reusable journal triage function
triage_unit() {
local unit="${1:?unit required}"
local priority="${2:-emerg..err}"
local since="${3:-1 hour ago}"
local logfile="/tmp/triage_${unit//[^a-zA-Z0-9]/_}_$(date +%s).log"
echo "[$(date -Iseconds)] Triaging ${unit} | prio=${priority} | since='${since}'" | tee "${logfile}"
journalctl --no-pager \
-u "${unit}" \
-p "${priority}" \
--since "${since}" \
-o short-iso \
| tee -a "${logfile}"
local count
count=$(wc -l < "${logfile}")
# Subtract 1 for the header line
(( count-- ))
if [[ "${count}" -gt 0 ]]; then
echo "[ALERT] ${count} line(s) logged to ${logfile}" >&2
return 1
fi
return 0
}
# Example: triage nginx errors in the last 30 minutes
triage_unit nginx.service "emerg..err" "30 minutes ago"自动化故障分诊脚本
下面的完整脚本将所有概念组合成一个实用的自动化故障分诊工具。它读取关键服务列表,在可配置的回溯时间窗口内逐个查询日志,汇总发现结果;如果检测到任何错误,则以失败代码退出,因此适合用作 cron 任务或 CI 健康检查步骤。
#!/usr/bin/env bash
# incident_triage.sh — automated multi-service journal triage
set -euo pipefail
LOOKBACK="${1:-15 minutes ago}"
PRIORITY="emerg..err"
SERVICES=(nginx.service postgresql.service myapp-api.service myapp-worker.service)
REPORT="/tmp/incident_report_$(date +%Y%m%d_%H%M%S).txt"
FAILED=0
{
echo "Incident Triage Report"
echo "Generated : $(date -Iseconds)"
echo "Lookback : ${LOOKBACK}"
echo "Priority : ${PRIORITY}"
echo "-----------------------------------"
} > "${REPORT}"
for svc in "${SERVICES[@]}"; do
ENTRIES=$(journalctl --no-pager \
-u "${svc}" \
-p "${PRIORITY}" \
--since "${LOOKBACK}" \
--output=cat 2>/dev/null || true)
COUNT=$(echo "${ENTRIES}" | grep -c . || true)
if [[ "${COUNT}" -gt 0 ]]; then
echo "[FAIL] ${svc}: ${COUNT} error(s)" | tee -a "${REPORT}"
echo "${ENTRIES}" >> "${REPORT}"
echo "-----------------------------------" >> "${REPORT}"
FAILED=1
else
echo "[OK] ${svc}"
fi
done
echo ""
echo "Full report: ${REPORT}"
exit "${FAILED}"知识检查:按优先级范围过滤
您正在编写一个脚本,只有当服务记录错误级别或更严重的消息时,才向值班工程师发出告警(即错误、严重、告警或紧急)。哪种 journalctl 标志组合可以准确捕获这一范围?
课程回顾:脚本中的 journalctl
在本课中,您学习了如何以编程方式查询 systemd 日志,以实现自动化故障分诊:
- 在脚本中始终传入
--no-pager,以防止交互式阻塞。 - 单元过滤(
-u)将查询限定到一个或多个服务;支持通配符和多个-u标志。 - 优先级过滤(
-p emerg..err)只捕获您关注的严重级别——请记住,数值越小表示越严重。 - 时间窗口(
--since/--until)使用易于人类阅读的时间戳,将日志输出限定在部署窗口或回溯时间段内。 - 启动范围(
-b -1)让事后分析脚本能够读取上一次崩溃会话中的日志。 - JSON 输出(
-o json)和jq支持结构化管道,为告警系统或 SIEM 系统提供数据。 - 使用
--after-cursor进行基于游标的轮询,避免在重复运行时重新处理旧条目。 - 原生字段匹配(
_COMM=、SYSLOG_IDENTIFIER=)比通过管道传给grep更快。
将这些标志组合到可复用的 Bash 函数中,您就能得到一个生产级故障分诊工具,并将其与 cron、CI 管道和服务值班告警流程顺畅集成。
常见问题解答
「在脚本中使用 journalctl 查询 journald」课时是免费的吗?
是的 — 「在脚本中使用 journalctl 查询 journald」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
「在脚本中使用 journalctl 查询 journald」这节课中我会学到什么?
按单元、优先级和时间筛选 systemd 日志条目,实现自动化事件初步分析。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「在脚本中使用 journalctl 查询 journald」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?
能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 大规模解析 Web 与应用日志
- 实时跟踪日志与流式告警
- 在脚本中使用 journalctl 查询 journald
- 从日志流计算指标与直方图