.agents/skills/wide-event-logging/SKILL.md
このプロジェクトでは1リクエストあたりの全イベントを1つのJSONログに集約する「ワイドイベントロギング」を採用している。MDCとThreadLocalを組み合わせ、リクエスト処理のトレーサビリティ向上とパフォーマンス分析を実現する。
npx skillsauth add ymkz/demo-monorepo wide-event-loggingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
1リクエストの全イベント(DBクエリ、業務処理、エラー等)を1つのJSONログに集約し、以下を実現する:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Filter (MDC) │────→│ EventsCollector │────→│ JSON出力 │
│ requestId設定 │ │ (ThreadLocal) │ │ レスポンス時 │
└─────────────────┘ └──────────────────┘ └─────────────┘
↑ │
└───────────────────────────────────────────────┘
各レイヤーで EventsCollector.addEvent() 呼び出し
リクエスト開始時(Filter)
EventsCollector.initialize() でコンテキスト初期化MDC.put("requestId") で全ログにrequestIdを紐付け処理中(Controller/Usecase/Domain)
EventsCollector.addEvent() で業務イベントを記録EventsCollector.setError() でエラー情報を設定レスポンス時(Filter finally)
EventsCollector.finalize() で集約されたデータを取得EventsCollector.clear() でThreadLocalを必ずクリア| レイヤー | 記録タイミング | 例 |
|---------|--------------|-----|
| Controller | 処理開始/終了 | book_search_executed |
| Usecase | 業務ロジック実行 | payment_processed |
| Domain | 重要な状態変化 | order_status_changed |
| Infrastructure | DBクエリ実行 | book_select_executed |
| ErrorHandler | 例外発生時 | (自動的に記録) |
// 良い例:シンプルで検索しやすい構造
EventsCollector.addEvent("book_search_executed",
Map.of("totalResults", 42, "queryType", "advanced"));
// 避けるべき例:循環参照・大きすぎるオブジェクト
EventsCollector.addEvent("book_search_executed", bookEntity); // NG
snake_case で動詞終わり
book_search_executedpayment_processing_startedbookSearch(camelCase)SEARCH_BOOK(定数スタイル)即時return前の記録
EventsCollector.addEvent("validation_failed",
Map.of("field", fieldName, "reason", errorReason));
return ResponseEntity.badRequest().body(error);
エラー情報の設定
try {
processPayment(order);
} catch (PaymentException ex) {
EventsCollector.setError(ex, Map.of("orderId", orderId));
throw ex; // 再スローしてハンドラに任せる
}
メタデータの最小化
| パターン | 問題 | 対応 | |---------|------|------| | JPAエンティティをmetadataに渡す | 循環参照でシリアライズ失敗 | IDや必要なフィールドのみ抽出 | | 大量のイベント記録(1000件以上) | ログサイズ肥大・遅延 | サンプリングまたは集計して記録 | | 個人情報をmetadataに含める | プライバシー侵害 | マスキングまたは除外 | | 例外を握りつぶして記録 | エラー隠蔽 | 記録後に再スロー | | @Async内でaddEvent呼び出し | ThreadLocalが別スレッド | 別のトレーシング方式を検討 |
ThreadLocalクリア確認
ログ出力確認
@Test
void 正常終了時にEventsCollectorのThreadLocalがクリアされること() {
// given
doAnswer(invocation -> {
assertThat(EventsCollector.getRequestId()).isNotEmpty();
return null;
}).when(chain).doFilter(any(), any());
// when
filter.doFilterInternal(request, response, chain);
// then
assertThat(EventsCollector.getRequestId()).isEmpty();
}
# エラーリクエスト抽出
{app="demo-api"} | json | msg="WIDE_EVENT" | severity="ERROR"
# 特定イベントを含むリクエスト
{app="demo-api"} | json | msg="WIDE_EVENT"
| line_format "{{.events}}" |~ "book_search_executed"
# 処理時間が閾値を超えたリクエスト
{app="demo-api"} | json | msg="WIDE_EVENT" | durationMs > 1000
-- エラーリクエスト抽出
fields @timestamp, requestId, path, statusCode
| filter severity = "ERROR"
| sort @timestamp desc
-- 平均処理時間の集計
fields durationMs, path
| stats avg(durationMs) by path
| sort avg(durationMs) desc
| 項目 | 制約 | 備考 | |------|------|------| | Async処理 | 非対応 | @Asyncなど別スレッドではThreadLocalが分離される | | イベント数 | 無制限(推奨:100件以下) | 過多の場合はログサイズに注意 | | metadataサイズ | 制限なし(推奨:1KB以下) | 大きすぎるとシリアライズ遅延 | | スレッドセーフ | 対応済み | ThreadLocal使用により自動的に保証 |
ThreadLocalはスレッドローカル変数であり、別スレッドに処理を委譲すると値が引き継がれません。
問題の例:
@Service
public class BookService {
@Async("taskExecutor")
public CompletableFuture<Book> findByIdAsync(Long id) {
// 別スレッドで実行されるため、EventsCollector.getRequestId() は空文字を返す
EventsCollector.addEvent("book_search_started", Map.of("id", id));
// ... 処理 ...
}
}
対応策:
非同期処理内ではEventsCollectorを使用しない
手動でContextを渡す(必要な場合)
String requestId = EventsCollector.getRequestId();
return CompletableFuture.supplyAsync(() -> {
// MDCだけは引き継げる
MDC.put("requestId", requestId);
try {
// 処理実行(ただしEventsCollectorは使えない)
return process();
} finally {
MDC.remove("requestId");
}
}, taskExecutor);
分散トレーシングを導入する
docs/adr/20260313-wide-event-logging.mdapps/api/src/main/java/dev/ymkz/demo/api/infrastructure/logging/apps/api/src/test/java/dev/ymkz/demo/api/infrastructure/logging/WideEventLoggingFilterTest.javatools
npm/pnpmベースのモノレポでWireitを使用してビルドパイプラインの依存関係を管理し、キャッシュと並列実行を活用する場合に使用する。
testing
単体テストとインテグレーションテストを使い分ける場合に使用する。テストピラミッドに基づき、テストの責務、実行速度、メンテナンスコストを考慮して適切なテスト戦略を選択する。
development
Spiceflow is a super simple, fast, and type-safe API and React Server Components framework for TypeScript. Works on Node.js, Bun, and Cloudflare Workers. Use this skill whenever working with spiceflow to get the latest docs and API reference.
tools
pnpmワークスペースでcatalog機能を使用して複数パッケージの依存バージョンを一元管理する場合に使用する。strictモードによる厳格なバージョン管理を実現する。