Skip to content
cccskills
FreePublished on GitHub

ci-stable-log-testing

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

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

The English summary is being prepared. Showing the original description.

Works withGitHub
See how to install

Files included (3)

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

SKILL.md (original)

Review the instructions the agent will receive before you install.

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だけに依存する方式(構造化ログのキーアクセスが失われる)
  • プロダクションコードの変更(テスト側のみで吸収する)

参照

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

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判定の順で進める。

The English summary is being prepared. Showing the original description.

studiogadget/skills3Updated May 25, 2026

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

The English summary is being prepared. Showing the original description.

studiogadget/skills3Updated May 25, 2026

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

The English summary is being prepared. Showing the original description.

studiogadget/skills3Updated May 25, 2026

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

The English summary is being prepared. Showing the original description.

studiogadget/skills3Updated May 25, 2026

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

The English summary is being prepared. Showing the original description.

studiogadget/skills3Updated May 25, 2026

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

The English summary is being prepared. Showing the original description.

studiogadget/skills3Updated May 25, 2026

See all skills by studiogadget

Report a problem with this skill