mcpField
L
logging-patterns

logging-patterns

github.com/piomin開発1.4k68 回インストール6 日前に更新
リポジトリ

logging-patterns について

logging-patternsは、JavaやSpringアプリケーションのログ設定と調査を行う開発者向けのリファレンススキルです。構造化JSONログ、相関コンテキスト、トレース、安全な記録方法を扱います。

主な機能

  1. 01構造化JSONログの設定
  2. 02MDCによる相関IDの付与
  3. 03OpenTelemetryトレースとの相関
  4. 04パラメーター化ログと例外記録
  5. 05機密データのログ出力指針

活用シーン

  1. 01本番環境やCI向けログの設定
  2. 02ログ行を使ったリクエスト追跡
  3. 03遅い処理やエラーの調査
  4. 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 は次のことができます:

  1. JSON を直接解析する(推測は不要です)
  2. requestId を使ってリクエストのフローを追跡する
  3. エラーが発生した箇所を正確に特定する
  4. ステップ間の所要時間を測定する

クイックセットアップ(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: logstashJSON を有効にします(Spring Boot 4.x)
kv("field", value)構造化引数(Logstash encoder)

コメント