本文へ移動
cccskills
無料GitHub で公開

ci-stable-log-testing

CI環境で安定動作するPythonテスト(特にstructlogのログ検証)を書く際に使用。「CIで失敗する」「capture_logsが不安定」「ログアサーションがCIだけ落ちる」「caplogフォールバック」「ANSI混入」「xdistでログが取れない」という課題で起動。

capture_logs→caplogフォールバック→ANSI正規化→アサーション設計の流れで堅牢なテストを書く。

インストール方法を見る

含まれるファイル(3)

  • SKILL.md6.0 KB
  • references/troubleshooting.md2.6 KB
  • scripts/find_event_logs.py5.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

CI-Stable Test: structlogログ検証の安定化パターン

背景と問題

structlog.testing.capture_logs() はローカルでは動作するが、CI環境(特にpytest-xdist並列実行、GitHub Actions ubuntu runner)では以下の理由でログが取得できないことがある。

  1. モジュールレベルのロガーキャッシュ: 先行テストがロガーをバインド済みだと capture_logs() のコンテキストマネージャがログを捕捉できない
  2. pytest-xdist並列実行: ワーカー間でstructlogのグローバル設定が競合する
  3. caplogメッセージのANSI混入: CI環境のstructlogレンダラがANSIエスケープ付きで出力し、key=value の単純一致が崩れる
  4. exc_info の表現差異: CI環境では record.exc_info が None でもメッセージ本文にTracebackが含まれる場合がある

解決パターン: 2段階フォールバック

パターン概要

capture_logs() で構造化ログを取得
 ├── 取得成功 → 構造化dictで厳密アサーション
 └── 取得失敗(空リスト)→ caplog にフォールバック
      └── ANSIエスケープ除去 + key=value正規化してアサーション

実装手順

Step 1: ANSI正規化ユーティリティを定義

import re

_ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m")

def _normalize_log_message(message: str) -> str:
    """ANSIエスケープを除去し、key=value比較しやすい形へ正規化する。"""
    normalized = _ANSI_ESCAPE_RE.sub("", message)
    normalized = re.sub(r"\s*=\s*", "=", normalized)
    return normalized

Step 2: フォールバック付きログ抽出ヘルパーを定義

def _find_event_logs(
    captured: list[dict[str, object]],
    *,
    event: str,
    caplog: pytest.LogCaptureFixture,
) -> list[dict[str, object]]:
    """capture_logs の結果が空の場合に caplog をフォールバック利用する。"""
    structured = [e for e in captured if e.get("event") == event]
    if structured:
        return structured

    fallback: list[dict[str, object]] = []
    seen: set[str] = set()
    for record in caplog.records:
        msg = _normalize_log_message(record.getMessage())
        raw = _normalize_log_message(str(record.msg))
        if event in msg or event in raw:
            merged = msg if event in msg else raw
            if merged in seen:
                continue
            seen.add(merged)
            fallback.append({
                "event": event,
                "log_level": record.levelname.lower(),
                "message": merged,
            })
    return fallback

設計判断:

  • seen セットで重複メッセージを除外(capture_logsとcaplog両方に同じログが流れるケースへの対策)
  • record.msg(フォーマット前)も確認する(ハンドラチェーンによる差異を吸収)

Step 3: テスト関数でcaplogを併用

@pytest.mark.asyncio
async def test_example(self, tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
    caplog.clear()
    caplog.set_level(logging.WARNING)  # 対象ログレベルに合わせる
    with capture_logs() as cap:
        await some_function()

    logs = _find_event_logs(cap, event="my_event", caplog=caplog)
    assert len(logs) >= 1

Step 4: アサーションの分岐パターン

log_entry = logs[0]
assert log_entry["log_level"] == "warning"

# 構造化パス(capture_logs成功時)はキーで直接検証
if "detail_key" in log_entry:
    assert log_entry["detail_key"] == expected_value
else:
    # フォールバックパス: 正規化済みメッセージでkey=value検証
    assert "detail_key=expected_value" in log_entry["message"]

判断チェックリスト

テストでstructlogのログ検証を書く際、以下に該当すれば本パターンを適用する。

  • structlog.testing.capture_logs() でログを検証している
  • CI(GitHub Actions等)でのみテストが失敗する
  • pytest-xdist(-n auto)で並列実行している
  • ログメッセージの key=value 部分文字列でアサーションしている

exc_info の検証

CI環境では record.exc_info が None でもメッセージ本文にTracebackが埋め込まれることがある。

fallback.append({
    # ...
    "exc_info": (
        record.exc_info is not None
        or "Traceback (most recent call last):" in merged
    ),
})

件数アサーションの注意

CI環境ではハンドラチェーンの差異によりログが複数回記録されることがある。

  • == 1 よりも >= 1 を推奨(特にフォールバックパスで)
  • 複数件を許容する場合は logs[-1](最後のエントリ)で検証すると安定する
  • seen セットによる重複除外を入れても、formatter差異で完全一致しない場合がある

やらないこと(スコープ外)

  • ログの出力順序の厳密な検証(CI環境では保証されない)
  • structlog自体の設定変更やモンキーパッチ
  • caplogだけに依存する方式(構造化ログのキーアクセスが失われる)
  • プロダクションコードの変更(テスト側のみで吸収する)

参照

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

cc-sdd-restack-after-merge

無料日本語概要

stacked PRの下位PRがマージされた後に上位branchをrestack(rebase/cherry-pick再構成)する際に使用。「restackして」「上位PRを更新して」「restack after merge」「PRのbaseを更新」「スタックを整理」という依頼でトリガーする。対象sliceの特定→rebase→変更ファイル確認と代表テスト実行→force-with-leaseでpush→PR base更新→auto-merge判定の順で進める。

studiogadget/skills32026年5月25日 更新

cc-sdd-review-slice-stack

無料日本語概要

cc-sdd実装用統合ブランチのコミット群(ベースブランチとの差分)をreview slice単位のstacked PRに再構成する際に使用。「review sliceに分割」「stacked PRを作成」「PRをスタックして」「レビュー用にPRを分けて」「review slice stack」という依頼でトリガーする。dry-run(既定)でslice計画を提示し、apply指示で実際にブランチ・PRを作成する。

studiogadget/skills32026年5月25日 更新

create-issue

無料日本語概要

GitHub Issueを作成してGitHubに投稿するスキル。「Issueを作成」「Issue作成」「Issueにして」「Issueとして投稿」「GitHubにIssue」「create issue」「open issue」「バグ報告」「機能追加をIssueに」「改善提案をIssueに」などの依頼でトリガーする。コンテキスト収集→Issue本文生成→ラベル存在確認・作成→gh issue create投稿の流れでIssueを作成する。

studiogadget/skills32026年5月25日 更新

docker-deployment-guide

無料日本語概要

Dockerベースのアプリケーションのデプロイメント手順書(Markdown)を作成するスキル。「デプロイ手順書」「デプロイメント手順」「deployment guide」「本番環境への配置手順」「サーバーへの配置手順」などの依頼でトリガーする。Docker Composeビルド→イメージ転送→サーバー配置→設定→動作確認→cron設定の流れで手順書を生成する。

studiogadget/skills32026年5月25日 更新

hibernate-pc

無料日本語概要

タスク完了後にPCを休止状態にする。Use when: ユーザーが「休止」「hibernate」「タスク完了後に休止状態」「作業後にPCをスリープ/休止」と明示的に指示した場合のみ。正常終了時だけでなく、エラーや未解決事項が残っていても、明示指示があれば最後に休止へ移行する。Windows only.

studiogadget/skills32026年5月25日 更新

Investigate implementation failures using root-cause-first debugging. Use when an implementer is blocked, verification fails, or repeated remediation does not converge.

日本語の概要は準備中です。原文の説明を表示しています。

studiogadget/skills32026年5月25日 更新

studiogadget のスキルをすべて見る

このスキルの問題を報告する