mcpField
L
log-ops

log-ops

github.com/0xdarkmatterデータ・データベース7836 回インストール2 週間前に更新
リポジトリ

log-ops について

log-opsは、特にJSONL形式のログを検索、抽出、変換、照合する実践的な手法をまとめたスキルです。開発者や運用担当者が、小規模から大規模までのログに適したコマンドラインツールや手順を選ぶのに役立ちます。

主な機能

  1. 01rgによるテキストログ検索
  2. 02jqによるJSONLの抽出と変換
  3. 03構造化ログデータの集計
  4. 04複数ファイル間のイベント照合
  5. 05時間範囲での絞り込みと時系列再構成
  6. 06ストリーミングや分割による大容量ログ処理

活用シーン

  1. 01アプリケーションログからエラーを検索
  2. 02エージェント会話のJSONLから項目を抽出
  3. 03サービスログをまたいでリクエストを追跡
  4. 04全件を読み込まずに大容量ログを分析

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/0xdarkmatter/claude-mods --skill log-ops

インストール後、Claude Code で/log-opsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
.gitkeep
references/
analysis-workflows.md
jsonl-patterns.md
tool-setup.md
scripts/
.gitkeep

詳細

ログ操作

ログファイルを分析するための実践的なパターンです。特に、エージェントの会話ログ、ベンチマーク出力、構造化アプリケーションログで使われる 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 ripgrep
  • jq - JSON/JSONL の抽出と変換。インストール:brew install jq / choco install jq

任意(機能が強化されます。なくても適切に動作します):

  • lnav - SQL クエリを使った対話的なログの調査。インストール:brew install lnav / WSL:apt install lnav
  • agrind(angle-grinder)- パイプライン集計構文。インストール:cargo install ag
  • mlr(Miller)- CSV/TSV ログ分析。インストール:brew install miller / choco install miller
  • GNU parallel - 分割ファイルの並列処理。インストール:brew install parallel

このスキルのすべてのパターンは、rg と jq だけで動作します。任意のツールを追加すると、対話的な調査(lnav)、パイプライン集計(agrind)、表形式データの分析(mlr)が可能になります。

ツール選択マトリクス

ツール最適な用途速度必須?
rg(ripgrep)あらゆる形式の生パターンマッチング最速はい
jqJSONL の構造化データ抽出と変換高速はい
jq -sJSONL の集計(全行を配列に読み込み)中速(すべてをメモリに読み込みます)はい(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 クイックリファレンス

構造化ログで最も一般的な形式です。1 行につき JSON オブジェクトが 1 つで、末尾のカンマはなく、配列で囲みません。

ストリームのフィルタリング(1 行ずつ、メモリ使用量一定)
# 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
集計(スラープが必要:ファイル全体を読み込みます)
# 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
ネストされたデータの抽出(エージェントのログ、複雑な構造)
# 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
2 段階のパイプライン(高速化に 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
常に 2 段階で処理
# 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を超えるファイルでは、OOM クラッシュやスワップの過剰な発生につながりますストリーミングを使います。rg による事前フィルタリング、jq --stream、または split と並列処理を使います
文字列値に改行が含まれる JSONL行単位で処理するツール(rg、awk、head)は、1つのレコードを複数行に分割してしまいますjq -c で再コンパクト化するか、jq -R 'fromjson?' で不正な行をスキップします
rg は値だけでなく JSON キーにも一致しますrg "error" は {"error_count": 0} にも一致しますが、これはエラーではありませんrg '"level":"error"' を使うか、パイプで jq 'select(.level == "error")' に渡します
タイムスタンプ比較でのタイムゾーンの不一致イベントの順序が不自然になったり、時間範囲からデータが漏れたりします比較の前に UTC に正規化します:`jq '.timestamp
ログメッセージ内の Unicode とエスケープシーケンス不正な UTF-8 や二重エスケープされた文字列により、jq がエラーになりますrg -a(バイナリモード)で事前フィルタリングするか、生文字列には jq -R を使います
ログ行ごとに JSON スキーマが一貫していない想定されるフィールドがない行で jq がエラーになりますデフォルト値には // 演算子を使います:.field // "missing"。任意の値には ? を使います:.arr[]?
JSONL で jq に -c フラグを付け忘れるjq が各行を整形出力するため、出力が有効な JSONL ではなくなります出力を別の JSONL コンシューマーに渡す場合は、必ず jq -c を使います
jq で tail -f を使う際のバッファリング出力が遅れて表示されたり、まったく表示されなかったりしますjq --unbuffered または stdbuf -oL jq を使います
スラープせずに 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.mdJSONL の抽出、集計、変換、比較、パフォーマンスのパターン約700
references/analysis-workflows.mdエージェントの会話分析、アプリケーションログ分析、ベンチマーク結果の解析、ディレクトリ横断のワークフロー約600
references/tool-setup.mdjq、lnav、angle-grinder、rg、awk、GNU parallel、Miller のインストールと設定約450

関連項目

  • data-processing -- jq と yq による JSON/YAML/TOML の処理
  • debug-ops -- 体系的なデバッグ手法、ログベースのデバッグに関するセクション
  • monitoring-ops -- 本番環境のオブザーバビリティ、アラート、ダッシュボード
  • file-search -- fd によるファイル検索、rg によるコード検索

コメント