0Pricing
Linux Command Line & Bash Scripting Mastery · 课时

在脚本中使用 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 — emerg
  • 1 — alert
  • 2 — crit
  • 3 — err
  • 4 — warning
  • 5 — notice
  • 6 — info
  • 7 — 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 大规模解析 Web 与应用日志
  2. 实时跟踪日志与流式告警
  3. 在脚本中使用 journalctl 查询 journald
  4. 从日志流计算指标与直方图
← 返回 Linux Command Line & Bash Scripting Mastery