Java
Z
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
Javaのログ出力ベストプラクティス — SLF4J + Logback 実践ガイド
System.out.println をやめてSLF4J+Logbackに移行する方法、ログレベルの使い分け、MDCを使ったリクエストID付与、ログローテーション設定を解説します。
一言結論
本番コードにSystem.out.printlnを使うのは論外で、SLF4J+Logbackを使いMDCでリクエストIDを付与することで分散環境でも1リクエストのログを追跡可能にするのが現代の標準だ。
なぜ System.out.println を使ってはいけないか
| 問題 | 説明 |
|---|---|
| レベルがない | エラーも情報も同じ扱い |
| 無効化できない | 本番でも全部出力される |
| フォーマットが貧弱 | タイムスタンプ、スレッド名などがない |
| パフォーマンス | 同期処理でスループットが下がる |
SLF4J + Logback の導入
<!-- pom.xml -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<!-- SLF4J API は logback-classic が自動で含む -->
Spring Boot を使っている場合は spring-boot-starter-logging が自動で含まれます。
基本的な使い方
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
// クラスごとにロガーを定義(static final 推奨)
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public User findUser(int id) {
log.debug("findUser called: id={}", id);
User user = repository.find(id);
if (user == null) {
log.warn("User not found: id={}", id);
return null;
}
log.info("User found: id={}, name={}", id, user.getName());
return user;
}
public void deleteUser(int id) {
try {
repository.delete(id);
log.info("User deleted: id={}", id);
} catch (Exception e) {
log.error("Failed to delete user: id={}", id, e); // 例外はこう渡す
throw e;
}
}
}
重要: log.error("message" + e.getMessage()) ではなく log.error("message", e) でスタックトレースが出力されます。
ログレベルの使い分け
| レベル | 使い場面 |
|---|---|
TRACE | 非常に詳細な処理追跡(開発時のみ) |
DEBUG | デバッグに必要な情報(テスト環境向け) |
INFO | 正常な動作の記録(起動、主要な処理完了) |
WARN | 問題ではないが注意が必要な事象 |
ERROR | エラー(処理は続行可能) |
{} プレースホルダーを使う理由
// NG: 文字列結合(ログが無効でも結合が実行される)
log.debug("User: " + user.toString());
// OK: プレースホルダー(ログが無効なら toString() も実行されない)
log.debug("User: {}", user);
// NG: isDebugEnabled() チェックは通常不要
if (log.isDebugEnabled()) {
log.debug("User: {}", user.toString()); // {} を使えばこれも不要
}
Logback の設定(logback.xml)
src/main/resources/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}.%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory> <!-- 30日分保持 -->
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{requestId}] - %msg%n</pattern>
</encoder>
</appender>
<!-- パッケージごとのレベル設定 -->
<logger name="com.example" level="DEBUG"/>
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<!-- ルートロガー -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
MDC(Mapped Diagnostic Context)でリクエストIDを追跡
import org.slf4j.MDC;
// サーブレットフィルターやインターセプターで設定
public class RequestIdFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String requestId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("requestId", requestId);
try {
chain.doFilter(req, res);
} finally {
MDC.clear(); // スレッドプールなので必ずクリア
}
}
}
logback.xml のパターンで %X{requestId} として出力します。これで同一リクエストのログが追跡しやすくなります。
2026-04-06 10:30:00.123 [http-nio-8080-exec-1] INFO UserService [a1b2c3d4] - User found: id=42
2026-04-06 10:30:00.456 [http-nio-8080-exec-1] INFO OrderService [a1b2c3d4] - Order created: 99
環境ごとのログ設定
Spring Boot の場合 application.properties で簡単に設定:
# 開発環境
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.file.name=logs/app.log
logging.logback.rollingpolicy.max-file-size=100MB
logging.logback.rollingpolicy.max-history=30
Lombok の @Slf4j アノテーション
// Lombok を使うと1行で済む
@Slf4j
public class UserService {
public void process() {
log.info("Processing..."); // log は自動生成
}
}
まとめ
System.out.printlnを SLF4J + Logback に移行する{}プレースホルダーを使って文字列結合を避ける- 例外は
log.error("msg", e)で渡す(スタックトレースが出る) - MDC でリクエスト ID を付与して追跡可能にする
- ログローテーションでディスクを圧迫しない設定にする