{“status”: “production-ready”, “engine”: “gemini-1.5-pro”, “focus”: “SRE/Log-Analysis”, “technique”: [“awk-RS”, “awk-getline”, “structured-logging”]}
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
分散システムのトラブルシューティングを加速する:awkによる複数行ログの自動抽出と構造化
【導入と前提】 JavaスタックトレースやSQLスロークエリ等、複数行にわたる非構造化ログを特定のパターンで抽出し、JSON形式へ整形して監視基盤へ転送する処理を自動化します。
- 前提環境: GNU/Linux (Ubuntu/RHEL), gawk (GNU awk) 4.0+, jq, systemd
【処理フローと設計】
graph TD
A["ログファイル監視"] --> B{"awkによるレコード分離"}
B -->|RS: レコード区切り| C["複数行パターンの抽出"]
C -->|getline: 次行結合| D["一時オブジェクト生成"]
D -->|jq| E["構造化JSON出力"]
E --> F["監視基盤/外部通知"]
この設計では、RS (Record Separator) を正規表現で定義することで、1つのログエントリ(タイムスタンプから次のタイムスタンプまで)を単一の「レコード」として扱います。
【実装:堅牢な自動化スクリプト】 以下のスクリプトは、アプリケーションログからエラー発生時のスタックトレースを含むコンテキストを抽出し、構造化します。
#!/bin/bash
# log_processor.sh - 複数行ログの抽出とJSON化
set -euo pipefail # エラー発生時に停止、未定義変数参照禁止、パイプエラーの伝播
IFS=$'\n\t'
# 設定
LOG_FILE="/var/log/app/production.log"
OUTPUT_JSON="/var/log/app/error_report.json"
TMP_FILE=$(mktemp)
# クリーンアップ処理
trap 'rm -f "$TMP_FILE"' EXIT
echo "Starting log extraction at $(date)"
# gawkによる複数行抽出処理
# RS (Record Separator) にタイムスタンプのパターンを指定し、1エントリを1レコードとして処理
gawk '
BEGIN {
# 2024-05-20 10:00:00 のような形式の行頭を区切り文字に設定 (gawk拡張)
RS = "\n[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"
}
/ERROR|CRITICAL/ {
# RT (Record Terminator) は一致した区切り文字を保持している
timestamp = gensub(/^\n/, "", "g", RT)
content = $0
# 特殊なパターンがあった場合、getlineでさらに後続を読み込む例
if (content ~ /SpecificException/) {
# 必要に応じて追加行を取得可能
# getline extra_line
# content = content "\n" extra_line
}
# JSONの中間形式として整形出力
printf "%s\t%s\n", (timestamp == "" ? "INIT_START" : timestamp), content
}' "$LOG_FILE" > "$TMP_FILE"
# jqによるJSON構造化とファイル出力
cat "$TMP_FILE" | jq -R -s '
split("\n") | map(select(length > 0)) | map(
split("\t") as $parts |
{
timestamp: $parts[0],
level: (if $parts[1] | contains("CRITICAL") then "CRITICAL" else "ERROR" end),
message: $parts[1] | ltrimstr(" ")
}
)
' > "$OUTPUT_JSON"
# curlで外部APIへ通知する例 (コメントアウト)
# curl -X POST -H "Content-Type: application/json" -d @"$OUTPUT_JSON" -L -s https://api.monitoring.example.com/ingest
定期実行のためのsystemd設定例:
/etc/systemd/system/log-parser.timer
[Unit] Description=Run log processor every 5 minutes [Timer] OnCalendar=*:0/5 Persistent=true [Install] WantedBy=timers.target
【検証と運用】
正常系確認:
tail -f /var/log/app/error_report.json | jq .を実行し、スタックトレース全体が一つのmessageフィールドに格納されているか確認します。ログ確認: 実行ログは systemd 経由で取得します。
journalctl -u log-parser.service
【トラブルシューティングと落とし穴】
メモリ消費量:
RSを使って巨大なスタックトレースを読み込む場合、awk プロセスがメモリを消費します。数MB単位の巨大な単一レコードが予想される場合は、getlineをループで回し、バッファを制限する設計を検討してください。RSの挙動差異: BSD系の
awkと GNUのgawkではRSに正規表現が使えるかどうかの差異があります。本スクリプトはgawkを前提としています。権限不足:
/var/log配下の読み取りには通常sudoが必要です。systemd ユニットファイル内でUser=log-reader等の専用ユーザーを定義し、対象ログへのACLを設定することを推奨します。
【まとめ】 運用の冪等性を維持するための3つのポイント:
ステート管理: 処理済みログのオフセット(inodeとバイト位置)を記録し、二重読み込みを防止する。
スキーマの分離:
awkでの抽出処理とjqでの整形処理を分離し、ログ形式変更への耐性を高める。安全な一時ファイル:
trapコマンドによる確実なクリーンアップを行い、ディスク逼迫を防止する。


コメント