こんにちは、かつコーチです。
Spring Bootは標準でSLF4J(Simple Logging Facade for Java、複数のログ実装を統一的なAPIで扱えるファサード)とLogbackの組み合わせを内蔵しています。
そのためbuild.gradleに何も追加しなくてもログ出力自体はできるのですが、設定を何もいじらないままだと本番運用に耐えるログにはなりません。
今回は、Spring Boot特有のログ設定と、実務でつまずいたポイントを整理します。
Spring Bootにおけるログの基本
何も設定しなくてもログが出る仕組み
Spring Bootのspring-boot-starterにはspring-boot-starter-loggingが推移的に含まれており、これがSLF4JとLogbackを自動でセットアップしてくれます。
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
public void placeOrder(Order order) {
logger.info("注文を受け付けました: orderId={}", order.getId());
}
}
Lombokを使っている場合は、@Slf4jアノテーション1つでこのLoggerの定義を省略できます。
@Slf4j
@Service
public class OrderService {
public void placeOrder(Order order) {
log.info("注文を受け付けました: orderId={}", order.getId());
}
}
application.propertiesでログレベルを制御する
コード側を変更せずに、設定ファイルだけでログの出力量を調整できるのがSLF4Jの利点です。
# 自分のアプリケーションのパッケージはDEBUGまで出す
logging.level.com.example.demo=DEBUG
# Spring Framework自体のログはWARN以上のみ
logging.level.org.springframework=WARN
# SQLログを確認したいとき
logging.level.org.hibernate.SQL=DEBUG
パッケージ単位で細かくレベルを制御できるため、「自分のコードは詳しく見たいが、フレームワーク内部のログは減らしたい」といった要望に柔軟に対応できます。
ログをファイルに出力する
基本設定
開発中はコンソール出力だけで十分ですが、本番環境ではファイルへの出力が必須になります。
logging.file.name=logs/app.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
logback-spring.xmlでの詳細設定
より細かく制御したい場合は、src/main/resources/logback-spring.xmlを用意します。
<configuration>
<springProfile name="prod">
<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} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</springProfile>
</configuration>
<springProfile name="prod">を使うと、環境別application.properties切り替えの記事で紹介したspring.profiles.activeの値に応じて、ログ設定自体を切り替えられます。
開発環境ではコンソールに出力し、本番環境ではファイルにローテーション付きで出力する、という使い分けが1つの設定ファイルで完結します。
実務でつまずいたポイント:リクエストの追跡ができない
問題:どのリクエストのログか分からない
複数のリクエストが同時に処理されるSpring Bootアプリでは、ログを時系列に眺めても、どのログがどのリクエストに属するのか分からなくなることがあります。
実際に僕が保守していたAPIで障害調査をした際、次のようなログが交互に出力されており、原因の特定に時間がかかった経験があります。
2026-08-31 10:00:01 INFO OrderService - 注文を受け付けました: orderId=1001
2026-08-31 10:00:01 INFO OrderService - 注文を受け付けました: orderId=1002
2026-08-31 10:00:01 ERROR OrderService - 在庫チェックに失敗しました
2026-08-31 10:00:01 INFO OrderService - 在庫を確保しました: orderId=1001
エラーがどちらの注文(1001か1002か)で発生したものか、ログだけでは判別できませんでした。
解決:MDCでリクエストごとの識別子を付与する
MDC(Mapped Diagnostic Context)を使うと、スレッドごとに紐づく識別子をログに自動で埋め込めます。
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String requestId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("requestId", requestId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
ログのパターンに%X{requestId}を追加すると、MDCに詰めた値が自動で各行に出力されます。
logging.pattern.console=%d{HH:mm:ss} [%X{requestId}] %-5level %logger{36} - %msg%n
10:00:01 [a1b2c3d4] INFO OrderService - 注文を受け付けました: orderId=1001
10:00:01 [e5f6g7h8] INFO OrderService - 注文を受け付けました: orderId=1002
10:00:01 [e5f6g7h8] ERROR OrderService - 在庫チェックに失敗しました
これで、エラーがorderId=1002側のリクエスト(requestId=e5f6g7h8)で起きたことが一目で分かるようになりました。
finallyブロックでMDC.clear()を忘れると、スレッドプールで使い回されたスレッドに前のリクエストの識別子が残り続けるバグにつながるため、必ずセットで書くようにしてください。
まとめ
この記事のポイント
- Spring BootはSLF4J+Logbackを標準で内蔵しており、追加設定なしでもログが出せる
application.propertiesのパッケージ単位設定でログレベルを細かく制御できるlogback-spring.xmlとspringProfileを組み合わせると環境別のログ設定を切り替えられる- 複数リクエストが交錯する環境ではMDCでリクエストごとの識別子を付与すると調査効率が上がる
MDC.put()した値はfinallyで必ずMDC.clear()する
次に読むべき記事
- 環境ごとにapplication.propertiesを切り替える
- よくあるSpring Bootのエラーまとめ(BeanCreationException等)
- @ControllerAdviceで例外ハンドリングを一元化する
タグ: Spring Boot, 中級者向け, エラー解決