awkによるスタックトレース等の複数行ログ解析:RSとgetlineによる構造化抽出の自動化

Tech

{ “role”: “SRE / DevOps Engineer”, “style”: “Technical, Robust, SRE-Standard”, “focus”: “awk multi-line processing, systemd integration, robust bash scripting”, “tools”: [“awk”, “jq”, “systemd”, “bash”] } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

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. 正常系確認: サンプルのスタックトレースを含むファイルを読み込ませ、出力が1つのJSONオブジェクトにまとまっているか確認します。

    ./log-parser.sh sample.log | jq .count
    
  2. ログ確認: 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 経由で実行するか、適切なグループに追加してください。

【まとめ】

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

  1. 正規表現によるレコード境界の定義: RS を活用し、行単位ではなく「意味のあるブロック」単位で処理を完結させる。

  2. パイプラインの安全停止: set -o pipefail を用い、途中の awkjq でエラーが発生した際にスクリプト全体を停止させる。

  3. 構造化データへの早期変換: テキストのまま引き回さず、早い段階で jq 等を用いてJSON化することで、後続の監視ツールとの親和性を高める。

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

コメント

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