Springの例外処理におけるログ設計を完全ガイド!初心者でもわかるスタックトレース・相関ID・マスク方針
生徒
「Spring Bootの例外処理で、ログってどう設計すればいいんですか?」
先生
「良い質問だね。例外時のログ設計は、後から不具合を調査するときにとても重要なんだ。スタックトレースを残すだけではなく、相関IDやマスクのルールも考慮する必要があるよ。」
生徒
「マスク?相関ID?なんだか難しそうです…」
先生
「安心して。今回はSpringの例外処理におけるログ設計のベストプラクティスを、初心者向けにわかりやすく解説するから、一緒に学んでいこう!」
1. Springの例外処理とは?
Spring Bootでは、アプリケーションで発生した例外を適切に処理するために、@ControllerAdviceや@ExceptionHandlerといったアノテーションがよく使われます。これにより、共通の例外処理ロジックを集中管理できます。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAllExceptions(Exception ex) {
// ログ出力など
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("サーバー内部でエラーが発生しました");
}
}
2. スタックトレースを出力する目的と注意点
スタックトレースは、どのクラスのどの行で例外が発生したかを示す非常に重要な情報です。ログにスタックトレースを出力することで、開発者が問題を特定しやすくなります。
ただし、スタックトレースをそのまま外部(クライアント)に返すのはセキュリティ上非常に危険です。あくまでログファイルにのみ出力するようにしましょう。
log.error("予期せぬエラーが発生しました", ex); // スタックトレース付きでログ出力
3. 相関ID(Correlation ID)とは?
分散システムやマイクロサービス環境では、1つのリクエストが複数のサービスを横断することがあります。このとき「どのログが同じリクエストに関係しているのか」を追跡するのが困難になります。
そこで登場するのが「相関ID(Correlation ID)」です。リクエスト単位で一意のIDを付与し、それを全てのログに含めることで、後から追跡が可能になります。
@Component
public class CorrelationIdFilter extends OncePerRequestFilter {
private static final String CORRELATION_ID = "X-Correlation-ID";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String correlationId = request.getHeader(CORRELATION_ID);
if (correlationId == null) {
correlationId = UUID.randomUUID().toString();
}
MDC.put(CORRELATION_ID, correlationId); // ログに含める
response.setHeader(CORRELATION_ID, correlationId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove(CORRELATION_ID);
}
}
}
ログ出力にはMDC(Mapped Diagnostic Context)を使うことで、自動的にログパターンへ相関IDを追加できます。
4. ログに機密情報を含めないためのマスク処理
ログ出力時に、パスワードやクレジットカード番号、個人情報(PII)などの機密情報を出力してしまうと、大きなセキュリティリスクになります。
そのため、ログ出力時には特定の項目をマスクするように設計しておくことが重要です。以下は例として、リクエストボディからマスク対象の項目を伏せ字に変換するフィルター処理です。
public class LogMaskingUtil {
private static final List<String> SENSITIVE_KEYS = Arrays.asList("password", "creditCard");
public static String maskSensitiveData(String json) {
for (String key : SENSITIVE_KEYS) {
json = json.replaceAll("(\"" + key + "\":\")([^\"\\\\]*)", "$1****");
}
return json;
}
}
マスク対象のキーは、業務要件に合わせて適宜追加してください。ログ出力前にこのユーティリティを通すことで、漏洩リスクを大幅に下げられます。
5. Spring Bootでのロガー設定と出力形式の設計
Spring Bootではapplication.propertiesまたはapplication.ymlでログ出力のフォーマットを簡単にカスタマイズできます。logback-spring.xmlを使えば、さらに柔軟な設定が可能です。
以下は、相関IDやログレベル、スレッド名、メッセージなどを出力するフォーマットの例です。
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%X{X-Correlation-ID}] [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
上記のように相関IDを%X{X-Correlation-ID}で指定することで、MDCに格納されたIDがログに出力されます。
6. 開発環境と本番環境でのログレベル設計
開発環境では詳細なデバッグログを出力して問題の特定をしやすくする一方、本番環境ではログレベルを調整し、ログの量を抑える必要があります。Spring Bootでは以下のように設定できます。
# application-dev.properties
logging.level.root=DEBUG
# application-prod.properties
logging.level.root=INFO
特定のパッケージやクラス単位でログレベルを個別に設定することも可能です。無駄なログを減らしつつ、重要な情報だけを残すように心がけましょう。
7. APIエラーレスポンスの標準化とログ連携
API利用者に返すエラーメッセージは、ログと連携しやすいように標準化するのがベストプラクティスです。内部的なエラーIDや相関IDを含めて返却することで、ユーザーからの問い合わせにもスムーズに対応できます。
public class ErrorResponse {
private String errorCode;
private String message;
private String correlationId;
// getter/setter
}
これにより、ユーザーが画面に表示された相関IDを運用者に伝えるだけで、どのログを見ればよいかが明確になります。
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazon広告リンク