关于 log-ops
log-ops 是一项实用技能,提供搜索、筛选、转换和关联日志文件的方法,尤其适用于 JSONL。它帮助开发者和运维人员为不同规模的日志选择命令行工具与处理流程。
主要功能
- 01使用 rg 搜索文本日志
- 02使用 jq 提取和转换 JSONL
- 03聚合结构化日志数据
- 04关联多个文件中的事件
- 05按时间筛选并重建时间线
- 06通过流式处理或拆分处理大文件
适用场景
- 01在应用日志中查找错误
- 02提取 Agent 对话 JSONL 的字段
- 03跨服务日志追踪请求
- 04无需全部载入即可分析大型日志
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ 0xdarkmatter/ claude-mods --skill log-ops安装后在 Claude Code 中输入/log-ops确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/.gitkeepreferences/analysis-workflows.mdjsonl-patterns.mdtool-setup.mdscripts/.gitkeep详细介绍
日志操作
分析日志文件的实用模式——尤其是 Agent 对话日志、基准测试输出和结构化应用日志中使用的 JSONL 格式。
日志格式决策树
Unknown Log File
│
├─ Is it one JSON object per line?
│ ├─ Yes ──────────────────────── JSONL
│ │ ├─ Small file (<100MB)
│ │ │ └─ jq for extraction, jq -s for aggregation
│ │ ├─ Large file (100MB-1GB)
│ │ │ └─ rg prefilter then pipe to jq
│ │ └─ Huge file (>1GB)
│ │ └─ split + parallel jq, or jq --stream
│ │
│ └─ No
│ ├─ Is it one large JSON object/array?
│ │ └─ Yes ──────────────── Single JSON
│ │ └─ jq --stream for SAX-style, or jq directly if fits in memory
│ │
│ ├─ Does it have key=value pairs?
│ │ └─ Yes ──────────────── Structured (logfmt / key-value)
│ │ └─ rg for search, awk/sd for extraction, angle-grinder for aggregation
│ │
│ ├─ Does it follow syslog format? (timestamp hostname service[pid]: message)
│ │ └─ Yes ──────────────── Syslog
│ │ └─ rg for search, awk for column extraction, lnav for interactive
│ │
│ ├─ Is it space/tab delimited with consistent columns?
│ │ └─ Yes ──────────────── Column-based (access logs, CSV)
│ │ └─ awk for extraction, mlr for CSV, rg for pattern search
│ │
│ └─ Mixed or unstructured
│ └─ Plain text ─────────── Freeform
│ └─ rg for search, rg -A/-B for context, lnav for exploration
前置条件
必需(必须安装):
rg(ripgrep)——文本搜索、预筛选。安装:cargo install ripgrep/choco install ripgrepjq——JSON/JSONL 提取与转换。安装:brew install jq/choco install jq
可选(增强功能;未安装时可正常降级):
lnav——使用 SQL 交互式探索日志。安装:brew install lnav/ WSL:apt install lnavagrind(angle-grinder)——管道聚合语法。安装:cargo install agmlr(Miller)——CSV/TSV 日志分析。安装:brew install miller/choco install millerGNU parallel——并行处理拆分后的文件。安装:brew install parallel
本技能中的所有模式只需
rg和jq即可使用。可选工具可提供交互式探索(lnav)、管道聚合(agrind)和表格分析(mlr)。
工具选择矩阵
| 工具 | 最适用场景 | 速度 | 是否必需? |
|---|---|---|---|
rg(ripgrep) | 任意格式的原始模式匹配 | 最快 | 是 |
jq | JSONL 结构化提取与转换 | 快 | 是 |
jq -s | JSONL 聚合(将所有行读入数组) | 中等(会将所有内容载入内存) | 是(属于 jq) |
lnav | 交互式探索、对日志运行 SQL | 交互式 | 可选 |
agrind(angle-grinder) | 管道聚合与计数 | 快 | 可选 |
awk | 基于列的日志格式、字段提取 | 快 | 预装 |
mlr(Miller) | CSV/TSV 日志分析、统计 | 快 | 可选 |
fd + rg | 在多个日志目录中搜索 | 快 | 预装于 dev-shell |
GNU parallel | 拆分大文件以便并行处理 | 不适用(编排工具) | 可选 |
如何选择
Need to...
│
├─ Find lines matching a pattern
│ └─ rg (always fastest for text search)
│
├─ Extract specific fields from JSONL
│ └─ jq -r '[.field1, .field2] | @tsv'
│
├─ Count/aggregate over JSONL
│ └─ jq -sc 'group_by(.field) | map({key: .[0].field, n: length})'
│
├─ Search JSONL by value then format results
│ └─ rg '"error"' file.jsonl | jq -r '.message' (two-stage)
│
├─ Explore interactively with filtering/SQL
│ └─ lnav file.log
│
├─ Aggregate with pipeline syntax
│ └─ agrind '* | parse "* * *" as ts, level, msg | count by level'
│
├─ Extract columns from space-delimited logs
│ └─ awk '{print $1, $4, $7}' access.log
│
└─ Process CSV/TSV logs with headers
└─ mlr --csv filter '$status >= 400' then stats1 -a count -f status
JSONL 快速参考
结构化日志中最常见的格式。每行一个 JSON 对象,不加尾随逗号,也不使用外层数组。
流式筛选(逐行处理,内存占用恒定)
# Filter by field value
jq -c 'select(.level == "error")' app.jsonl
# Filter by nested field
jq -c 'select(.request.method == "POST")' app.jsonl
# Filter by multiple conditions
jq -c 'select(.level == "error" and .status >= 500)' app.jsonl
# Filter by array contains
jq -c 'select(.tags | index("critical"))' app.jsonl
# Filter by field existence
jq -c 'select(.stack_trace != null)' app.jsonl
# Negate a filter
jq -c 'select(.level != "debug")' app.jsonl
字段提取
# Extract single field
jq -r '.message' app.jsonl
# Extract multiple fields as TSV
jq -r '[.timestamp, .level, .message] | @tsv' app.jsonl
# Extract with default for missing fields
jq -r '.error_code // "none"' app.jsonl
# Extract nested field safely
jq -r '.response.headers["content-type"] // "unknown"' app.jsonl
聚合(需要使用 slurp:会载入整个文件)
# Count by field value
jq -sc 'group_by(.level) | map({level: .[0].level, count: length})' app.jsonl
# Top-N most common values
jq -sc '[.[].error_type] | group_by(.) | map({type: .[0], count: length}) | sort_by(-.count) | .[:10]' app.jsonl
# Sum a numeric field
jq -sc 'map(.duration_ms) | add' app.jsonl
# Average
jq -sc 'map(.duration_ms) | add / length' app.jsonl
# Min and max
jq -sc 'map(.duration_ms) | {min: min, max: max}' app.jsonl
嵌套提取(Agent 日志、复杂结构)
# Extract tool calls from conversation logs
jq -c '.content[]? | select(.type == "tool_use") | .name' conversation.jsonl
# De-escape nested JSON strings
jq -c '.content | fromjson' app.jsonl
# Flatten nested arrays
jq -c '[.events[]? | .action]' app.jsonl
# Extract from arrays of objects
jq -c '.results[]? | select(.passed == false) | {test: .name, error: .message}' results.jsonl
两阶段管道(用 rg 提速,用 jq 处理结构)
# Fast prefilter then structured extraction
rg '"error"' app.jsonl | jq -r '[.timestamp, .message] | @tsv'
# Search for specific value then aggregate
rg '"timeout"' app.jsonl | jq -sc 'length'
# Pattern match then extract
rg '"user_id":"u-123"' app.jsonl | jq -c '{ts: .timestamp, action: .action}'
按时间范围筛选
# Filter by timestamp range (ISO 8601 string comparison works)
jq -c 'select(.timestamp > "2026-03-08T10:00" and .timestamp < "2026-03-08T11:00")' app.jsonl
# Events in the last N minutes (using epoch seconds)
jq -c --arg cutoff "$(date -d '30 minutes ago' +%s)" 'select((.timestamp | sub("\\.[0-9]+Z$"; "Z") | fromdate) > ($cutoff | tonumber))' app.jsonl
# Extract hour for histogram
jq -r '.timestamp | split("T")[1] | split(":")[0]' app.jsonl | sort | uniq -c
跨文件关联
# Extract IDs from one file, search in another
jq -r '.request_id' errors.jsonl | while read id; do
rg "\"$id\"" responses.jsonl | jq -c '{id: .request_id, status: .status}'
done
# Faster: build lookup, then join
jq -r '.request_id' errors.jsonl | sort -u > /tmp/error_ids.txt
rg -Ff /tmp/error_ids.txt responses.jsonl | jq -c '{id: .request_id, status: .status}'
# Join two JSONL files by key using jq --slurpfile
jq --slurpfile lookup <(jq -sc 'map({(.id): .}) | add' lookup.jsonl) \
'. + ($lookup[0][.ref_id] // {})' main.jsonl
纯文本日志模式
带上下文的模式搜索
# Show 5 lines before and after each match
rg -B5 -A5 "OutOfMemoryError" app.log
# Show only matching files
rg -l "FATAL" /var/log/
# Count matches per file
rg -c "ERROR" /var/log/*.log | sort -t: -k2 -rn
# Multiline patterns (stack traces)
rg -U "Exception.*\n(\s+at .*\n)+" app.log
使用 awk 提取列
# Apache/nginx access log: extract status codes
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# Extract specific time range from syslog
awk '$0 >= "Mar 8 10:00" && $0 <= "Mar 8 11:00"' syslog
# Calculate average response time (column 11)
awk '{sum += $11; n++} END {print sum/n}' access.log
# Filter by status code and show URL + response time
awk '$9 >= 500 {print $7, $11"ms"}' access.log
实时监控
# Follow with filtering
tail -f app.log | rg --line-buffered "ERROR"
# Follow JSONL and extract fields
tail -f app.jsonl | jq --unbuffered -r '[.timestamp, .level, .message] | @tsv'
# Follow multiple files
tail -f /var/log/service-*.log | rg --line-buffered "error|warn"
时间线重建
提取并按时间戳排序
# Merge multiple log files by timestamp
sort -t' ' -k1,2 service-a.log service-b.log > timeline.log
# JSONL: sort by timestamp field
jq -sc 'sort_by(.timestamp)[]' combined.jsonl > sorted.jsonl
# Extract timestamps and calculate gaps
jq -r '.timestamp' app.jsonl | awk '
NR > 1 {
cmd = "date -d \"" prev "\" +%s"; cmd | getline t1; close(cmd)
cmd = "date -d \"" $0 "\" +%s"; cmd | getline t2; close(cmd)
gap = t2 - t1
if (gap > 5) print gap "s gap before " $0
}
{ prev = $0 }
'
# Quick duration between first and last event
jq -sc '{start: .[0].timestamp, end: .[-1].timestamp}' app.jsonl
计算事件间的持续时间
# Duration between paired events (start/end)
jq -sc '
group_by(.request_id) |
map(
(map(select(.event == "start")) | .[0].timestamp) as $start |
(map(select(.event == "end")) | .[0].timestamp) as $end |
{id: .[0].request_id, start: $start, end: $end}
)
' events.jsonl
# Identify the slowest phase
jq -sc '
sort_by(.timestamp) |
[range(1; length) | {
from: .[.-1].event,
to: .[.].event,
gap: ((.[.].ts_epoch) - (.[.-1].ts_epoch))
}] |
sort_by(-.gap) | .[0]
' events.jsonl
跨日志关联
按关联 ID
# Find a request across all service logs
fd -e jsonl . /var/log/services/ -x rg "\"req-abc-123\"" {}
# Build a timeline for a single request
fd -e jsonl . /var/log/services/ -x rg "\"req-abc-123\"" {} \; | jq -sc 'sort_by(.timestamp)[] | [.timestamp, .service, .event] | @tsv'
按时间戳范围
# Find events within 2 seconds of a known event
# First get the target timestamp
TARGET="2026-03-08T14:23:15"
jq -c --arg t "$TARGET" '
select(
.timestamp > ($t | sub("15$"; "13")) and
.timestamp < ($t | sub("15$"; "17"))
)
' other-service.jsonl
按会话/用户
# Reconstruct a user session across log files
fd -e jsonl . /var/log/ -x rg "\"user-42\"" {} \; |
jq -sc 'sort_by(.timestamp)[] | [.timestamp, .service, .action] | @tsv'
大文件处理策略
仅搜索最近内容
# Last 10,000 lines (fast for append-only logs)
tail -n 10000 huge.log | rg "pattern"
# Last N lines of JSONL with structured extraction
tail -n 5000 huge.jsonl | jq -c 'select(.level == "error")'
拆分文件以便并行处理
# Split into 100K-line chunks
split -l 100000 huge.jsonl /tmp/chunk_
# Process in parallel
fd 'chunk_' /tmp/ -x jq -c 'select(.level == "error")' {} > errors.jsonl
# With GNU parallel
split -l 100000 huge.jsonl /tmp/chunk_
ls /tmp/chunk_* | parallel 'jq -c "select(.level == \"error\")" {} >> /tmp/errors.jsonl'
对超大的单个 JSON 文件进行流式处理
# SAX-style processing of a huge JSON array
jq --stream 'select(.[0][0] == "results" and .[0][-1] == "status") | .[1]' huge.json
# Extract items from a huge array without loading all
jq -cn --stream 'fromstream(1 | truncate_stream(inputs))' huge-array.json
始终采用两阶段处理
# ALWAYS faster: rg filters text, jq parses survivors
rg '"error"' huge.jsonl | jq -r '.message'
# vs. SLOW: jq reads and parses every line
jq -r 'select(.level == "error") | .message' huge.jsonl
跨目录搜索
多目录模式
# Find all JSONL files with errors across trial directories
fd -e jsonl . trials/ -x rg -l '"error"' {}
# Count errors per log file across directories
fd -e jsonl . trials/ -x bash -c 'echo "$(rg -c "\"error\"" "$1" 2>/dev/null || echo 0) $1"' _ {}
# Extract and aggregate across directories
fd -e jsonl . trials/ -x jq -c 'select(.level == "error") | {file: input_filename, msg: .message}' {}
# Build summary table from multiple runs
for dir in trials/*/; do
total=$(wc -l < "$dir/results.jsonl")
errors=$(rg -c '"error"' "$dir/results.jsonl" 2>/dev/null || echo 0)
echo -e "$dir\t$total\t$errors"
done | column -t -N DIR,TOTAL,ERRORS
常见问题
| 问题 | 影响 | 解决方法 |
|---|---|---|
对超大文件使用 jq -s 会将所有内容加载到内存中 | 处理超过 ~500MB 的文件时会因内存不足而崩溃,或导致交换分区频繁读写 | 使用流式处理:先用 rg 预筛选,再用 jq --stream,或使用 split 后并行处理 |
| JSONL 字符串值中嵌入换行符 | 逐行处理的工具(rg、awk、head)会将一条记录拆分成多行 | 使用 jq -c 重新压缩,或使用 jq -R 'fromjson?' 跳过格式错误的行 |
| rg 匹配的是 JSON 键,而不只是值 | rg "error" 会匹配 {"error_count": 0},但这并不表示发生了错误 | 使用 rg '"level":"error"',或通过管道传给 jq 'select(.level == "error")' |
| 时间戳比较时的时区不一致 | 事件可能显示为乱序,或时间范围会漏掉数据 | 比较前统一转换为 UTC:`jq '.timestamp |
| 日志消息中的 Unicode 字符和转义序列 | jq 遇到无效的 UTF-8 或双重转义字符串时会报错 | 使用 rg -a(二进制模式)预筛选,或使用 jq -R 处理原始字符串 |
| 不同日志行的 JSON 架构不一致 | jq 遇到缺少预期字段的行时会报错 | 使用 // 运算符提供默认值:.field // "missing";使用 ? 处理可选字段:.arr[]? |
使用 jq 时忘记加 -c 标志 | jq 会将每行美化输出,导致输出不再是有效的 JSONL | 如果输出要传给另一个 JSONL 消费端,始终使用 jq -c |
jq 缓冲导致 tail -f 输出延迟 | 输出会延迟显示,或完全不显示 | 使用 jq --unbuffered 或 stdbuf -oL jq |
| 未使用 slurp 按时间戳对 JSONL 排序 | sort 命令会按整行进行字典序排序,而不是按字段排序 | 使用 jq -sc 'sort_by(.timestamp)[]',或先提取时间戳前缀 |
| 假设日志文件都是完整的 | 日志可能已轮转、已压缩,或仍在写入 | 检查轮转的 .gz 文件:fd -e gz . /var/log/ -x zcat {} | rg pattern |
| 在 Windows 上于 jq 中使用单引号 | PowerShell/cmd 对单引号的处理方式与 bash 不同 | 使用双引号并转义其中的引号,或将 jq 筛选器写入文件 |
参考文件
| 文件 | 内容 | 行数 |
|---|---|---|
references/jsonl-patterns.md | JSONL 提取、聚合、转换、比较和性能模式 | ~700 |
references/analysis-workflows.md | Agent 对话分析、应用程序日志分析、基准测试结果解析、跨目录工作流 | ~600 |
references/tool-setup.md | jq、lnav、angle-grinder、rg、awk、GNU parallel、Miller 的安装与配置 | ~450 |
另请参阅
- data-processing —— 使用 jq 和 yq 处理 JSON/YAML/TOML
- debug-ops —— 系统化调试方法、基于日志的调试章节
- monitoring-ops —— 生产环境可观测性、告警、仪表板
- file-search —— 使用 fd 查找文件,使用 rg 搜索代码
评论
支持 Markdown