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(); ApiResponseCode responseCode = ApiResponseCode.from(codeName, status); ErrorLogEntity errorLog = saveErrorLogData( request, responseCode, 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); } }