MENU

【現場で使える】Javaのログ出力完全ガイド!現役エンジニアが教えるログの全て

こんにちは!バックエンドエンジニアのキョンです。今回は、実務で本当に大切な「ログ出力」について、現場での経験を元に詳しく解説していきます。

目次

1. Javaのログ出力基礎

なぜログが必要?

入社当時の私は「System.out.println()でいいじゃん」って思ってました(笑) でも本番環境で障害が起きた時、ログがないと原因究明できないんですよね…

ログレベルの使い分け

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingExample {
    private static final Logger logger = 
        LoggerFactory.getLogger(LoggingExample.class);
    
    public void demonstration() {
        // エラー:システムの異常など重大な問題
        logger.error("データベース接続エラー: {}", e.getMessage());
        
        // 警告:想定外だが復旧可能な問題
        logger.warn("APIレスポンスが遅延しています: {}ms", responseTime);
        
        // 情報:システムの状態変更など
        logger.info("ユーザー{}がログインしました", userId);
        
        // デバッグ:開発時に必要な詳細情報
        logger.debug("リクエストパラメータ: {}", requestParams);
        
        // トレース:より詳細なデバッグ情報
        logger.trace("メソッド開始: {}", methodName);
    }
}

2. SLF4J + Logbackの使い方

基本設定

まずはbuild.gradlepom.xmlに依存関係を追加:

<!-- pom.xmlの場合 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>

logback.xmlの設定例

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- コンソール出力 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
    
    <!-- ファイル出力 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
    
    <!-- ロガーの設定 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

3. 実践的なログ設計

トランザクションIDの追跡

分散システムでよく使用するパターンです:

public class TransactionLogger {
    private static final Logger logger = 
        LoggerFactory.getLogger(TransactionLogger.class);
    private static final String TRANSACTION_ID = "transactionId";
    
    public void processOrder(Order order) {
        String txId = generateTransactionId();
        MDC.put(TRANSACTION_ID, txId);
        
        try {
            logger.info("注文処理開始: 注文ID={}", order.getId());
            // 処理実行
            processOrderDetails(order);
            logger.info("注文処理完了");
        } catch (Exception e) {
            logger.error("注文処理エラー: {}", e.getMessage(), e);
            throw e;
        } finally {
            MDC.remove(TRANSACTION_ID);
        }
    }
}

構造化ログ

JSON形式でログを出力する例:

public class StructuredLogger {
    private static final Logger logger = 
        LoggerFactory.getLogger(StructuredLogger.class);
    
    public void logUserAction(String userId, String action, Map<String, Object> details) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("userId", userId);
        logData.put("action", action);
        logData.put("details", details);
        logData.put("timestamp", System.currentTimeMillis());
        
        logger.info("ユーザーアクション: {}", new ObjectMapper().writeValueAsString(logData));
    }
}

4. ログ出力のベストプラクティス

パフォーマンスを考慮したログ出力

public class PerformanceLogger {
    private static final Logger logger = 
        LoggerFactory.getLogger(PerformanceLogger.class);
    
    public void processWithLogging(String data) {
        // 良い例:ログレベルチェック
        if (logger.isDebugEnabled()) {
            logger.debug("処理開始: データ長={}", data.length());
        }
        
        // 悪い例:無条件の文字列連結
        logger.debug("処理開始: データ長=" + data.length());  // 非効率
    }
}

センシティブ情報の取り扱い

public class SecurityLogger {
    private static final Logger logger = 
        LoggerFactory.getLogger(SecurityLogger.class);
    
    public void logUserLogin(User user) {
        // 悪い例
        logger.info("ログイン: email={}, password={}", 
            user.getEmail(), user.getPassword());
        
        // 良い例
        logger.info("ログイン: userId={}, maskedEmail={}", 
            user.getId(), maskEmail(user.getEmail()));
    }
    
    private String maskEmail(String email) {
        if (email == null) return null;
        int atIndex = email.indexOf('@');
        if (atIndex > 1) {
            return email.substring(0, 1) + "***" + email.substring(atIndex);
        }
        return email;
    }
}

5. トラブルシューティング

ログローテーション

// logback.xmlでのログローテーション設定
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/app.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>
            logs/app-%d{yyyy-MM-dd}-%i.log
        </fileNamePattern>
        <maxFileSize>100MB</maxFileSize>
        <maxHistory>30</maxHistory>
        <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
</appender>

エラー時のスタックトレース出力

public class ErrorLogger {
    private static final Logger logger = 
        LoggerFactory.getLogger(ErrorLogger.class);
    
    public void handleException(Exception e) {
        // 良い例:完全なスタックトレースを出力
        logger.error("エラーが発生しました", e);
        
        // 悪い例:スタックトレース情報の欠落
        logger.error("エラーが発生しました: {}", e.getMessage());
    }
}

まとめ

ログ出力は、システム運用の要です。私の経験から、以下のポイントを意識すると良いでしょう:

  1. 適切なログレベルの使用
  2. トランザクションの追跡可能性
  3. パフォーマンスへの配慮
  4. セキュリティへの考慮

実践的なTips

  • 本番環境ではINFOレベル以上を出力
  • 開発環境ではDEBUGレベルも活用
  • エラーログには必ずコンテキスト情報を含める
  • ログローテーションを適切に設定

次回は「Javaのユニットテスト」について解説する予定です。お楽しみに!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次