Files

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> &gt; <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> &gt; <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&lt;ApiResult&lt;Void&gt;&gt; handleBusinessException(
BusinessException exception,
HttpServletRequest request
) {
<span class="fc" id="L37"> log.warn(</span>
&quot;Business exception. exceptionType={} errorCode={} method={} requestPath={}&quot;,
<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은 &quot;응답이 작성되지 않음&quot;으로 해석해 리졸버 체인으로 재진입할 수 있고,
* 우리가 막으려는 재귀 실패가 바로 그것이다.
*
* 응답이 아직 커밋되지 않았다면 표준 ApiResult 본문을 시도한다. 직렬화 실패가
* 구조적인 원인이면 2차 시도도 같은 이유로 실패할 수 있는데, 그때는
* @ExceptionHandler(Exception.class) 안전망이 다시 잡아낸다.
*/
@ExceptionHandler(HttpMessageNotWritableException.class)
public ResponseEntity&lt;ApiResult&lt;Void&gt;&gt; handleMessageNotWritableException(
HttpMessageNotWritableException exception,
HttpServletRequest request,
HttpServletResponse response
) {
<span class="fc" id="L68"> log.error(</span>
&quot;Response body not writable. committed={} method={} requestPath={}&quot;,
<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&lt;ApiResult&lt;Void&gt;&gt; handleUncaughtException(
Exception exception,
HttpServletRequest request
) {
<span class="fc" id="L101"> log.error(</span>
&quot;Uncaught exception reached @ExceptionHandler safety net. exceptionType={} method={} requestPath={}&quot;,
<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>