第143回 実務で使える運用と点検:監査ログ・SLO・運用チェックリストをPythonで自動化する手順

運用を始めると「何を記録すれば良いか」「どこまで自動化できるか」で迷うことが多いです。本記事は、監査ログ(予測の起点・入力・モデルバージョン・出力)を確実に残し、SLO/SLIを定義して日次・週次チェックをPythonで自動化する実務手順をまとめます。現場でそのまま試せるスキーマ例、チェックリスト、簡易ランブック、サンプルコードと運用上の注意点を提供します。

目次

  • 概要と要件定義
  • 監査ログ設計:JSONLスキーマ例と必須フィールド
  • SLO/SLIの定義と閾値設計
  • Pythonで作る日次/週次自動チェック(サンプルスクリプト)
  • ランブックと対応フロー
  • CI/スケジューラへの組み込みとレポート配信
  • 運用でよくある落とし穴と改善ループ
  • まとめと次につなげる企画案

① 概要と要件定義

まず最初に決めるのは「どのイベントを残すか」と「保持期間・マスキング方針」です。実務では記録不足で原因追跡ができないことがよくあるため、後から必要になりそうな情報は最小限でも残す方針が安全です。以下のポイントを決めてから実装を始めてください。

  • 記録するイベント:リクエスト受信、前処理、モデル呼び出し、応答、エラー、モデルデプロイ/ロールバック
  • 保持期間:法規や契約に従う。一般的には30日~1年を検討(個人情報含む場合は短縮)
  • マスキング:個人情報(PII)は保存しないかハッシュ化/部分マスクする
  • アクセス制御:監査ログは読み取り権限を限定
  • 改ざん防止:ハッシュや署名で改ざん検知できる仕組みを検討

② 監査ログ設計:JSONLスキーマ例と必須フィールド

実務では行指向(JSONL)が扱いやすく、ストリーム処理や検索にも適しています。まずは必須フィールドを定義し、運用に必要なメタデータを含めます。

JSONLスキーマ(例)

{"timestamp": "2026-08-31T09:12:34.123Z", "request_id": "req_0001", "user_id_hash": "sha256:...", "model": "predictor", "model_version": "v1.2.3", "input_summary": "token_count=45, features_ok=true", "input_hash": "sha256:...", "prediction": "approve", "confidence": 0.87, "latency_ms": 210, "error": null, "provenance": {"pipeline_version": "p1.0"}, "signature": "hmac:..."}

上記の1行が1レコード(JSONL)です。運用ではgzipで圧縮して保管することもあります。

フィールド 説明 必須
timestamp UTCタイムスタンプ(ISO8601) 必須
request_id リクエストを一意に識別するID 必須
user_id_hash 個人を特定しないハッシュ/マスク済みID 必須(PIIは非保存が原則)
model / model_version 使用したモデルとバージョン 必須
input_hash / input_summary 入力のハッシュと簡易要約(全文保存しない場合) 必須
prediction / confidence 出力と確信度 必須
latency_ms 推論所要時間(ミリ秒) 推奨
error エラー情報(発生時) 推奨
provenance 処理パイプラインや前処理のバージョン 推奨
signature ログ改ざん検知用の署名(省略可) オプション

③ SLO/SLIの定義と閾値設計

SLO(Service Level Objective)は運用目標、SLI(Service Level Indicator)はそれを測る指標です。現実的な閾値を決めるためには、基線となるメトリクスを数週間観察することが重要です。

指標(SLI) 式(例) SLO目標例
応答時間(P95 latency) 95パーセンタイルの遅延(ms) P95 < 500ms(可用性:99%)
エラー率 エラー数 / 総リクエスト数 < 1%(月間)
データ品質(欠損率) 欠損あるいは不整合レコードの割合 < 0.5%(週次)

SLOは技術的に達成可能で、かつビジネスに意味のある水準で設定します。最初は緩めに設定して運用で引き締めるのが現実的です。

SLOを計算するサンプル関数(Python)

def calc_error_rate(records):
    total = len(records)
    errors = sum(1 for r in records if r.get("error"))
    return errors / total if total else 0.0

def calc_p95_latency(latencies):
    if not latencies:
        return None
    latencies = sorted(latencies)
    import math
    idx = math.ceil(0.95 * len(latencies)) - 1
    return latencies[idx]

④ Pythonで作る日次/週次自動チェック(サンプルスクリプト付き)

ここでは、JSONLログを読み込み、SLOを計算してCSVと簡易HTMLレポートを出力し、Slackへ送る軽量な流れを示します。標準ライブラリ中心で書き、必要に応じてpandas導入を検討します。

ポイントで使う標準要素

  • logging と RotatingFileHandler(ログローテーション)
  • json / csv モジュール
  • datetime, timezone(UTC管理)
  • pathlib(ファイル操作)
  • 例外処理と簡易リトライ

サンプル:日次チェックの簡易スクリプト

import logging
from logging.handlers import RotatingFileHandler
import json
from pathlib import Path
from datetime import datetime, timezone
import csv
import requests

# ログ設定
logger = logging.getLogger("audit_checker")
handler = RotatingFileHandler("logs/audit_checker.log", maxBytes=5*1024*1024, backupCount=3)
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

DATA_DIR = Path("/var/logs/audit")
REPORT_DIR = Path("/var/reports/audit")
REPORT_DIR.mkdir(parents=True, exist_ok=True)

# JSONLを読み込むユーティリティ
def read_jsonl(path):
    with path.open("r", encoding="utf-8") as f:
        for line in f:
            try:
                yield json.loads(line)
            except json.JSONDecodeError:
                logger.warning(f"invalid json: {path} : {line[:100]}")

# 集計処理
def aggregate_for_period(paths):
    records = []
    latencies = []
    for p in paths:
        for r in read_jsonl(p):
            records.append(r)
            if r.get("latency_ms") is not None:
                latencies.append(r["latency_ms"])
    return records, latencies

# SLO計算(先ほどの関数を活用)
def calc_error_rate(records):
    total = len(records)
    errors = sum(1 for r in records if r.get("error"))
    return errors / total if total else 0.0

def calc_p95_latency(latencies):
    if not latencies:
        return None
    latencies = sorted(latencies)
    import math
    idx = math.ceil(0.95 * len(latencies)) - 1
    return latencies[idx]

# レポート出力
def write_csv_report(path, summary):
    with path.open("w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["metric", "value"])
        for k, v in summary.items():
            writer.writerow([k, v])

# Slackへ送る(軽量な例)
def post_slack(webhook_url, text):
    try:
        requests.post(webhook_url, json={"text": text}, timeout=5)
    except Exception as e:
        logger.warning(f"slack post failed: {e}")

# メイン処理(例:前日分)
def run_daily_check(date_str, slack_webhook=None):
    # 日付文字列 -> ファイルを選ぶルールは運用で決める
    day_file = DATA_DIR / f"audit_{date_str}.jsonl"
    records, latencies = aggregate_for_period([day_file])
    error_rate = calc_error_rate(records)
    p95 = calc_p95_latency(latencies)
    summary = {"date": date_str, "total_requests": len(records), "error_rate": error_rate, "p95_latency_ms": p95}
    csv_path = REPORT_DIR / f"summary_{date_str}.csv"
    write_csv_report(csv_path, summary)
    logger.info(f"daily check done: {summary}")
    if slack_webhook:
        post_slack(slack_webhook, f"Daily audit report {date_str}: {summary}")

# 実行例
if __name__ == '__main__':
    today = datetime.now(timezone.utc).strftime('%Y-%m-%d')
    run_daily_check(today, slack_webhook=None)

上記は最小限の例です。実運用ではログのローテーション、古いログの削除、圧縮、取得失敗時のリトライなどを追加してください。

⑤ ランブックと対応フロー(トリアージ、エスカレーション、復旧手順)

平時から復旧までの手順を文章化しておくと、担当が不在でも対応が回ります。以下は簡易ランブックの例です。

アラート種別 優先度 初動対応(担当) エスカレーション先
SLO違反(エラー率急上昇) ログ確認、直近のデプロイ確認(オンコール) 開発リード/インフラ担当
モデルパフォーマンス劣化(急低下) サンプルの入力と出力を抽出して比較(運用担当) MLエンジニア
ログ書き込み失敗 ディスク容量・権限を確認(運用担当) インフラ担当

トリアージのポイント

  • まずは影響範囲(ユーザー数、バッチ処理かリアルタイムか)を評価する
  • 短時間で復旧可能かを判断し、ロールバックや一時遮断を検討する
  • 根本原因調査は別タスクにして、まずサービス維持を優先する

⑥ CI/スケジューラへの組み込みとレポート配信

定期チェックはcronやGCP Cloud Scheduler、GitHub Actionsなどに組み込みます。成果物はCSV/HTMLレポートとして保存し、Slackやメールで配信します。レポートは短く要点を示す形式が受け入れられやすいです。

軽量なSlack送信例(requests使用)は前述の post_slack を参照してください。メール送信はSMTPライブラリ、あるいは外部サービスのAPI利用を検討します。

⑦ 運用でよくある落とし穴と改善ループ

  • 大量ログのコスト増:すべて保存せずサンプリングや条件付き保存(エラー時のみ詳細を保存)を設計する
  • プライバシー:PIIは生データで保存しない。ハッシュ化や部分マスキングの具体例をポリシー化する
  • 改ざん対策:ログに単純なHMACを付与して改ざん検知の第一歩とする(秘密鍵は別管理)
  • SLOの現実的妥協:初期は広めに設定し、運用で引き締める。過度なSLOは誤検知や過剰対応を招く
  • 担当1人でも回せる仕組み:手順を簡潔に、復旧手順を優先度付きで定義する

運用チェックリスト(そのまま使える表)

項目 チェック内容 推奨頻度 責任者 備考
ログ保持 保存ポリシーが適用されているか(古いログは削除/アーカイブ) 月次 運用担当 法規遵守を確認
アクセス制御 ログストアのアクセス権を確認 月次 セキュリティ担当 最小権限の原則
マスキング PIIがマスク/ハッシュされているか 週次 運用担当 抜き打ち検査を推奨
異常検知閾値 SLO/SLIの閾値が現状に合っているか 四半期 SRE/運用 基線を見て調整
通知先 Slack/メールの通知先が最新か 週次 運用担当 オンコール表と同期
抜き打ちテスト テストリクエストでログ整合性を確認 月次 運用担当 結果は記録して改善へ

まとめ

監査ログ設計、SLO/SLI定義、日次/週次の自動チェック、そしてランブック化は運用の基本です。本記事では現場で使えるJSONLスキーマ例、SLO計算、Pythonを使った自動チェックの骨子、実務的な注意点を示しました。まずは最小限のログを確実に取り、SLOを設定して自動化することを優先してください。運用は作って終わりではなく、定期的な見直し(改善ループ)が重要です。

次につなげる企画案

  • ポストモーテムと改善ループの作り方(テンプレート付き)
  • 監査ログを検索・分析する軽量な検索基盤構築(ELK代替)
  • 監査ログからの自動改善サイクル(フィードバックループ)実装例

この記事は「AIとPythonの実務」シリーズの一部です。実務での導入や具体的なスクリプト改良の相談があれば、次回以降でより深掘りします。