java-logging について
Java開発者向けのロギングスキルで、SLF4Jの利用、LogbackとLog4j2の設定、構造化JSONログ、運用上の指針を扱います。パラメーター化メッセージ、コンテキストの記録、機密データの保護を重視します。
主な機能
- 01SLF4J APIの利用パターン
- 02LogbackとLog4j2の設定例
- 03MDCを使った構造化JSONログ
- 04ログレベルとパラメーター化メッセージ
- 05セキュリティとトラブルシューティングの指針
活用シーン
- 01Javaアプリケーションのロギング設定
- 02アプリケーションログへのリクエストコンテキスト追加
- 03ログの欠落や設定不備の調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill java-loggingインストール後、Claude Code で/java-loggingを入力して読み込まれたか確認します。
同梱ファイルquick-ref/log-levels.md
詳細
Java ロギング
アーキテクチャの概要
┌─────────────────────────────────────────────────────────────┐
│ Application Code │
│ (uses SLF4J API only) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SLF4J Facade │
│ (abstraction layer) │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Logback │ │ Log4j2 │ │ java.util │
│ │ │ │ │ .logging │
└──────────┘ └──────────┘ └──────────────┘
ルール:常に SLF4J API を使用してコードを記述してください。実装はランタイム依存関係です。
SLF4J API の使用方法
基本的なロギング
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public User findUser(Long id) {
log.debug("Finding user with id: {}", id);
try {
User user = repository.findById(id);
log.info("User found: {}", user.getEmail());
return user;
} catch (Exception e) {
log.error("Failed to find user with id: {}", id, e);
throw e;
}
}
}
Lombok を使用する場合
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class OrderService {
public void processOrder(Order order) {
log.info("Processing order: {}", order.getId());
}
}
ログレベル
| レベル | 用途 | 例 |
|---|---|---|
TRACE | 非常に詳細なデバッグ | ループの反復、変数の値 |
DEBUG | デバッグ情報 | メソッドの開始/終了、クエリパラメーター |
INFO | ビジネスイベント | ユーザーのログイン、注文の登録 |
WARN | 潜在的な問題 | 非推奨 API の使用、再試行 |
ERROR | 対応が必要なエラー | 例外の捕捉、操作の失敗 |
log.trace("Entering loop iteration {}", i);
log.debug("Query parameters: userId={}, status={}", userId, status);
log.info("Order {} placed successfully", orderId);
log.warn("Payment retry attempt {} of {}", attempt, maxRetries);
log.error("Failed to process payment for order {}", orderId, exception);
パラメーター化メッセージ(ベストプラクティス)
// GOOD - uses parameterized logging (efficient)
log.debug("Processing user {} with role {}", userId, role);
// BAD - string concatenation (always evaluated)
log.debug("Processing user " + userId + " with role " + role);
// For expensive operations, use isEnabled check
if (log.isDebugEnabled()) {
log.debug("Complex data: {}", computeExpensiveDebugInfo());
}
Logback の設定
Spring Boot(logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_PATH" value="${LOG_PATH:-logs}"/>
<property name="LOG_FILE" value="${LOG_FILE:-application}"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}-json.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-json.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
</encoder>
</appender>
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>
<logger name="com.yourcompany" level="DEBUG"/>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql" level="TRACE"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</root>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="JSON"/>
</root>
</springProfile>
</configuration>
application.yml の設定
logging:
level:
root: INFO
com.yourcompany: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG
pattern:
console: "%d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
file:
name: logs/application.log
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30
Log4j2 の設定
log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_PATH">logs</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<RollingFile name="File" fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="100MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<Async name="AsyncFile">
<AppenderRef ref="File"/>
</Async>
</Appenders>
<Loggers>
<Logger name="com.yourcompany" level="debug"/>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="AsyncFile"/>
</Root>
</Loggers>
</Configuration>
構造化ロギング(JSON)
依存関係
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
MDC(Mapped Diagnostic Context)
import org.slf4j.MDC;
@Component
public class RequestLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
try {
MDC.put("traceId", generateTraceId());
MDC.put("userId", getCurrentUserId());
MDC.put("requestPath", ((HttpServletRequest) request).getRequestURI());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
構造化ログの出力
import static net.logstash.logback.argument.StructuredArguments.*;
log.info("Order processed",
kv("orderId", order.getId()),
kv("customerId", order.getCustomerId()),
kv("amount", order.getTotal()),
kv("currency", "USD"));
出力:
{
"@timestamp": "2025-01-15T10:30:00.000Z",
"level": "INFO",
"logger": "com.example.OrderService",
"message": "Order processed",
"orderId": "ORD-12345",
"customerId": "CUST-789",
"amount": 99.99,
"currency": "USD",
"traceId": "abc123",
"userId": "user456"
}
ベストプラクティス
推奨事項
- あらゆる場所で SLF4J API を使用する
- パラメーター化メッセージを使用する
log.info("User {}", userId) - 相関 ID(traceId、requestId)を含める
- 適切なログレベルを使用する
- 本番環境では非同期アペンダーを使用する
- ログローテーションを設定する
- ログ集約には JSON 形式を使用する
禁止事項
- ログメッセージで文字列連結を使用しない
- 機密データ(パスワード、トークン、個人識別情報)をログに記録しない
- レベルを確認せずに高頻度のループ内でログを記録しない
- ロギングに System.out.println を使用しない
- 例外を捕捉して、何もせずに握りつぶさない
セキュリティ
// BAD - logs sensitive data
log.info("User login: email={}, password={}", email, password);
// GOOD - mask sensitive data
log.info("User login: email={}", maskEmail(email));
// Helper
private String maskEmail(String email) {
int atIndex = email.indexOf('@');
if (atIndex > 2) {
return email.substring(0, 2) + "***" + email.substring(atIndex);
}
return "***";
}
このスキルを使用しない場合
- SLF4J API のみについての質問:API の使用パターンには
slf4jスキルを使用してください - Logback の設定の詳細:XML 設定には
logbackスキルを使用してください - Node.js/Python プロジェクト:各言語に適したロギングスキルを使用してください
- アプリケーションコードのパターン:実装ではなく、SLF4J API に重点を置いてください
- フレームワークの移行:移行に特化したガイドを参照してください
アンチパターン
| アンチパターン | 問題点 | 解決策 |
|---|---|---|
| System.out.println の使用 | 制御できず、永続化されず、フィルタリングもできない | SLF4J ロガーを使用する |
| ログでの文字列連結 | 常に評価され、パフォーマンスが低下する | パラメーター化ロギングを使用する:log.info("User {}", id) |
| 本番環境で非同期アペンダーを使用しない | アプリケーションのスレッドをブロックする | AsyncAppender でラップする |
| マルチスレッドアプリで MDC を使用せずにロギングする | リクエストのコンテキストが失われる | 相関 ID には MDC を使用する |
| 本番環境で DEBUG レベルを使用する | パフォーマンスへの影響、ディスク使用量の増加 | 本番環境では INFO または WARN を使用する |
| 機密データをマスキングしない | セキュリティ/コンプライアンス違反 | ログ記録の前にパスワード、トークン、個人識別情報をフィルタリングする |
トラブルシューティングの早見表
| 問題 | 原因 | 解決策 |
|---|---|---|
| NoClassDefFoundError: StaticLoggerBinder | SLF4J 実装がない | Logback または Log4j2 の依存関係を追加する |
| 複数バインディングの警告 | クラスパス上に複数の実装がある | Logback または Log4j2 のいずれか 1 つだけを残す |
| ログが表示されない | ログレベルの誤り、または設定がない | logback.xml とログレベルを確認する |
| パフォーマンスの低下 | 同期アペンダー | AsyncAppender ラッパーを使用する |
| ログがローテーションされない | ローリングポリシーがない | RollingFileAppender を設定する |
| MDC の値が表示されない | パターンに %X{key} がない | ログパターンに MDC プレースホルダーを追加する |
参考資料
コメント
Markdown 対応