diff --git a/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java b/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java index 82e5eab3..a4f78051 100644 --- a/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java +++ b/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java @@ -5,6 +5,7 @@ import lombok.experimental.Accessors; import org.springframework.http.HttpStatus; +import static com.dnd.runus.global.exception.type.ErrorType.Level.*; import static lombok.AccessLevel.PRIVATE; import static org.springframework.http.HttpStatus.*; @@ -13,43 +14,51 @@ @RequiredArgsConstructor(access = PRIVATE) public enum ErrorType { // WebErrorType - UNHANDLED_EXCEPTION(INTERNAL_SERVER_ERROR, "WEB_001", "직접적으로 처리되지 않은 예외, 문의해주세요"), - FAILED_VALIDATION(BAD_REQUEST, "WEB_002", "Request 요청에서 올바르지 않은 값이 있습니다"), - FAILED_PARSING(BAD_REQUEST, "WEB_003", "Request JSON body를 파싱하지 못했습니다"), - UNSUPPORTED_API(BAD_REQUEST, "WEB_004", "지원하지 않는 API입니다"), - COOKIE_NOT_FOND(BAD_REQUEST, "WEB_005", "요청에 쿠키가 필요합니다"), - INVALID_BASE64(BAD_REQUEST, "WEB_006", "잘못된 Base64 문자열입니다"), + UNHANDLED_EXCEPTION(INTERNAL_SERVER_ERROR, ERROR, "WEB_001", "직접적으로 처리되지 않은 예외, 문의해주세요"), + FAILED_VALIDATION(BAD_REQUEST, WARN, "WEB_002", "Request 요청에서 올바르지 않은 값이 있습니다"), + FAILED_PARSING(BAD_REQUEST, WARN, "WEB_003", "Request JSON body를 파싱하지 못했습니다"), + UNSUPPORTED_API(BAD_REQUEST, DEBUG, "WEB_004", "지원하지 않는 API입니다"), + COOKIE_NOT_FOND(BAD_REQUEST, DEBUG, "WEB_005", "요청에 쿠키가 필요합니다"), + INVALID_BASE64(BAD_REQUEST, DEBUG, "WEB_006", "잘못된 Base64 문자열입니다"), // AuthErrorType - FAILED_AUTHENTICATION(UNAUTHORIZED, "AUTH_001", "인증에 실패하였습니다"), - INVALID_ACCESS_TOKEN(UNAUTHORIZED, "AUTH_002", "유효하지 않은 토큰입니다"), - EXPIRED_ACCESS_TOKEN(UNAUTHORIZED, "AUTH_003", "만료된 토큰입니다"), - MALFORMED_ACCESS_TOKEN(UNAUTHORIZED, "AUTH_004", "잘못된 형식의 토큰입니다"), - TAMPERED_ACCESS_TOKEN(UNAUTHORIZED, "AUTH_005", "변조된 토큰입니다"), - UNSUPPORTED_JWT_TOKEN(UNAUTHORIZED, "AUTH_006", "지원하지 않는 JWT 토큰입니다"), - UNSUPPORTED_SOCIAL_TYPE(UNAUTHORIZED, "AUTH_007", "지원하지 않는 소셜 타입입니다."), - INVALID_CREDENTIALS(UNAUTHORIZED, "AUTH_008", "해당 사용자의 정보가 없거나 일치하지 않아 처리할 수 없습니다."), + FAILED_AUTHENTICATION(UNAUTHORIZED, INFO, "AUTH_001", "인증에 실패하였습니다"), + INVALID_ACCESS_TOKEN(UNAUTHORIZED, DEBUG, "AUTH_002", "유효하지 않은 토큰입니다"), + EXPIRED_ACCESS_TOKEN(UNAUTHORIZED, DEBUG, "AUTH_003", "만료된 토큰입니다"), + MALFORMED_ACCESS_TOKEN(UNAUTHORIZED, DEBUG, "AUTH_004", "잘못된 형식의 토큰입니다"), + TAMPERED_ACCESS_TOKEN(UNAUTHORIZED, DEBUG, "AUTH_005", "변조된 토큰입니다"), + UNSUPPORTED_JWT_TOKEN(UNAUTHORIZED, DEBUG, "AUTH_006", "지원하지 않는 JWT 토큰입니다"), + UNSUPPORTED_SOCIAL_TYPE(UNAUTHORIZED, DEBUG, "AUTH_007", "지원하지 않는 소셜 타입입니다"), + INVALID_CREDENTIALS(UNAUTHORIZED, DEBUG, "AUTH_008", "해당 사용자의 정보가 없거나 일치하지 않아 처리할 수 없습니다"), // OauthErrorType - USER_NOT_FOUND(NOT_FOUND, "OAUTH_001", "존재하지 않은 사용자 입니다."), + USER_NOT_FOUND(NOT_FOUND, DEBUG, "OAUTH_001", "존재하지 않은 사용자 입니다"), // DatabaseErrorType - ENTITY_NOT_FOUND(NOT_FOUND, "DB_001", "해당 엔티티를 찾을 수 없습니다"), - VIOLATION_OCCURRED(NOT_ACCEPTABLE, "DB_002", "저장할 수 없는 값입니다"), + ENTITY_NOT_FOUND(NOT_FOUND, DEBUG, "DB_001", "해당 엔티티를 찾을 수 없습니다"), + VIOLATION_OCCURRED(NOT_ACCEPTABLE, ERROR, "DB_002", "저장할 수 없는 값입니다"), // TimeErrorType - START_AFTER_END(BAD_REQUEST, "TIME_001", "시작 시간이 종료 시간보다 빨라야 합니다"), + START_AFTER_END(BAD_REQUEST, DEBUG, "TIME_001", "시작 시간이 종료 시간보다 빨라야 합니다"), // RunningErrorType - ROUTE_MUST_HAVE_AT_LEAST_TWO_COORDINATES(BAD_REQUEST, "RUNNING_001", "경로는 최소 2개의 좌표를 가져야 합니다"), - CHALLENGE_MODE_WITH_PERSONAL_GOAL(BAD_REQUEST, "RUNNING_002", "챌린지 모드에서는 개인 목표를 설정할 수 없습니다."), - GOAL_MODE_WITH_CHALLENGE_ID(BAD_REQUEST, "RUNNING_003", "개인 목표 모드에서는 챌린지 ID를 설정할 수 없습니다."), - GOAL_TIME_AND_DISTANCE_BOTH_EXIST(BAD_REQUEST, "RUNNING_004", "개인 목표 시간과 거리 중 하나만 설정해야 합니다."), + ROUTE_MUST_HAVE_AT_LEAST_TWO_COORDINATES(BAD_REQUEST, DEBUG, "RUNNING_001", "경로는 최소 2개의 좌표를 가져야 합니다"), + CHALLENGE_MODE_WITH_PERSONAL_GOAL(BAD_REQUEST, DEBUG, "RUNNING_002", "챌린지 모드에서는 개인 목표를 설정할 수 없습니다"), + GOAL_MODE_WITH_CHALLENGE_ID(BAD_REQUEST, DEBUG, "RUNNING_003", "개인 목표 모드에서는 챌린지 ID를 설정할 수 없습니다"), + GOAL_TIME_AND_DISTANCE_BOTH_EXIST(BAD_REQUEST, DEBUG, "RUNNING_004", "개인 목표 시간과 거리 중 하나만 설정해야 합니다"), // WeatherErrorType - WEATHER_API_ERROR(SERVICE_UNAVAILABLE, "WEATHER_001", "날씨 API 호출 중 오류가 발생했습니다"), + WEATHER_API_ERROR(SERVICE_UNAVAILABLE, WARN, "WEATHER_001", "날씨 API 호출 중 오류가 발생했습니다"), ; private final HttpStatus httpStatus; + private final Level level; private final String code; private final String message; + + public enum Level { + INFO, + DEBUG, + WARN, + ERROR + } } diff --git a/src/main/java/com/dnd/runus/presentation/handler/ExceptionRestHandler.java b/src/main/java/com/dnd/runus/presentation/handler/ExceptionRestHandler.java index a7a2531e..bfe9a867 100644 --- a/src/main/java/com/dnd/runus/presentation/handler/ExceptionRestHandler.java +++ b/src/main/java/com/dnd/runus/presentation/handler/ExceptionRestHandler.java @@ -26,13 +26,11 @@ public class ExceptionRestHandler { @ExceptionHandler(BaseException.class) public ResponseEntity handleBaseException(BaseException e) { - log.warn(e.getMessage()); return toResponseEntity(e.getType(), e.getMessage()); } @ExceptionHandler(AuthException.class) public ResponseEntity handleAuthException(AuthException e) { - log.warn(e.getMessage()); return toResponseEntity(e.getType(), e.getMessage()); } @@ -64,6 +62,11 @@ public ResponseEntity handleInsufficientAuthenticationException( return toResponseEntity(ErrorType.FAILED_AUTHENTICATION, e.getMessage()); } + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + return toResponseEntity(ErrorType.FAILED_VALIDATION, e.getMessage()); + } + ////////////////// 요청 파라미터 예외 / 타입 불일치, Enum 매개변수 관련 예외 @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity handleMethodArgumentTypeMismatchException( @@ -103,7 +106,6 @@ public ResponseEntity handleMethodArgumentTypeMismatchException( ////////////////// 직렬화 / 역직렬화 예외 @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { - log.warn(ex.getBindingResult().getAllErrors().toString()); return toResponseEntity( ErrorType.FAILED_VALIDATION, ex.getBindingResult().getAllErrors().toString()); @@ -117,21 +119,29 @@ public ResponseEntity handleHttpMessageNotReadableException(HttpMes ////////////////// Database 관련 예외 @ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { - log.warn(ex.getMessage(), ex); return toResponseEntity(ErrorType.VIOLATION_OCCURRED, ex); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex) { - log.warn(ex.getMessage(), ex); return toResponseEntity(ErrorType.VIOLATION_OCCURRED, ex); } private static ResponseEntity toResponseEntity(@NotNull ErrorType type, Exception exception) { - return toResponseEntity(type, exception.getMessage()); + return toResponseEntity(type, exception.getClass().getName() + ": " + exception.getMessage()); } private static ResponseEntity toResponseEntity(@NotNull ErrorType type, String message) { + loggingExceptionByErrorType(type, message); return ResponseEntity.status(type.httpStatus().value()).body(ApiErrorDto.of(type, message)); } + + private static void loggingExceptionByErrorType(ErrorType type, String message) { + switch (type.level()) { + case INFO -> log.info(message); + case DEBUG -> log.debug(message); + case WARN -> log.warn(message); + default -> log.error(message); + } + } }