awkによるスタックトレース等の複数行ログ解析:RSとgetlineによる構造化抽出の自動化
【導入と前提】
Java等のスタックトレースや複数行に渡るエラーログを、awkのRS(レコード区切り文字)を用いて1つの論理レコードとして扱い、構造化データへ集約・自動処理します。
- 実行環境: GNU/Linux (Ubuntu 22.04+, RHEL 8+), GNU awk (gawk) 4.0+, jq
【処理フローと設計】
graph TD A["Raw Multi-line Logs"] -->|awk RS/getline| B["Logical Record Extraction"] B -->|Format Processing| C["Structured JSON Output"] C -->|Pipeline| D["Log Dispatcher/Analyzer"] D -->|Error Detection| E["Systemd Journal/Alert"]
awkのレコードセパレータ(RS)に正規表現を用いることで、タイムスタンプ等のパターンでログを分割します。また、getlineを利用して特定行の直後にあるコンテキストを動的に取得し、フィルタリングの精度を高めます。
【実装:堅牢な自動化スクリプト】
以下のスクリプトは、複数行のログファイルを監視し、エラーブロックのみを抽出してJSON化するテンプレートです。
#!/usr/bin/env bash
# --- 安全設定 ---
set -euo pipefail
IFS=$'\n\t'
# --- 終了処理の定義 ---
cleanup() {
local exit_code=$?
# 一時ファイルの削除などをここに記述
exit "$exit_code"
}
trap cleanup EXIT
# --- メイン処理 ---
# ログファイルパス(引数またはデフォルト)
LOG_FILE="${1:-/var/log/app/error.log}"
if [[ ! -f "$LOG_FILE" ]]; then
echo "Error: Log file not found: $LOG_FILE" >&2
exit 1
fi
# awkによる複数行処理
# RS: タイムスタンプ [YYYY-MM-DD ...] で始まる位置をレコードの区切りとする
# RT: GNU awk特有。RSにマッチした文字列を保持
parse_logs() {
awk '
BEGIN {
# レコード区切りをタイムスタンプ直前の空行またはパターンに設定 (GNU awk)
RS = "(^|\n)\\[[0-9]{4}-[0-9]{2}-[0-9]{2}";
OFS = " ";
}
{
# 空のレコードをスキップ
if (NR == 1 && $0 == "") next;
# 1行目からメッセージ内容を取得
# RT(デリミタ自体)と現在のレコード内容を結合
content = RT $0;
# 特定のキーワード(ERRORなど)が含まれる場合のみ抽出
if (content ~ /ERROR/ || content ~ /Exception/) {
# JSON形式への整形準備
gsub(/"/, "\\\"", content); # ダブルクォートのエスケープ
gsub(/\n/, "\\n", content); # 改行の文字列化
print content;
}
}
' "$LOG_FILE"
}
# パイプライン処理: awk結果をjqでJSON配列として集約
process_to_json() {
parse_logs | jq -R -s '
split("\n")
| map(select(length > 0))
| { "log_entries": ., "count": length, "timestamp": (now | strftime("%Y-%m-%dT%H:%M:%SZ")) }
'
}
process_to_json
systemd ユニットファイル例 (/etc/systemd/system/log-watcher.service)
ログ解析をバックグラウンドで常駐させる場合の構成案です。
[Unit] Description=Log Parser Service After=network.target [Service] Type=simple ExecStart=/usr/local/bin/log-parser.sh /var/log/myapp.log Restart=always RestartSec=10 StandardOutput=append:/var/log/parsed_errors.json StandardError=journal [Install] WantedBy=multi-user.target
【検証と運用】
正常系確認: サンプルのスタックトレースを含むファイルを読み込ませ、出力が1つのJSONオブジェクトにまとまっているか確認します。
./log-parser.sh sample.log | jq .count
ログ確認: systemd経由で実行している場合、稼働状況は
journalctlで追跡します。journalctl -u log-watcher.service -f
【トラブルシューティングと落とし穴】
RSの正規表現互換性: 標準の
awk(mawk等) ではRSに1文字しか指定できない場合があります。本スクリプトはgawk(GNU awk) を前提としています。メモリ消費:
jq -s(slurp) は入力を全てメモリに読み込むため、巨大すぎるログファイルに対してはjq -c(Line-delimited JSON) で逐次処理する構成に変更してください。権限設定:
/var/log以下のファイルを読む場合、スクリプト実行ユーザーに読み取り権限が必要です。sudo経由で実行するか、適切なグループに追加してください。
【まとめ】
運用の冪等性と堅牢性を維持するためのポイント:
正規表現によるレコード境界の定義:
RSを活用し、行単位ではなく「意味のあるブロック」単位で処理を完結させる。パイプラインの安全停止:
set -o pipefailを用い、途中のawkやjqでエラーが発生した際にスクリプト全体を停止させる。構造化データへの早期変換: テキストのまま引き回さず、早い段階で
jq等を用いてJSON化することで、後続の監視ツールとの親和性を高める。

