2026年6月9日火曜日

SpringBoot のエラーハンドリングで500エラー時にトレースバックを表示する方法

SpringBoot のエラーハンドリングで500エラー時にトレースバックを表示する方法

概要

これまでカスタムエラーを作成しそれをハンドリングし返却できるようにしました
今回はそれにトレースバックを表示する方法を紹介します

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
    • jasypt-spring-boot 4.0.4
    • jackson-databind 2.21.2
    • jobrunr 8.6.1
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0
  • Redis 8.6.3

エラーハンドラの修正

各種エラーをハンドリングした際に Exception 情報は受け取れるのでそこからスタックトレース情報を取得します
今回は dev の場合だけレスポンスにも trace 情報を含めるようにしていますがこの機能は不要であれば削除して OK です

@RestControllerAdvice で定義したエラーハンドラのクラスでは引数で Environment が受け取れるのでこれを元にデプロイ環境を判断しています

  • vim src/main/java/com/example/demo/exception/GlobalExceptionHandler.java
package com.example.demo.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.example.demo.exception.dto.ErrorResponse;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@RestControllerAdvice
public class GlobalExceptionHandler {

        private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
        private final Environment environment;

        public GlobalExceptionHandler(Environment environment) {
                this.environment = environment;
        }

        @ExceptionHandler(BusinessException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public ErrorResponse handleBusinessException(BusinessException ex) {
                return new ErrorResponse(ex.getCode(), ex.getMessage(), LocalDateTime.now());
        }

        @ExceptionHandler(Exception.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ErrorResponse handleException(Exception ex) {
                log.error("Unexpected internal server error", ex);
                String trace = isDevProfile() ? getStackTrace(ex) : null;
                return new ErrorResponse("INTERNAL_SERVER_ERROR", "予期しないエラーが発生しました", LocalDateTime.now(), trace);
        }

        private boolean isDevProfile() {
                return Arrays.stream(environment.getActiveProfiles()).anyMatch("dev"::equalsIgnoreCase);
        }

        private String getStackTrace(Exception ex) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                ex.printStackTrace(pw);
                pw.flush();
                return sw.toString();
        }
}

エラーレスポンス用の DTO の修正

レスポンスに trace 情報を含めたい場合は追加してください
不要な場合はこちらの修正は不要です

  • vim src/main/java/com/example/demo/exception/dto/ErrorResponse.java
package com.example.demo.exception.dto;

import java.time.LocalDateTime;

public class ErrorResponse {

        private String code;
        private String message;
        private LocalDateTime timestamp;
        private String trace;

        public ErrorResponse(String code, String message, LocalDateTime timestamp) {
                this(code, message, timestamp, null);
        }

        public ErrorResponse(String code, String message, LocalDateTime timestamp, String trace) {
                this.code = code;
                this.message = message;
                this.timestamp = timestamp;
                this.trace = trace;
        }

        public String getCode() {
                return code;
        }

        public String getMessage() {
                return message;
        }

        public LocalDateTime getTimestamp() {
                return timestamp;
        }

        public String getTrace() {
                return trace;
        }
}

動作確認

  • ./gradlew clean && ./gradlew bootRun --args='--jasypt.encryptor.password=xxx'

で500エラーを発生させトレースバックが表示されることを確認しましょう

最後に

ハンドリングできている40x系のエラーはトレース不要なケースが多いですが500系のエラーは基本ハンドリングできていない予期せぬエラーの場合が多いのでその場合はトレースバックをちゃんと出力させましょう

0 件のコメント:

コメントを投稿