awkのRSとgetlineを用いた複数行ログパターンの抽出とJSON構造化の自動化

Tech

{ “expert_role”: “SRE/DevOps Engineer”, “topic”: “Advanced awk techniques for multi-line log parsing”, “focus”: “RS and getline for structured data extraction”, “tools”: [“gawk”, “jq”, “systemd”, “bash”] } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

awkのRSとgetlineを用いた複数行ログパターンの抽出とJSON構造化の自動化

【導入と前提】

スタックトレース等の複数行に渡る非構造化ログを、awkのレコード制御技術を用いて解析し、後続の監視基盤が処理可能なJSON形式へ変換・自動転送します。

  • OS: GNU/Linux (gawk 4.0以降推奨)

  • ツール: awk (gawk), jq, systemd, curl

  • 前提: 対象ログが空行または特定のデリミタで区切られていること

【処理フローと設計】

graph TD
A["Raw Log File"] --> B{"awk Record Separator"}
B -->|RSでブロック分割| C["getlineで詳細行をスキャン"]
C --> D["一時配列にバッファリング"]
D --> E["JSON文字列として出力"]
E --> F["jqによるバリデーション"]
F --> G["監視エンドポイントへ転送"]

awkのRS(Record Separator)を空文字("")に設定することで、連続した空行をレコードの区切りとして扱い、getlineで各レコード内の特定行を動的に抽出します。

【実装:堅牢な自動化スクリプト】

#!/bin/bash


# ==============================================================================


# Multi-line Log Processor to JSON


# ==============================================================================

set -euo pipefail
IFS=$'\n\t'

# 設定

LOG_FILE="/var/log/app/error.log"
OUTPUT_JSON="/tmp/log_processed.json"
ENDPOINT_URL="http://localhost:9091/metrics/job/log_check"

# クリーンアップ処理

trap 'rm -f "$OUTPUT_JSON"' EXIT

# メイン処理:awkによる抽出と整形


# ------------------------------------------------------------------------------


# RS="": 空行をレコード区切りとする


# getline: 現在のレコード内でさらに行を進めて読み込む


# ------------------------------------------------------------------------------

process_logs() {
    gawk '
    BEGIN {
        RS = "";       # 空行をレコード区切りに設定
        FS = "\n";     # 行内フィールドは改行で区切る
    }
    {

        # 1行目はヘッダー情報(タイムスタンプとレベル)と仮定

        header = $1;

        # 2行目以降(スタックトレース等)を結合

        body = "";
        for(i=2; i<=NF; i++) {
            body = body $i " ";
        }

        # 特定のキーワードが含まれる場合のみ抽出

        if (header ~ /ERROR/) {
            printf "{\"timestamp\":\"%s\", \"level\":\"ERROR\", \"message\":\"%s\"}\n", $1, body;
        }
    }' "$LOG_FILE" | \
    jq -c '.' > "$OUTPUT_JSON" # -c: 出力をコンパクトな1行JSONにする
}

# 転送処理

upload_logs() {
    if [[ -s "$OUTPUT_JSON" ]]; then
        curl -X POST -H "Content-Type: application/json" \
             --data-binary "@$OUTPUT_JSON" \
             -L # リダイレクトに従う \
             -s # 進捗バーを非表示にする \
             -o /dev/null \
             "$ENDPOINT_URL"
    fi
}

process_logs
upload_logs

systemdによる定期実行の設定例

/etc/systemd/system/log-parser.timer

[Unit]
Description=Run multi-line log parser every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

【検証と運用】

  1. 正常系の確認: スクリプトを手動実行し、jqがエラーを吐かずにJSONを出力するか確認します。

    bash log_processor.sh
    
  2. エラーログの確認: systemd経由で実行している場合、標準出力とエラー出力はjournalctlで追跡可能です。

    journalctl -u log-parser.service -f
    
  3. awk動作テスト: 特定のレコードのみが抽出されているか、RSの動作をテストファイルで確認します。

【トラブルシューティングと落とし穴】

  • awkのバリアント: mawk(Debian/Ubuntuのデフォルトの一部)はRSに正規表現を使用する際の挙動がgawkと異なる場合があります。ポータビリティが必要な場合はgawkを明示的に指定してください。

  • メモリ制限: RS="" で巨大なログファイルを処理する場合、1つのレコード(空行間のデータ)が極端に大きいとメモリを消費します。レコードサイズに上限があることを確認してください。

  • 権限問題: /var/log配下のファイルを読み取る際、実行ユーザーがロググループ(adm等)に属している必要があります。sudoを多用せず、適切なグループ権限で実行してください。

  • JSONのエスケープ: 上記のawk内での手動JSON構築は、メッセージ内にダブルクォートが含まれると破壊されます。実戦では jq --arg を使って変数を渡す構成を推奨します。

【まとめ】

運用の冪等性を維持するためのポイント:

  1. 状態管理: 処理済みログのオフセット(inodeとバイト数)を記録し、二重処理を防止する。

  2. パイプラインの保護: set -o pipefail により、awkが成功してもjqやcurlが失敗した場合にスクリプト全体を異常終了させる。

  3. 構造化の徹底: awkは抽出に徹し、複雑なエスケープが必要なJSON整形はjq等の専用ツールへ委ねる。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました