116 lines
7.6 KiB
HTML
116 lines
7.6 KiB
HTML
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">project-auth-server</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">ApplicationExceptionHandler.java</span></div><h1>ApplicationExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
|
|
|
import com.project.auth.application.support.exception.BusinessException;
|
|
import com.project.auth.application.support.exception.CommonErrorCode;
|
|
import com.project.auth.application.support.logging.LogSanitizer;
|
|
import com.project.auth.presentation.support.response.ApiResult;
|
|
import com.project.auth.presentation.support.response.ApiResultFactory;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.core.Ordered;
|
|
import org.springframework.core.annotation.Order;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.http.converter.HttpMessageNotWritableException;
|
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
|
|
@RestControllerAdvice
|
|
@Order(Ordered.LOWEST_PRECEDENCE)
|
|
public class ApplicationExceptionHandler {
|
|
|
|
<span class="fc" id="L24"> private static final Logger log = LoggerFactory.getLogger(ApplicationExceptionHandler.class);</span>
|
|
|
|
private final ApiResultFactory apiResultFactory;
|
|
|
|
<span class="fc" id="L28"> public ApplicationExceptionHandler(ApiResultFactory apiResultFactory) {</span>
|
|
<span class="fc" id="L29"> this.apiResultFactory = apiResultFactory;</span>
|
|
<span class="fc" id="L30"> }</span>
|
|
|
|
@ExceptionHandler(BusinessException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleBusinessException(
|
|
BusinessException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L37"> log.warn(</span>
|
|
"Business exception. exceptionType={} errorCode={} method={} requestPath={}",
|
|
<span class="fc" id="L39"> exception.getClass().getSimpleName(),</span>
|
|
<span class="fc" id="L40"> exception.getErrorCode().code(),</span>
|
|
<span class="fc" id="L41"> request.getMethod(),</span>
|
|
<span class="fc" id="L42"> LogSanitizer.requestPath(request.getRequestURI())</span>
|
|
);
|
|
|
|
<span class="fc" id="L45"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(exception.getErrorCode()))</span>
|
|
<span class="fc" id="L46"> .body(apiResultFactory.failure(exception.getErrorCode().code(), exception.getErrorCode().message()));</span>
|
|
}
|
|
|
|
/**
|
|
* 응답 직렬화 실패에 대한 정직한 처리.
|
|
*
|
|
* 응답이 이미 커밋된 상태(이미 바이트가 wire에 나간 상태)라면 회복 경로가 없다.
|
|
* 같은 직렬화 경로로 본문을 또 쓰려고 하면 동일한 실패가 재발하거나 무시된다.
|
|
* 이때는 본문 없는 빈 ResponseEntity(500)를 반환한다 — @ExceptionHandler가 null을
|
|
* 반환하면 Spring은 "응답이 작성되지 않음"으로 해석해 리졸버 체인으로 재진입할 수 있고,
|
|
* 우리가 막으려는 재귀 실패가 바로 그것이다.
|
|
*
|
|
* 응답이 아직 커밋되지 않았다면 표준 ApiResult 본문을 시도한다. 직렬화 실패가
|
|
* 구조적인 원인이면 2차 시도도 같은 이유로 실패할 수 있는데, 그때는
|
|
* @ExceptionHandler(Exception.class) 안전망이 다시 잡아낸다.
|
|
*/
|
|
@ExceptionHandler(HttpMessageNotWritableException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMessageNotWritableException(
|
|
HttpMessageNotWritableException exception,
|
|
HttpServletRequest request,
|
|
HttpServletResponse response
|
|
) {
|
|
<span class="fc" id="L68"> log.error(</span>
|
|
"Response body not writable. committed={} method={} requestPath={}",
|
|
<span class="fc" id="L70"> response.isCommitted(),</span>
|
|
<span class="fc" id="L71"> request.getMethod(),</span>
|
|
<span class="fc" id="L72"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
exception
|
|
);
|
|
|
|
<span class="fc bfc" id="L76" title="All 2 branches covered."> if (response.isCommitted()) {</span>
|
|
<span class="fc" id="L77"> return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
|
|
}
|
|
|
|
<span class="fc" id="L80"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.MESSAGE_NOT_WRITABLE))</span>
|
|
<span class="fc" id="L81"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L82"> PresentationErrorCode.MESSAGE_NOT_WRITABLE.code(),</span>
|
|
<span class="fc" id="L83"> PresentationErrorCode.MESSAGE_NOT_WRITABLE.message()</span>
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 더 구체적인 @ExceptionHandler에 매칭되지 않은 모든 예외에 대한 최후의 안전망.
|
|
*
|
|
* 이 핸들러가 없으면 매칭되지 않은 예외는 advice를 우회해 {@code /error}로 흘러가고,
|
|
* Spring Boot 기본 응답 형태가 반환되어 {@link ApiResult} 계약이 깨진다.
|
|
*
|
|
* 항상 500 + COMMON-999를 반환하고, 알 수 없는 장애 모드를 운영이 식별할 수 있도록
|
|
* ERROR 레벨로 전체 스택트레이스를 남긴다.
|
|
*/
|
|
@ExceptionHandler(Exception.class)
|
|
public ResponseEntity<ApiResult<Void>> handleUncaughtException(
|
|
Exception exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L101"> log.error(</span>
|
|
"Uncaught exception reached @ExceptionHandler safety net. exceptionType={} method={} requestPath={}",
|
|
<span class="fc" id="L103"> exception.getClass().getName(),</span>
|
|
<span class="fc" id="L104"> request.getMethod(),</span>
|
|
<span class="fc" id="L105"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
exception
|
|
);
|
|
|
|
<span class="fc" id="L109"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(CommonErrorCode.INTERNAL_SERVER_ERROR))</span>
|
|
<span class="fc" id="L110"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L111"> CommonErrorCode.INTERNAL_SERVER_ERROR.code(),</span>
|
|
<span class="fc" id="L112"> CommonErrorCode.INTERNAL_SERVER_ERROR.message()</span>
|
|
));
|
|
}
|
|
}
|
|
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.14.202510111229</span></div></body></html> |