-
Notifications
You must be signed in to change notification settings - Fork 0
전역 예외 처리 및 공통 예외 구조 구현 #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6e80989
feat: 전역 예외 처리 클래스(GlobalExceptionHandler) 구현
dnzp75 ec1a1cd
feat: 모든 도메인에서 사용할 공통 예외 인터페이스 및 ServiceException 구현
dnzp75 5dc474f
feat: 공통 예외 응답 클래스(ApiErrorResponse) 및 에러 코드 기반 구조 생성
dnzp75 82b3e2c
feat: 외부 API 호출에 대한 예외 처리 클래스 추가
dnzp75 ac43355
feat: RestTemplate용 ResponseErrorHandler 및 설정 클래스 추가
dnzp75 5c50c2d
feat: 도메인 예외 사용 예시로 Comment 도메인 Exception 구조 예시 추가
dnzp75 d03d624
feat: 테스트 시 401 상태 코드로 인한 스프링 시큐리티 설정 임시 주석 처리
dnzp75 ecf432b
remove: Test API 제거
dnzp75 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
src/main/java/com/example/log4u/common/advice/GlobalExceptionHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| package com.example.log4u.common.advice; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.http.HttpStatusCode; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.lang.NonNull; | ||
| import org.springframework.validation.BindException; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
| import org.springframework.web.context.request.ServletWebRequest; | ||
| import org.springframework.web.context.request.WebRequest; | ||
| import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; | ||
|
|
||
| import com.example.log4u.common.exception.ApiErrorResponse; | ||
| import com.example.log4u.common.exception.CommonErrorCode; | ||
| import com.example.log4u.common.exception.base.ErrorCode; | ||
| import com.example.log4u.common.exception.base.ServiceException; | ||
|
|
||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @RestControllerAdvice | ||
| @Slf4j | ||
| public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { | ||
|
|
||
| @Override | ||
| public ResponseEntity<Object> handleMethodArgumentNotValid( | ||
| MethodArgumentNotValidException e, | ||
| @NonNull HttpHeaders headers, | ||
| @NonNull HttpStatusCode status, | ||
| @NonNull WebRequest request) { | ||
| HttpServletRequest servletRequest = ((ServletWebRequest)request).getRequest(); | ||
|
|
||
| String requestUrl = servletRequest.getRequestURI(); | ||
| String httpMethod = servletRequest.getMethod(); | ||
| List<String> errors = e.getBindingResult() | ||
| .getFieldErrors() | ||
| .stream() | ||
| .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| log.warn("Validation failed for request to {} {}. Errors: {}", | ||
| httpMethod, requestUrl, errors); | ||
| CommonErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; | ||
| return handleExceptionInternal(e, errorCode); | ||
| } | ||
|
|
||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public ResponseEntity<ApiErrorResponse> handleIllegalArgument(IllegalArgumentException e) { | ||
| String location = getExceptionLocation(e); | ||
|
|
||
| log.warn("Illegal argument encountered at {}: {}", location, e.getMessage()); | ||
|
|
||
| CommonErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; | ||
| return handleExceptionInternal(errorCode); | ||
| } | ||
|
|
||
| @ExceptionHandler(ServiceException.class) | ||
| public ResponseEntity<ApiErrorResponse> handleGiveMeTiConException(ServiceException e) { | ||
| String location = getExceptionLocation(e); | ||
| log.warn("Error invoke in our app at {}: {} ErrorCode: {}", location, e.getMessage(), e.getErrorCode()); | ||
| ErrorCode errorCode = e.getErrorCode(); | ||
| return handleExceptionInternal(errorCode); | ||
| } | ||
|
|
||
| @ExceptionHandler({Exception.class}) | ||
| public ResponseEntity<ApiErrorResponse> handleAllException(Exception e) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 경우 로그에 HttpMethod나 API 경로 같은게 포함되면 더 로그 파악이 좋을 것 같아요
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그럴 수 있겠네요. 확인 감사합니다 |
||
| String location = getExceptionLocation(e); | ||
| log.warn("Unhandled exception occurred at {}: {}", location, e.getMessage()); | ||
|
|
||
| CommonErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR; | ||
| return handleExceptionInternal(errorCode); | ||
| } | ||
|
|
||
| private ResponseEntity<ApiErrorResponse> handleExceptionInternal(ErrorCode errorCode) { | ||
| return ResponseEntity.status(errorCode.getHttpStatus()) | ||
| .body(makeErrorResponse(errorCode)); | ||
| } | ||
|
|
||
| private ApiErrorResponse makeErrorResponse(ErrorCode errorCode) { | ||
| return ApiErrorResponse.builder() | ||
| .errorMessage(errorCode.getErrorMessage()) | ||
| .errorCode(errorCode.getHttpStatus().value()) | ||
| .build(); | ||
| } | ||
|
|
||
| private ResponseEntity<Object> handleExceptionInternal(BindException e, ErrorCode errorCode) { | ||
| return ResponseEntity.status(errorCode.getHttpStatus()) | ||
| .body(makeErrorResponse(e, errorCode)); | ||
| } | ||
|
|
||
| private ApiErrorResponse makeErrorResponse(BindException e, ErrorCode errorCode) { | ||
| List<ApiErrorResponse.ValidationError> validationErrorList = e.getBindingResult() | ||
| .getFieldErrors() | ||
| .stream() | ||
| .map(ApiErrorResponse.ValidationError::of) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| return ApiErrorResponse.builder() | ||
| .errorMessage(errorCode.getErrorMessage()) | ||
| .errorCode(errorCode.getHttpStatus().value()) | ||
| .errors(validationErrorList) | ||
| .build(); | ||
| } | ||
|
|
||
| private String getExceptionLocation(Exception e) { | ||
| StackTraceElement element = e.getStackTrace()[0]; | ||
| return element.getClassName() + "." + element.getMethodName() + ":" + element.getLineNumber(); | ||
| } | ||
| } | ||
29 changes: 29 additions & 0 deletions
29
src/main/java/com/example/log4u/common/exception/ApiErrorResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.example.log4u.common.exception; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.validation.FieldError; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @RequiredArgsConstructor | ||
| public class ApiErrorResponse { | ||
| private final String errorMessage; | ||
| private final int errorCode; | ||
|
|
||
| @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||
| private final List<ValidationError> errors; | ||
|
|
||
| public record ValidationError(String field, String message) { | ||
|
|
||
| public static ValidationError of(final FieldError fieldError) { | ||
| return new ValidationError(fieldError.getField(), fieldError.getDefaultMessage()); | ||
| } | ||
| } | ||
| } |
33 changes: 33 additions & 0 deletions
33
src/main/java/com/example/log4u/common/exception/CommonErrorCode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.example.log4u.common.exception; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| import com.example.log4u.common.exception.base.ErrorCode; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum CommonErrorCode implements ErrorCode { | ||
|
|
||
| INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "잘못된 요청입니다"), | ||
| UNAUTHENTICATED(HttpStatus.UNAUTHORIZED,"로그인이 필요한 기능입니다."), | ||
| FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다"), | ||
| RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "요청 정보를 찾을 수 없습니다"), | ||
| INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다. 관리자에게 문의하세요.") | ||
| ; | ||
|
|
||
| private final HttpStatus httpStatus; | ||
| private final String message; | ||
|
|
||
| @Override | ||
| public HttpStatus getHttpStatus() { | ||
| return this.httpStatus; | ||
| } | ||
|
|
||
| @Override | ||
| public String getErrorMessage() { | ||
| return this.message; | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/example/log4u/common/exception/base/ErrorCode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.log4u.common.exception.base; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public interface ErrorCode { | ||
| String name(); | ||
| HttpStatus getHttpStatus(); | ||
| String getErrorMessage(); | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/example/log4u/common/exception/base/ServiceException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.example.log4u.common.exception.base; | ||
|
|
||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class ServiceException extends RuntimeException { | ||
|
|
||
| private final ErrorCode errorCode; | ||
|
|
||
| public ServiceException(ErrorCode errorCode) { | ||
| super(errorCode.getErrorMessage()); | ||
| this.errorCode = errorCode; | ||
| } | ||
| } |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/example/log4u/common/external/ClientConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.example.log4u.common.external; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.web.client.RestTemplate; | ||
|
|
||
| import com.example.log4u.common.external.hanlder.ApiResponseErrorHandler; | ||
|
|
||
| @Configuration | ||
| public class ClientConfig { | ||
|
|
||
| @Bean | ||
| public RestTemplate restTemplate() { | ||
| RestTemplate restTemplate = new RestTemplate(); | ||
| restTemplate.setErrorHandler(new ApiResponseErrorHandler()); | ||
| return restTemplate; | ||
| } | ||
| } |
13 changes: 13 additions & 0 deletions
13
src/main/java/com/example/log4u/common/external/exception/ExternalApiRequestException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.example.log4u.common.external.exception; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public class ExternalApiRequestException extends RuntimeException{ | ||
|
|
||
| private final String statusCode; | ||
| private final String message; | ||
|
|
||
| } |
34 changes: 34 additions & 0 deletions
34
src/main/java/com/example/log4u/common/external/hanlder/ApiResponseErrorHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.example.log4u.common.external.hanlder; | ||
|
|
||
| import java.io.BufferedReader; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import org.springframework.http.client.ClientHttpResponse; | ||
| import org.springframework.web.client.ResponseErrorHandler; | ||
|
|
||
| import com.example.log4u.common.external.exception.ExternalApiRequestException; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @Slf4j | ||
| public class ApiResponseErrorHandler implements ResponseErrorHandler { | ||
| @Override | ||
| public boolean hasError(ClientHttpResponse response) throws IOException { | ||
| return !response.getStatusCode().is2xxSuccessful(); | ||
| } | ||
|
|
||
| @Override | ||
| public void handleError(ClientHttpResponse response) throws IOException { | ||
| String body; | ||
| try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.getBody()))) { | ||
| body = reader.lines().collect(Collectors.joining("\n")); | ||
| } | ||
|
|
||
| log.error("API 호출 중 에러 발생: HTTP 상태 코드: {}, 응답 본문: {}", response.getStatusCode().value(), body); | ||
|
|
||
| throw new ExternalApiRequestException(response.getStatusCode().toString(), | ||
| "API 호출 중 에러 발생: " + response.getStatusCode().value() + " 응답 본문: " + body); | ||
| } | ||
| } |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/example/log4u/domain/comment/exception/CommentErrorCode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.example.log4u.domain.comment.exception; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| import com.example.log4u.common.exception.base.ErrorCode; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum CommentErrorCode implements ErrorCode { | ||
|
|
||
| NOT_FOUND_COMMENT(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."); | ||
|
|
||
| private final HttpStatus httpStatus; | ||
| private final String message; | ||
|
|
||
| @Override | ||
| public HttpStatus getHttpStatus() { | ||
| return httpStatus; | ||
| } | ||
|
|
||
| @Override | ||
| public String getErrorMessage() { | ||
| return message; | ||
| } | ||
| } |
10 changes: 10 additions & 0 deletions
10
src/main/java/com/example/log4u/domain/comment/exception/CommentException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.log4u.domain.comment.exception; | ||
|
|
||
| import com.example.log4u.common.exception.base.ErrorCode; | ||
| import com.example.log4u.common.exception.base.ServiceException; | ||
|
|
||
| public class CommentException extends ServiceException { | ||
| public CommentException(ErrorCode errorCode) { | ||
| super(errorCode); | ||
| } | ||
| } |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/example/log4u/domain/comment/exception/NotFoundCommentException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example.log4u.domain.comment.exception; | ||
|
|
||
| public class NotFoundCommentException extends CommentException { | ||
| public NotFoundCommentException() { | ||
| super(CommentErrorCode.NOT_FOUND_COMMENT); | ||
| } | ||
| } |
34 changes: 34 additions & 0 deletions
34
src/main/java/com/example/log4u/domain/comment/testController/TestController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.example.log4u.domain.comment.testController; | ||
|
|
||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import com.example.log4u.domain.comment.testDto.TestRequest; | ||
| import com.example.log4u.domain.comment.exception.NotFoundCommentException; | ||
|
|
||
| import jakarta.validation.Valid; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/test") | ||
| public class TestController { | ||
|
|
||
| @PostMapping("/valid") | ||
| public ResponseEntity<Void> testValidation(@RequestBody @Valid TestRequest request) { | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @GetMapping("/illegal") | ||
| public String testIllegalArgument() { | ||
| throw new IllegalArgumentException("잘못된 인자입니다!"); | ||
| } | ||
|
|
||
| @GetMapping("/log4u") | ||
| public String testLog4uException() { | ||
| throw new NotFoundCommentException(); // 또는 임의의 ServiceException | ||
| } | ||
|
|
||
| } |
19 changes: 19 additions & 0 deletions
19
src/main/java/com/example/log4u/domain/comment/testDto/TestRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.example.log4u.domain.comment.testDto; | ||
|
|
||
| // dto/TestRequest.java | ||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| public class TestRequest { | ||
|
|
||
| @NotBlank | ||
| private String name; | ||
|
|
||
| @Min(value = 18, message = "나이는 18세 이상이어야 합니다.") | ||
| private int age; | ||
|
|
||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
headers랑 status 파라미터 안 쓰는 걸로 보이는데 맞나요?
맞으면 혹시 이 파라미터는 왜 필요한가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 headers와 status 파라미터는 직접 사용하고 있지 않지만, 이 메서드는 ResponseEntityExceptionHandler가 정의한 오버라이드 대상 메서드입니다. 따라서 시그니처 그대로 맞춰서 오버라이드하기 위해 작성이 필요합니다.
커스텀 응답 객체를 반환하고 있어서 직접 활용하고 있지 않지만,필요 시 응답 헤더를 추가하거나 상태 코드를 조정할 수 있도록 하기 위해 시그니처에 포함되어 있는거로 알고 있습니다.