こんにちは!バックエンドエンジニアのキョンです。今回は、実務で本当に大切な「ログ出力」について、現場での経験を元に詳しく解説していきます。
目次
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.gradle
やpom.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());
}
}
まとめ
ログ出力は、システム運用の要です。私の経験から、以下のポイントを意識すると良いでしょう:
- 適切なログレベルの使用
- トランザクションの追跡可能性
- パフォーマンスへの配慮
- セキュリティへの考慮
実践的なTips
- 本番環境ではINFOレベル以上を出力
- 開発環境ではDEBUGレベルも活用
- エラーログには必ずコンテキスト情報を含める
- ログローテーションを適切に設定
次回は「Javaのユニットテスト」について解説する予定です。お楽しみに!
コメント