From db3560a8194f389c24b7ee8db1a72b8ff5b093e0 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 06:25:59 +0000 Subject: [PATCH] =?UTF-8?q?TA=20=EC=97=AD=ED=95=A0=20Spring=20=EA=B2=BD?= =?UTF-8?q?=EA=B3=84=20smoke=20(role-ta-live-1522-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/GlobalExceptionHandler.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/com/runtimematrix/config/GlobalExceptionHandler.java diff --git a/src/main/java/com/runtimematrix/config/GlobalExceptionHandler.java b/src/main/java/com/runtimematrix/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..d2a2a99 --- /dev/null +++ b/src/main/java/com/runtimematrix/config/GlobalExceptionHandler.java @@ -0,0 +1,65 @@ +package com.runtimematrix.config; + +import com.runtimematrix.controller.dto.ErrorResponse; +import com.runtimematrix.domain.exception.BusinessException; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.Instant; +import java.util.UUID; +import java.util.stream.Collectors; + +/** 전역 예외 처리기 - RFC 7807 Problem Details 형식으로 변환 */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + MDC.put("traceId", traceId); + log.warn("[{}] {} - {}", traceId, ex.getErrorCode(), ex.getMessage()); + + return ResponseEntity.status(ex.getHttpStatus()) + .body(ErrorResponse.of(ex.getType(), ex.getErrorCode(), ex.getMessage(), + ex.getHttpStatus(), request.getRequestURI(), traceId)); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationException(MethodArgumentNotValidException ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + String detail = ex.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage).collect(Collectors.joining("; ")); + log.warn("[{}] Validation failed: {}", traceId, detail); + + return ResponseEntity.badRequest() + .body(ErrorResponse.of("https://api.runtimematrix.com/errors/validation-failed", + "validation-failed", "입력 검증에 실패했습니다: " + detail, + HttpStatus.BAD_REQUEST.value(), request.getRequestURI(), traceId)); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + log.error("[{}] Unexpected error", traceId, ex); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ErrorResponse.of("https://api.runtimematrix.com/errors/internal-server-error", + "internal-server-error", "예상치 못한 오류가 발생했습니다.", + HttpStatus.INTERNAL_SERVER_ERROR.value(), request.getRequestURI(), traceId)); + } + + private String getOrGenerateTraceId() { + String existing = MDC.get("traceId"); + return existing != null ? existing : UUID.randomUUID().toString().substring(0, 12); + } +}