DefaultErrorAttributesについて

エキサイト株式会社の中尾です。

RestControllerAdviceで拾えない例外処理を拾う方法を記載します。

よくある方法は、ExceptionHandlerで全てcatchする方法だと思います。

    @ExceptionHandler
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleNotFoundException(Exception exception) {
        this.writeLog(exception.getStackTrace());
        return "all Exception catch";
    }

しかし、これだとResponseStatusが全てINTERNAL_SERVER_ERRORになります。 ResponseStatusを指定しない場合、ステータスOKになってしまいます。

なぜこのようなことしないといけないのでしょうか?

そもそもデフォルトのエラーレスポンスはいかになっています。

{
  "timestamp": "2021-09-04T20:25:31.581+09:00",
  "status": 404,
  "error": "Not Found",
  "trace": "org.springframework.web.servlet.NoHandlerFoundException: No handler found for GET /aaa\n\tat org.springframework.web.servlet.DispatcherServlet.noHandlerFound(DispatcherServlet.java:1278)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1041)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter(ResourceUrlEncodingFilter.java:67)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142)\n\tat org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:834)\n",
  "message": "No handler found for GET /aaa",
  "path": "/aaa"
}

エラー情報ですぎですよね。 本番環境でこれだと困ると思います。 以下の設定を追加することで、traceログを非表示にできます。

server.error.include-message: never
server.error.include-binding-errors: never
server.error.include-stacktrace: never
server.error.include-exception: false

traceとmessageが消えました。

shogo.nakao@localhost: $ curl http://localhost:8080/aaa | jq                                                                                                    [/Users/shogo.nakao]
{
  "timestamp": "2021-09-04T20:34:27.516+09:00",
  "status": 404,
  "error": "Not Found",
  "path": "/aaa"
}

しかし、アプリケーションによって、エラーレスポンスの形は変わると思います。 アプリケーションに合わせてエラーレスポンスを変えてあげましょう。

@Component
@Profile("!local")
public class CustomErrorAttributes extends DefaultErrorAttributes {

    /**
     * error response default setting 
     *
     * @param webRequest
     * @param options
     * @return
     */
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        final String message = this.getMessage(webRequest, this.getError(webRequest));
        Map<String, Object> customErrorAttribute = new HashMap<>();
        customErrorAttribute.put("error",
                new ErrorResponse.ErrorData()
                        .setMessage(message));
        return customErrorAttribute;
    }
}
{
  "error": {
    "message": 404
   }
}

解説します。

DefaultErrorAttributesを継承することで、デフォルトエラーレスポンスの型を変えられます。 @Profile("!local")を設定することでローカル環境以外の場合に適応させます。 ローカル環境では詳細なトレースログみたいですからね。 どっちにしてもコンソールログに詳細なログは出力されますが。

final String message = this.getMessage(webRequest, this.getError(webRequest));

こちらで、this.getMessageとthis.getErrorはDefaultErrorAttributesの実装を見てください。