package com.kamco.cd.kamcoback.config; import com.kamco.cd.kamcoback.auth.CustomUserDetails; import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.common.exception.DuplicateFileException; import com.kamco.cd.kamcoback.common.exception.ValidationException; import com.kamco.cd.kamcoback.config.api.ApiLogFunction; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; import com.kamco.cd.kamcoback.members.exception.MemberException; import com.kamco.cd.kamcoback.postgres.entity.ErrorLogEntity; import com.kamco.cd.kamcoback.postgres.repository.log.ErrorLogRepository; import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.http.HttpServletRequest; import java.nio.file.AccessDeniedException; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.multipart.MaxUploadSizeExceededException; @Slf4j @Order(value = 1) @RestControllerAdvice public class GlobalExceptionHandler { private final ErrorLogRepository errorLogRepository; public GlobalExceptionHandler(ErrorLogRepository errorLogRepository) { this.errorLogRepository = errorLogRepository; } @ResponseStatus(HttpStatus.CONFLICT) @ExceptionHandler(DuplicateFileException.class) public ApiResponseDto handleDuplicateFileException( DuplicateFileException e, HttpServletRequest request) { log.warn("[DuplicateFileException] resource :{} ", e.getMessage()); this.errorLog(request, e); ApiResponseCode code = ApiResponseCode.CONFLICT; ErrorLogEntity errorLog = saveErrorLogData( request, code, HttpStatus.CONFLICT, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( code, e.getMessage(), HttpStatus.CONFLICT, errorLog.getId()); } @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(ValidationException.class) public ApiResponseDto handleValidationException( ValidationException e, HttpServletRequest request) { log.warn("[ValidationException] resource :{} ", e.getMessage()); this.errorLog(request, e); ApiResponseCode code = ApiResponseCode.UNPROCESSABLE_ENTITY; ErrorLogEntity errorLog = saveErrorLogData( request, code, HttpStatus.UNPROCESSABLE_ENTITY, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( code, e.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY, errorLog.getId()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(IllegalArgumentException.class) public ApiResponseDto handleIllegalArgumentException( IllegalArgumentException e, HttpServletRequest request) { log.warn("[IllegalArgumentException] resource :{} ", e.getMessage()); this.errorLog(request, e); ApiResponseCode code = ApiResponseCode.BAD_REQUEST; ErrorLogEntity errorLog = saveErrorLogData( request, code, HttpStatus.BAD_REQUEST, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( code, e.getMessage(), HttpStatus.BAD_REQUEST, errorLog.getId()); } @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(EntityNotFoundException.class) public ApiResponseDto handlerEntityNotFoundException( EntityNotFoundException e, HttpServletRequest request) { log.warn("[EntityNotFoundException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "NOT_FOUND_DATA"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), errorLog.getId()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(HttpMessageNotReadableException.class) public ApiResponseDto handleUnreadable( HttpMessageNotReadableException e, HttpServletRequest request) { log.warn("[HttpMessageNotReadableException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "BAD_REQUEST"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(NoSuchElementException.class) public ApiResponseDto handlerNoSuchElementException( NoSuchElementException e, HttpServletRequest request) { log.warn("[NoSuchElementException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "NOT_FOUND_DATA"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("NOT_FOUND"), errorLog.getId()); } @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(DataIntegrityViolationException.class) public ApiResponseDto handlerDataIntegrityViolationException( DataIntegrityViolationException e, HttpServletRequest request) { log.warn("[DataIntegrityViolationException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "DATA_INTEGRITY_ERROR"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), errorLog.getId()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) public ApiResponseDto handlerMethodArgumentNotValidException( MethodArgumentNotValidException e, HttpServletRequest request) { log.warn("[MethodArgumentNotValidException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "BAD_REQUEST"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } @ResponseStatus(HttpStatus.FORBIDDEN) @ExceptionHandler(AccessDeniedException.class) public ApiResponseDto handlerAccessDeniedException( AccessDeniedException e, HttpServletRequest request) { log.warn("[AccessDeniedException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "FORBIDDEN"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.ERROR, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } @ResponseStatus(HttpStatus.BAD_GATEWAY) @ExceptionHandler(HttpServerErrorException.BadGateway.class) public ApiResponseDto handlerHttpServerErrorException( HttpServerErrorException e, HttpServletRequest request) { log.warn("[HttpServerErrorException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "BAD_GATEWAY"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(IllegalStateException.class) public ApiResponseDto handlerIllegalStateException( IllegalStateException e, HttpServletRequest request) { log.warn("[IllegalStateException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "UNPROCESSABLE_ENTITY"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MemberException.DuplicateMemberException.class) public ApiResponseDto handlerDuplicateMemberException( MemberException.DuplicateMemberException e, HttpServletRequest request) { log.warn("[DuplicateMemberException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = ""; switch (e.getField()) { case EMPLOYEE_NO -> { codeName = "DUPLICATE_EMPLOYEEID"; } default -> { codeName = "DUPLICATE_DATA"; } } ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf("BAD_REQUEST"), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("BAD_REQUEST"), errorLog.getId()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MemberException.MemberNotFoundException.class) public ApiResponseDto handlerMemberNotFoundException( MemberException.MemberNotFoundException e, HttpServletRequest request) { log.warn("[MemberNotFoundException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "NOT_FOUND_USER"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf("BAD_REQUEST"), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("BAD_REQUEST"), errorLog.getId()); } @ResponseStatus(HttpStatus.CONFLICT) @ExceptionHandler(DuplicateKeyException.class) public ApiResponseDto handlerDuplicateKeyException( DuplicateKeyException e, HttpServletRequest request) { log.warn("[DuplicateKeyException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "DUPLICATE_DATA"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf("CONFLICT"), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("CONFLICT"), errorLog.getId()); } @ExceptionHandler(BadCredentialsException.class) public ResponseEntity> handleBadCredentials( BadCredentialsException e, HttpServletRequest request) { log.warn("[BadCredentialsException] resource : {} ", e.getMessage()); this.errorLog(request, e); String codeName = "UNAUTHORIZED"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); ApiResponseDto body = ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) // ๐Ÿ”ฅ ์—ฌ๊ธฐ์„œ 401 ์ง€์ • .body(body); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(RuntimeException.class) public ApiResponseDto handlerRuntimeException( RuntimeException e, HttpServletRequest request) { log.warn("[RuntimeException] resource :{} ", e.getMessage()); this.errorLog(request, e); String codeName = "INTERNAL_SERVER_ERROR"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) public ApiResponseDto handlerException(Exception e, HttpServletRequest request) { this.errorLog(request, e); String codeName = "INTERNAL_SERVER_ERROR"; ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); } /** * ์—๋Ÿฌ ๋กœ๊ทธ ํ…Œ์ด๋ธ” ์ €์žฅ ๋กœ์ง * * @param request : request * @param errorCode : ์ •์˜๋œ enum errorCode * @param httpStatus : HttpStatus ๊ฐ’ * @param logErrorLevel : WARNING, ERROR, CRITICAL * @param stackTrace : ์—๋Ÿฌ ๋‚ด์šฉ * @return : insertํ•˜๊ณ  ๊ฒฐ๊ณผ๋กœ ๋ฐ›์€ Entity */ private ErrorLogEntity saveErrorLogData( HttpServletRequest request, ApiResponseCode errorCode, HttpStatus httpStatus, ErrorLogDto.LogErrorLevel logErrorLevel, StackTraceElement[] stackTrace) { Long userid = null; /** * servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth ์ด ์š”์ฒญ์ด * JWT ์ธ์ฆ์„ ํ†ต๊ณผํ•œ ์š”์ฒญ์ธ๊ฐ€? ๊ทธ๋ฆฌ๊ณ  Spring Security Authentication ๊ฐ์ฒด๊ฐ€ UsernamePasswordAuthenticationToken * ํƒ€์ž…์ธ๊ฐ€? ์ฒดํฌ */ /** * auth.getPrincipal() instanceof CustomUserDetails customUserDetails principal ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ๊ฐ์ฒด๊ฐ€ ๋‚ด๊ฐ€ * ๋งŒ๋“  CustomUserDetails ํƒ€์ž…์ธ๊ฐ€? ์ฒดํฌ */ if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth && auth.getPrincipal() instanceof CustomUserDetails customUserDetails) { // audit ์—๋Š” long ํƒ€์ž… user_id๊ฐ€ ๋“ค์–ด๊ฐ€์ง€๋งŒ ํ† ํฐ sub์€ uuid์—ฌ์„œ user_id ๊ฐ€์ ธ์˜ค๊ธฐ userid = customUserDetails.getMember().getId(); } String stackTraceStr = Arrays.stream(stackTrace) .map(StackTraceElement::toString) .collect(Collectors.joining("\n")) .substring(0, 255); ErrorLogEntity errorLogEntity = new ErrorLogEntity( request.getRequestURI(), ApiLogFunction.getEventType(request), logErrorLevel, String.valueOf(httpStatus.value()), errorCode.getText(), stackTraceStr, userid, ZonedDateTime.now()); return errorLogRepository.save(errorLogEntity); } @ExceptionHandler(CustomApiException.class) public ApiResponseDto handleCustomApiException( CustomApiException e, HttpServletRequest request) { log.warn("[CustomApiException] resource : {}", e.getMessage()); this.errorLog(request, e); String codeName = e.getCodeName(); HttpStatus status = e.getStatus(); // String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) : // e.getMessage(); // // ApiResponseCode apiCode = ApiResponseCode.getCode(codeName); // // ErrorLogEntity errorLog = // saveErrorLogData( // request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); // // ApiResponseDto body = // ApiResponseDto.createException(apiCode, message, status, errorLog.getId()); ErrorLogEntity errorLog = saveErrorLogData( request, ApiResponseCode.getCode(codeName), HttpStatus.valueOf(status.value()), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(status.value()), errorLog.getId()); // return new ResponseEntity<>(body, status); } @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE) @ExceptionHandler(MaxUploadSizeExceededException.class) public ApiResponseDto handleMaxUploadSizeExceeded( MaxUploadSizeExceededException e, HttpServletRequest request) { log.warn("[MaxUploadSizeExceededException] resource :{} ", e.getMessage()); this.errorLog(request, e); ApiResponseCode code = ApiResponseCode.PAYLOAD_TOO_LARGE; ErrorLogEntity errorLog = saveErrorLogData( request, code, HttpStatus.PAYLOAD_TOO_LARGE, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); return ApiResponseDto.createException( code, code.getText(), HttpStatus.PAYLOAD_TOO_LARGE, errorLog.getId()); } private void errorLog(HttpServletRequest request, Throwable e) { String uri = request != null ? request.getRequestURI() : "N/A"; // ์˜ˆ์™ธ ํƒ€์ž…๋ช… (IllegalArgumentException, BadCredentialsException ๋“ฑ) String exceptionName = e.getClass().getSimpleName(); log.error("[{}] uri={}, message={}", exceptionName, uri, e.getMessage()); log.error("[{}] stacktrace", exceptionName, e); } }