logging-patterns について
logging-patternsは、JavaやSpringアプリケーションのログ設定と調査を行う開発者向けのリファレンススキルです。構造化JSONログ、相関コンテキスト、トレース、安全な記録方法を扱います。
主な機能
- 01構造化JSONログの設定
- 02MDCによる相関IDの付与
- 03OpenTelemetryトレースとの相関
- 04パラメーター化ログと例外記録
- 05機密データのログ出力指針
活用シーン
- 01本番環境やCI向けログの設定
- 02ログ行を使ったリクエスト追跡
- 03遅い処理やエラーの調査
- 04ログへの秘密情報や個人データの混入防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ piomin/ claude-ai-spring-boot --skill logging-patternsインストール後、Claude Code で/logging-patternsを入力して読み込まれたか確認します。
詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
ロギングパターンのスキル
ルール:ログ形式のデフォルトは JSON です。 プレーンテキストのロギングは、human-logs プロファイルを使用したローカル開発でのみ使用します。本番環境、CI/CD、Kubernetes 環境では、構造化 JSON を使用する必要があります。
使用する場面
- ユーザーが「ロギングを追加して」「ログを改善して」「これをデバッグして」と依頼した場合
- ログからアプリケーションのフローを分析する場合
- 構造化ロギング(JSON)を設定する場合
- 相関 ID を使ってリクエストをトレースする場合
- AI/Claude Code がアプリケーションの動作を分析する必要がある場合
AI に適したロギング
重要なポイント: JSON ログは AI による分析に適しています。解析が速く、トークン数が少なく、フィールドに直接アクセスできます。
AI/Claude Code に JSON を使う理由
# Text format - AI must "interpret" the string
2026-01-29 10:15:30 INFO OrderService - Order 12345 created for user-789, total: 99.99
# JSON format - AI extracts fields directly
{"timestamp":"2026-01-29T10:15:30Z","level":"INFO","orderId":12345,"userId":"user-789","total":99.99}
| 項目 | テキスト | JSON |
|---|---|---|
| 解析 | 正規表現/解釈 | フィールドに直接アクセス |
| トークン使用量 | 多い(パターンが繰り返される) | 少ない(構造化されている) |
| エラーの抽出 | スタックトレースのテキストを解析 | exception フィールド |
| フィルタリング | grep パターン | jq クエリ |
AI 支援開発におすすめの設定
# application.yml - JSON by default
logging:
structured:
format:
console: logstash # Spring Boot 4.x
# When YOU need to read logs manually:
# Option 1: Use jq
# tail -f app.log | jq .
# Option 2: Switch profile temporarily
# java -jar app.jar --spring.profiles.active=human-logs
AI 分析に最適化されたログ形式
{
"timestamp": "2026-01-29T10:15:30.123Z",
"level": "INFO",
"logger": "pl.piomin.services.OrderService",
"message": "Order created",
"requestId": "req-abc123",
"traceId": "trace-xyz",
"orderId": 12345,
"userId": "user-789",
"duration_ms": 45,
"step": "payment_completed"
}
AI によるデバッグに重要なフィールド:
requestId- 同じリクエストのすべてのログをグループ化しますstep- フローの進行状況を追跡しますduration_ms- 処理が遅い操作を特定しますlevel- エラーをすばやく絞り込みます
AI/Claude Code を使ったログの読み取り
AI にログの分析を依頼する場合:
# Get recent errors
cat app.log | jq 'select(.level == "ERROR")' | tail -20
# Follow specific request
cat app.log | jq 'select(.requestId == "req-abc123")'
# Find slow operations
cat app.log | jq 'select(.duration_ms > 1000)'
AI は次のことができます:
- JSON を直接解析する(推測は不要です)
- requestId を使ってリクエストのフローを追跡する
- エラーが発生した箇所を正確に特定する
- ステップ間の所要時間を測定する
クイックセットアップ(Spring Boot 4.x)
ネイティブの構造化ロギング
Spring Boot 4.x には JSON ロギングが組み込まれているため、追加の依存関係は不要です。
# application.yml — this is the REQUIRED default for every app
logging:
structured:
format:
console: logstash # or "ecs" for Elastic Common Schema
# Supported formats:
# logstash - JSON (most common, compatible with ELK and OpenTelemetry Collector)
# ecs - Elastic Common Schema (native Elastic/Kibana integration)
# gelf - Graylog Extended Log Format
OpenTelemetry との統合
Micrometer Tracing がクラスパスに含まれている場合、すべての JSON ログ行に traceId と spanId が自動的に含まれます。これにより、OTel Collector はログと分散トレースを関連付けられます。
# application.yml — tracing + structured logging together
management:
tracing:
sampling:
probability: 1.0
logging:
structured:
format:
console: logstash
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
出力:
{"@timestamp":"2026-01-29T10:15:30.123Z","level":"INFO","logger_name":"pl.piomin.services.OrderService","message":"Order created","traceId":"abc123def456","spanId":"789xyz","service.name":"order-service"}
JSON ログの traceId フィールドは、Jaeger、Tempo、または OTel 互換のバックエンドで確認できるトレース ID と同じです。これにより、ログとトレースを直接関連付けられます。
プロファイルに基づく切り替え
# application.yml (default - JSON for AI/prod)
spring:
profiles:
default: json-logs
---
spring:
config:
activate:
on-profile: json-logs
logging:
structured:
format:
console: logstash
---
spring:
config:
activate:
on-profile: human-logs
# No structured format = human-readable default
logging:
pattern:
console: "%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
使用方法:
# Default: JSON (for AI, CI/CD, production)
./mvnw spring-boot:run
# Human-readable when needed
./mvnw spring-boot:run -Dspring.profiles.active=human-logs
Spring Boot 3.4 未満のセットアップ(レガシー)
Logstash Logback Encoder
pom.xml:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProfile name="!human-logs">
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>requestId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON"/>
</root>
</springProfile>
<springProfile name="human-logs">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
カスタムフィールドの追加(Logstash Encoder)
import static net.logstash.logback.argument.StructuredArguments.kv;
// Fields appear as separate JSON keys
log.info("Order created",
kv("orderId", order.getId()),
kv("userId", user.getId()),
kv("total", order.getTotal()),
kv("step", "order_created")
);
// Output:
// {"message":"Order created","orderId":123,"userId":"u-456","total":99.99,"step":"order_created"}
SLF4J の基本
Logger の宣言
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
// use `log` directly for logging
}
パラメーター化ロギング
// ✅ GOOD: Evaluated only if level enabled
log.debug("Processing order {} for user {}", orderId, userId);
// ❌ BAD: Always concatenates
log.debug("Processing order " + orderId + " for user " + userId);
// ✅ For expensive operations
if (log.isDebugEnabled()) {
log.debug("Order details: {}", order.toDetailedString());
}
例外のロギング
// ✅ GOOD: Pass exception as last argument
try {
processOrder(order);
} catch (PaymentException e) {
log.error("Payment failed for order {}", order.getId(), e);
}
// ❌ BAD: Loses stack trace
log.error("Payment failed: " + e.getMessage());
// ❌ BAD: Exception in placeholder (toString only)
log.error("Payment failed for order {}: {}", order.getId(), e);
ログレベルのガイド
| レベル | 使用する場面 |
|---|---|
ERROR | 問題が発生し、対応が必要な場合。決済失敗、回復不能な状態 |
WARN | 想定外の事態が発生したものの、処理できた場合。リトライ成功、フォールバックの使用 |
INFO | ビジネスイベント。注文の作成、ユーザーのログイン、ジョブの完了 |
DEBUG | 技術的な詳細。SQL クエリ、HTTP リクエスト、キャッシュヒット |
TRACE | 詳細な内部情報。メソッドの開始/終了、ループの反復 |
MDC(Mapped Diagnostic Context)
MDC は、コンテキスト(リクエスト ID、ユーザー ID)を現在のスレッドのすべてのログ行に自動的に付加します。
相関 ID 用リクエストフィルター
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MdcFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestId = httpRequest.getHeader("X-Request-Id");
if (requestId == null) {
requestId = UUID.randomUUID().toString().substring(0, 8);
}
MDC.put("requestId", requestId);
MDC.put("clientIp", httpRequest.getRemoteAddr());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
サービス層での MDC
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public Order createOrder(OrderRequest request) {
MDC.put("userId", request.userId());
MDC.put("orderId", String.valueOf(request.orderId()));
log.info("Creating order");
// All subsequent log lines in this thread automatically include userId and orderId
return processOrder(request);
}
}
仮想スレッドでの MDC
仮想スレッドは、親スレッドから MDC を継承しません。TaskDecorator を使って伝播させます:
@Configuration
public class AsyncConfig {
@Bean
public TaskDecorator mdcTaskDecorator() {
return runnable -> {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
if (context != null) {
MDC.setContextMap(context);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
};
}
}
クイックリファレンス
| パターン | 用途 |
|---|---|
LoggerFactory.getLogger(MyClass.class) | ロガーを宣言します(@Slf4j は使用しません) |
log.info("msg {}", var) | パラメーター化ロギング |
log.error("msg {}", var, exception) | スタックトレース付きでログを記録します |
MDC.put("key", "value") | スレッド内のすべてのログにコンテキストを追加します |
MDC.clear() | finally ブロックでクリーンアップします |
logging.structured.format.console: logstash | JSON を有効にします(Spring Boot 4.x) |
kv("field", value) | 構造化引数(Logstash encoder) |