Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 50/100

python-logging-best-practices

loguru・structlog・orjsonを活用したPythonのロギングベストプラクティスを提供します。`loguru`や`structlog`、構造化ロギングに関する実装が必要な際にトリガーされ、適切なログ設定やフォーマットの指針を示します。

description の原文を見る

Python logging with loguru, structlog, and orjson. TRIGGERS - loguru, structlog, structured logging

SKILL.md 本文

Python Logging Best Practices

Self-Evolving Skill: このスキルは使用を通じて改善されます。指示が誤っている、パラメータが変動している、回避策が必要だった場合 — このファイルを即座に修正してください。延期しないでください。実際に再現可能な問題の場合のみ更新します。

このスキルを使用する場面

以下の場合にこのスキルを使用してください:

  • 任意のサービスまたはスクリプトの Python logging をセットアップする場合
  • 分析用に構造化 JSONL logging を設定する場合
  • ログローテーションを実装する場合
  • 軽量(無依存)とフル機能の logging の間で選択する場合
  • コンテナ化、systemd、またはローカルアプリケーションに logging を追加する場合

概要

機械読取性(Claude Code 分析)と運用信頼性に最適化された Python logging パターンの統一リファレンス。最も軽量な実行可能なアプローチで開始し、必要な場合のみスケールアップします。

決定ヒューリスティック: 軽量で開始、必要に応じてスケールアップ

単一マシン上の < 5 サービス、< 1 イベント/秒ですか?
  はい → 軽量パターン(print + JSONL テレメトリ)
  いいえ → コンテナ化 / サーバーレスですか?
    はい → stdout JSON(任意のライブラリ)、ファイルローテーションなし
    いいえ → OTel トレースが必要ですか?
      はい → structlog + OTel
      いいえ → loguru(CLI ツール)または stdlib RotatingFileHandler
アプローチユースケース利点欠点
Lightweight小規模 systemd サービス、自社ホスト、単一オペレータ無依存、journald 統合、最小限のコード重大度フィルタリングなし、モジュール単位の制御なし
loguruCLI ツール、スクリプト、ローカルサービスゼロ設定、組み込みローテーション、優れた DX外部依存、真のスキーマ強制なし
structlog本番サービス、OTel 統合ContextVars、プロセッサチェーン、OTel ネイティブ学習曲線が急
stdlibLaunchAgent デーモン、無依存制約依存関係なし、Python 3.13 merge_extraより多くのボイラープレート、構造化デフォルトなし
LogfireAI/LLM オブザーバビリティ、Pydantic アプリOTel ベース、トークン/コスト追跡、SQLSaaS 依存、新しいエコシステーム

推奨: 軽量パターン(無依存)

対象: < 5 systemd サービス、単一サーバー、単一オペレータ。ccmax-monitor によって本番環境で実証済み。

このパターンは 2チャネルアーキテクチャ を使用します:

  • チャネル 1: print(flush=True) → systemd journald(運用ログ、人間が読める形式)
  • チャネル 2: アペンドオンリー JSONL ファイル(構造化テレメトリ、機械が読める形式)

これは 12-Factor App の「ログをイベントストリームとして扱う」原則にマップされます。journald は ops(ローテーション、フィルタリング、メタデータ)を処理し、JSONL ファイルは事後分析用のドメインテレメトリとして機能します。

アーキテクチャ: 3 つの関心の分離

関心メカニズム目的ライフサイクル
Ops loggingprint() → journald人間によるデバッグ、journalctl -u service -fjournald により管理(自動ローテーション)
TelemetryJSONL ファイル(telemetry.jsonl構造化監査証跡、AI/LLM 分析アペンドオンリー、サイズベースでローテーション
State recoveryWAL ファイル(オプション)元に戻せない操作のクラッシュ回復一時的、成功時に削除

完全な軽量パターンの例

"""アペンドオンリー JSONL テレメトリロガーとサイズベースのローテーション。

外部依存なし。ops logging 用の systemd journald と
構造化機械可読テレメトリ用の別々の JSONL ファイルで動作します。
"""

import json
from datetime import datetime, timezone
from pathlib import Path

TELEMETRY_PATH = Path(__file__).parent / "telemetry.jsonl"
MAX_SIZE = 10 * 1024 * 1024  # 10 MB
BACKUP_COUNT = 3             # ローテーション済みバックアップを 3 つ保持(合計〜30MB)


def log_event(event_type: str, data: dict) -> None:
    """構造化 JSON 行を telemetry.jsonl に追加します。"""
    entry = {
        "ts": datetime.now(timezone.utc).isoformat(),
        "type": event_type,
        **data,
    }
    line = json.dumps(entry, separators=(",", ":")) + "\n"

    try:
        try:
            if TELEMETRY_PATH.stat().st_size > MAX_SIZE:
                _rotate()
        except FileNotFoundError:
            pass

        with open(TELEMETRY_PATH, "a") as f:
            f.write(line)
    except OSError as e:
        # stderr へのフォールバック(journald によってキャプチャ)
        print(f"[telemetry] write failed: {e}", file=__import__("sys").stderr, flush=True)


def _rotate() -> None:
    """テレメトリファイルをローテーション: .jsonl → .jsonl.1 → .jsonl.2 → .jsonl.3"""
    for i in range(BACKUP_COUNT, 1, -1):
        src = TELEMETRY_PATH.with_suffix(f".jsonl.{i - 1}")
        dst = TELEMETRY_PATH.with_suffix(f".jsonl.{i}")
        if src.exists():
            dst.unlink(missing_ok=True)
            src.rename(dst)
    backup = TELEMETRY_PATH.with_suffix(".jsonl.1")
    backup.unlink(missing_ok=True)
    TELEMETRY_PATH.rename(backup)


# === Ops logging(stdout 経由で journald に送信) ===

def log(msg: str) -> None:
    """人間が読める運用ログ行。journald によってキャプチャされます。"""
    ts = datetime.now(timezone.utc).strftime("%H:%M:%S")
    print(f"[{ts}] {msg}", flush=True)

使用例:

# 運用ログ(人間が journalctl -u myservice -f で読む)
log("Refreshing token for account X")
log("Switch: account A → account B (reason: 5h breach)")

# テレメトリ(機械が jq/DuckDB/Claude Code で読む)
log_event("token_refresh", {"account": "X", "expires_in_h": 8.0, "token_fp": "abc12345"})
log_event("account_switch", {"from": "A", "to": "B", "reason": "5h_breach"})

セキュリティ: トークンフィンガープリント(正規表現リダクションではなく)

シークレットをログパイプラインに渡さないでください。 非可逆的なフラグメントのみをログに記録します:

def _token_fingerprint(token: str) -> str:
    """トークンの中間セクションから一意に識別可能な文字を抽出します。

    プレフィックス(sk-ant-oat01-)とサフィックス(...AA)は
    トークン間で共通しています。
    プレフィックス後の文字 14-22 がトークンごとに最も一意です。
    中間スライスは、プレフィックスベースのアプローチが公開する
    タイププレフィックスメタデータのリークを回避します。
    """
    if len(token) > 25:
        return token[14:22]
    return token[:8] if token else ""

# 使用例: フィンガープリントをログに記録し、決してトークンを渡さない
log_event("token_refresh", {"account": name, "token_fp": _token_fingerprint(token)})

これが正規表現リダクションフィルターより優れている理由:

アプローチセキュリティメンテナンス故障モード
トークンフィンガープリント(スライスのみをログに記録)シークレットはログパイプラインに入らないゼロ — 任意のトークン形式で動作失敗できない — リダクションするものがない
正規表現リダクションフィルターシークレットはパスされ、出力時にフィルタリング新しいトークン形式用に正規表現を更新する必要ありサイレント失敗 = ログにシークレット

これは OWASP Logging Cheat Sheet に一致しています: 「ログエントリにセンシティブデータが含まれていないことを確認してください。」主要プラットフォーム(AWS、Stripe、GitHub)は、完全なトークンと正規表現スクラビングではなく、別々の非シークレット識別子または部分的なトークン表示を使用しています。

正規表現フィルターは 多層防御のバックストップ として有用なままです。主要なコントロールではなく。

オブザーバビリティとしてのヘルスエンドポイント

小規模デプロイの場合、豊富な JSON ヘルスエンドポイントはログ集約を置き換えます:

@app.get("/api/status")
def status():
    """ホワイトボックスモニタリング — オンデマンドの現在の状態。"""
    return {"active_account": ..., "accounts": [...], "polled_at": ...}

@app.get("/api/vault-health")
def vault_health():
    """すべてのアカウントのトークンヘルス。"""
    return {name: {"status": "healthy", "expires_in": "7.5h", ...} for ...}

これは ヘルスエンドポイントモニタリングパターン(Microsoft Azure Architecture Center)/ ヘルスチェック API パターン(microservices.io)です。ダッシュボード IS モニタリングツール — Grafana/Prometheus は不要です。

サービス自体が構造化 JSON として独自の運用状態を提供する場合、以下が得られます:

  • リアルタイム 現在の状態(ログ取り込みパイプラインによる遅延なし)
  • ゼロインフラストラクチャ(ログシッパー、ストレージ、またはクエリエンジンなし)
  • AI が解析可能(Claude Code は直接 curl して分析できます)

FOSS CLI ツールを使用した事後分析

ログ集約スタック不要。これらのシングルバイナリツールは JSONL で直接動作します:

# DuckDB — JSONL での SQL 分析(最も強力)
duckdb -c "SELECT type, count(*) FROM read_json_auto('telemetry.jsonl') GROUP BY 1 ORDER BY 2 DESC"

# jq — アドホック JSON フィルタリング
jq 'select(.type == "token_refresh")' telemetry.jsonl

# journalctl — 既に JSONL をネイティブにエクスポート
journalctl -u ccmax-switcher -o json --since "1h ago" | jq 'select(.PRIORITY == "3")'

# lnav — SQL を備えた対話型ターミナルログビューア
lnav telemetry.jsonl

# llm(Simon Willison)— LLM にパイプして AI 事後分析
journalctl -u myservice --since "2h ago" --priority=err -o json | llm "analyze root cause"

軽量を超えてアップグレードする場合

以下のいずれかが当てはまる場合は loguru/structlog にアップグレードしてください:

  • 複数ホスト上の > 5 サービス(トレース ID が相関に必要)
  • > 10 イベント/秒 継続(非同期シンク、orjson が必要)
  • 複数オペレータ がモジュール単位のログレベルフィルタリングを必要とする
  • コンプライアンス要件 が署名付きの構造化監査証跡を要求する
  • コンテナ/K8s デプロイメント(stdout JSON が標準)

フル機能: Loguru + JSONL パターン

logging ライブラリの恩恵を受ける CLI ツール、スクリプト、サービス用:

ログローテーション(ローカル/CLI アプリの場合は常に設定)

from loguru import logger

logger.add(
    log_path,
    rotation="10 MB",
    retention="7 days",
    compression="gz"
)

# stdlib 代替案(無依存)
from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    log_path,
    maxBytes=100 * 1024 * 1024,  # 100MB
    backupCount=5
)

コンテナ/サーバーレスアプリ: ファイルローテーションはスキップしてください。stdout/stderr を JSON として ログに記録してください。コンテナランタイムが収集とローテーションを処理します。

JSONL フォーマット(機械が読める)

# 行あたり 1 つの JSON オブジェクト - jq で解析可能
{"timestamp": "2026-01-14T12:45:23.456Z", "level": "info", "message": "..."}

ファイル拡張子: 常に .jsonl を使用(.json または .log ではなく)

パフォーマンス: > 10k レコード/秒の場合は json.dumps() の代わりに orjson を使用:

import orjson

def json_formatter(record) -> str:
    log_entry = { ... }
    return orjson.dumps(log_entry).decode()

正規表現リダクション(多層防御)

トークンフィンガープリント とともに バックストップ として使用します。主要なコントロールではなく:

import re

REDACT_PATTERNS = [
    (re.compile(r'AKIA[0-9A-Z]{16}'), '[REDACTED_AWS_KEY]'),
    (re.compile(r'sk-[a-zA-Z0-9]{48}'), '[REDACTED_API_KEY]'),
    (re.compile(r'(?i)bearer\s+[a-zA-Z0-9._~+/=-]+'), '[REDACTED_BEARER]'),
]

def redact_filter(record):
    for pattern, replacement in REDACT_PATTERNS:
        record["message"] = pattern.sub(replacement, record["message"])
    return True

logger.add(sink, filter=redact_filter)

シャットダウン — 常にキューに入ったメッセージをフラッシュ

import asyncio
from loguru import logger

async def main():
    logger.add("app.jsonl", enqueue=True)
    await logger.complete()

asyncio.run(main())
# 同期: logger.remove()

完全な Loguru + JSONL の例

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.13"
# dependencies = ["loguru", "orjson"]
# ///

import re
import sys
from pathlib import Path
from uuid import uuid4

import orjson
from loguru import logger

REDACT_PATTERNS = [
    (re.compile(r'AKIA[0-9A-Z]{16}'), '[REDACTED_AWS_KEY]'),
    (re.compile(r'sk-[a-zA-Z0-9]{48}'), '[REDACTED_API_KEY]'),
]


def json_formatter(record) -> str:
    log_entry = {
        "timestamp": record["time"].strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
        "level": record["level"].name.lower(),
        "component": record["function"],
        "operation": record["extra"].get("operation", "unknown"),
        "operation_status": record["extra"].get("status", None),
        "trace_id": record["extra"].get("trace_id"),
        "message": record["message"],
        "context": {k: v for k, v in record["extra"].items()
                   if k not in ("operation", "status", "trace_id", "metrics")},
        "metrics": record["extra"].get("metrics", {}),
        "error": None
    }

    if record["exception"]:
        exc_type, exc_value, _ = record["exception"]
        log_entry["error"] = {
            "type": exc_type.__name__ if exc_type else "Unknown",
            "message": str(exc_value) if exc_value else "Unknown error",
        }

    return orjson.dumps(log_entry).decode()


def redact_filter(record):
    for pattern, replacement in REDACT_PATTERNS:
        record["message"] = pattern.sub(replacement, record["message"])
    return True


def setup_logger(app_name: str, log_dir: Path | None = None):
    logger.remove()
    logger.add(sys.stderr, format=json_formatter, filter=redact_filter, level="INFO")
    if log_dir is not None:
        log_dir.mkdir(parents=True, exist_ok=True)
        logger.add(
            str(log_dir / f"{app_name}.jsonl"),
            format=json_formatter,
            filter=redact_filter,
            rotation="10 MB",
            retention="7 days",
            compression="gz",
            level="DEBUG"
        )
    return logger

セマンティックフィールドリファレンス

フィールド目的
timestamp / tsISO 8601イベント順序付け(ミリ秒精度最小)
level / typestring重大度またはイベントタイプ
component / svcstringモジュール、関数、またはサービス名
operationstring実行中のアクション
operation_statusstringstarted/success/failed/skipped
trace_idUUID4 or OTel相関 ID(本番サービス用の OTel トレース ID)
messagestring人間が読める説明
contextobject操作固有のメタデータ
metricsobject定量データ(カウント、期間)
errorobject/null失敗した場合の例外詳細

関連リソース

避けるべきアンチパターン

  1. 無限のログ - 常にローテーション(ローカル)または stdout(コンテナ)を設定
  2. 完全なシークレットをログに記録 - トークンフィンガープリントを使用。正規表現リダクションはバックストップ、主要ではない
  3. < 5 個の低ボリュームサービスに loguru/structlog を追加 - print + JSONL で十分。依存関係は無料ではない
  4. シークレットなしの裸の例外処理 - 特定の例外をキャッチしてログに記録
  5. サイレント失敗 - エラーを抑制する前にログに記録
  6. enqueue=Truelogger.complete() なし - サイレントログ損失がシャットダウンで発生
  7. 遅いシンクで enqueue=True - 無限メモリ増殖
  8. > 10k イベント/秒で json.dumps() - 2~10 倍の高速化で orjson を使用
  9. OTel サービスで UUID4 トレース ID - OTel によって伝播されたトレース ID を使用
  10. < 5 サービス用の Prometheus/Grafana - ヘルスエンドポイント + Uptime Kuma で十分
  11. WAL とテレメトリを混同 - WAL はクラッシュ回復用(一時的)、テレメトリは監査用(永続的)

トラブルシューティング

問題原因解決方法
loguru が見つからないインストールされていないuv add loguru を実行
ログが表示されていないログレベルが間違っているトラブルシューティング用に DEBUG レベルを設定
ログローテーションが機能しないローテーション設定が不足logger.add() にrotation パラメータを追加
JSONL 解析エラー不正なログ行エスケープされていない特殊文字を確認
enqueue=True での OOM無限内部キューRSS を監視;structlog を使用または遅いシンクを回避
シャットダウンで ログ損失logger.complete() が不足await logger.complete() または logger.remove() を呼び出し
JSONL シリアライゼーションが遅い高ボリュームで stdlib jsonorjson.dumps().decode() に切り替え
ログにシークレットが存在フィンガープリントなしトークン全体ではなくスライスをログに記録
journald がキャプチャしないフラッシュが不足print(..., flush=True) または PYTHONUNBUFFERED=1 を使用
サービスクラッシュ時にアラートなし外部モニタ不足Uptime Kuma または Gatus をポーリングヘルスエンドポイント用に追加

実行後の反省

このスキルが完了した後、閉じる前に確認してください:

  1. コマンドは成功しましたか? — そうでない場合は、エラーを引き起こした指示またはエラーテーブルを修正します。
  2. パラメータまたは出力が変わりましたか? — 基盤となるツールのインターフェースが変動した場合は、使用例とパラメータテーブルを更新して一致させます。
  3. 回避策が必要でしたか? — 即興が必要だった場合(異なるフラグ、追加ステップ)、次の呼び出しで同じ回避策が不要になるように SKILL.md を更新します。

実際に再現可能な問題の場合のみ更新してください — 投機的ではなく。

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
terrylica
リポジトリ
terrylica/cc-skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/terrylica/cc-skills / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: terrylica · terrylica/cc-skills · ライセンス: MIT