Skip to content

Commit

Permalink
[BE] feat: 예외에 대한 로그 작성 (#255)
Browse files Browse the repository at this point in the history
* feat: Controller advice 에 로깅 추가

* refactor: 스택 트레이스 로깅 추가

* feat: 리뷰그룹 생성시 길이 검증 로깅 추가

* feat: 답변 길이 검증 로깅 추가

* feat: 리뷰 그룹 코드 검증 검증 로깅 추가

* feat: 리뷰 조회 검증 검증 로깅 추가

* feat: 선택된 키워드 존재하지 않는 검증 로깅 추가

* feat: 중복 선택된 키워드 검증 로깅 추가

* feat: 키워드 조회 검증 로깅 추가

* feat: 선택 키워드 갯수 검증 로깅 추가

* feat: 선택된 질문 중복 검증 로깅 추가

* feat: 질문 조회 검증 로깅 추가

* feat: 중복 질문 검증 로깅 추가

* feat: 스프링 발생 예외 로깅에 메세지 추가

* feat: 인코딩 설정

* style: 개행 수정

Co-authored-by: Donghoon Lee <[email protected]>

* style: 개행 및 공백 수정

* refactor: 불필요한 검증 제거

- 선택된 키워드와 질문이 DB에 있는지를 validator 에서 검증한 후에도, repository.getById 를 할 때 한번 더 검증이 들어간다. 따라서 'DB에 있는지'에 대한 검증을 validator 에서 할 필요는 없다는 판단 하에 해당 로직을 삭제한다.

---------

Co-authored-by: Donghoon Lee <[email protected]>
  • Loading branch information
nayonsoso and donghoony authored Aug 8, 2024
1 parent 2a4ba6a commit e62a91a
Show file tree
Hide file tree
Showing 26 changed files with 128 additions and 105 deletions.
35 changes: 30 additions & 5 deletions backend/src/main/java/reviewme/global/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
Expand All @@ -24,52 +23,59 @@
import reviewme.global.exception.BadRequestException;
import reviewme.global.exception.FieldErrorResponse;
import reviewme.global.exception.NotFoundException;
import reviewme.global.exception.ReviewMeException;
import reviewme.global.exception.UnAuthorizedException;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFoundException(NotFoundException ex) {
logReviewMeException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getErrorMessage());
}

@ExceptionHandler(BadRequestException.class)
public ProblemDetail handleBadRequestException(BadRequestException ex) {
logReviewMeException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getErrorMessage());
}

@ExceptionHandler(UnAuthorizedException.class)
public ProblemDetail handleUnAuthorizedException(UnAuthorizedException ex) {
logReviewMeException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getErrorMessage());
}

@ExceptionHandler(Exception.class)
public ProblemDetail handleException(Exception ex) {
log.error("An error occurred", ex);
logInitialServerError(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러가 발생했습니다.");
}

// Following exceptions are exceptions that occur in Spring
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ProblemDetail handleHttpRequestMethodNotSupportedException(Exception ex) {
logSpringException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 HTTP 메서드입니다.");
}

@ExceptionHandler(HttpMediaTypeException.class)
public ProblemDetail handleHttpMediaTypeException(Exception ex) {
logSpringException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "잘못된 media type 입니다.");
}

@ExceptionHandler({MissingRequestValueException.class, MissingServletRequestPartException.class})
public ProblemDetail handleMissingRequestException(Exception ex) {
logSpringException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "필수 요청 데이터가 누락되었습니다.");
}

@ExceptionHandler({ServletRequestBindingException.class, HttpMessageNotReadableException.class})
public ProblemDetail handleServletRequestBindingException(Exception ex) {
logSpringException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "요청을 읽을 수 없습니다.");
}

Expand All @@ -78,16 +84,19 @@ public ProblemDetail handleServletRequestBindingException(Exception ex) {
TypeMismatchException.class, HandlerMethodValidationException.class
})
public ProblemDetail handleRequestFormatException(Exception ex) {
logSpringException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "요청의 형식이 잘못되었습니다.");
}

@ExceptionHandler({NoHandlerFoundException.class, NoResourceFoundException.class})
public ProblemDetail handleNoHandlerFoundException(Exception ex) {
logSpringException(ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "잘못된 경로의 요청입니다.");
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
logSpringException(ex);
List<FieldErrorResponse> fieldErrors = ex.getBindingResult()
.getFieldErrors()
.stream()
Expand All @@ -104,4 +113,20 @@ public ProblemDetail handleMethodArgumentNotValid(MethodArgumentNotValidExceptio
problemDetail.setProperties(properties);
return problemDetail;
}

private void logReviewMeException(ReviewMeException ex) {
log.info("{} is occurred - {}",
ex.getClass().getSuperclass().getSimpleName(),
ex.getClass().getSimpleName(),
ex
);
}

private void logInitialServerError(Exception ex) {
log.error("Initial server error is occurred", ex);
}

private void logSpringException(Exception ex) {
log.info("Spring error is occurred - {}: {}", ex.getClass().getSimpleName(), ex.getLocalizedMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package reviewme.keyword.domain.exception;

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class DuplicateKeywordException extends BadRequestException {

public DuplicateKeywordException() {
public DuplicateKeywordException(List<Long> selectedKeywordIds) {
super("키워드는 중복되지 않게 선택해 주세요.");
log.info("Selected keywords are duplicated - selectedKeywordIds: {}", selectedKeywordIds);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package reviewme.keyword.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class KeywordLimitExceedException extends BadRequestException {

public KeywordLimitExceedException(int minSize, int maxSize) {
super("키워드는 최소 %d개, 최대 %d개 선택할 수 있어요.".formatted(minSize, maxSize));
log.info("Selected keywords are out of bound - minSize:{}, maxSize: {}", minSize, maxSize);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package reviewme.keyword.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.NotFoundException;

@Slf4j
public class KeywordNotFoundException extends NotFoundException {

public KeywordNotFoundException() {
public KeywordNotFoundException(long id) {
super("키워드가 존재하지 않아요.");
log.info("Keyword not found by id - id: {}", id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
public interface KeywordRepository extends JpaRepository<Keyword, Long> {

default Keyword getKeywordById(long id) {
return findById(id).orElseThrow(KeywordNotFoundException::new);
return findById(id).orElseThrow(() -> new KeywordNotFoundException(id));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package reviewme.question.domain.exception;

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class DuplicateQuestionException extends BadRequestException {

public DuplicateQuestionException() {
public DuplicateQuestionException(List<Long> selectedQuestionIds) {
super("질문은 중복될 수 없어요.");
log.info("Selected questions are duplicated - selectedQuestionIds: {}", selectedQuestionIds);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package reviewme.question.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.NotFoundException;

@Slf4j
public class QuestionNotFoundException extends NotFoundException {

public QuestionNotFoundException() {
public QuestionNotFoundException(long questionId) {
super("질문이 존재하지 않아요.");
log.info("Question not found - questionId: {}", questionId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public ReviewContent(long questionId, String answer) {

private void validateAnswerLength(String answer) {
if (answer.length() < MIN_ANSWER_LENGTH || answer.length() > MAX_ANSWER_LENGTH) {
throw new InvalidAnswerLengthException(MIN_ANSWER_LENGTH, MAX_ANSWER_LENGTH);
throw new InvalidAnswerLengthException(answer.length(), MIN_ANSWER_LENGTH, MAX_ANSWER_LENGTH);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class InvalidAnswerLengthException extends BadRequestException {

public InvalidAnswerLengthException(int minLength, int maxLength) {
public InvalidAnswerLengthException(int answerLength, int minLength, int maxLength) {
super("답변의 길이는 %d자 이상 %d자 이하여야 합니다.".formatted(minLength, maxLength));
log.info("AnswerLength is out of bound - answerLength:{}, minLength: {}, maxLength: {}",
answerLength, minLength, maxLength);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class InvalidProjectNameLengthException extends BadRequestException {

public InvalidProjectNameLengthException(int maxLength) {
super("프로젝트 이름은 1글자 이상 %d글자 이하여야 해요.".formatted(maxLength));
public InvalidProjectNameLengthException(int projectNameLength, int minLength, int maxLength) {
super("프로젝트 이름은 %d글자 이상 %d글자 이하여야 해요.".formatted(minLength, maxLength));
log.info("ProjectName is out of bound - projectNameLength:{}, minLength:{}, maxLength: {}",
projectNameLength, minLength, maxLength);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class InvalidRevieweeNameLengthException extends BadRequestException {

public InvalidRevieweeNameLengthException(int maxLength) {
super("리뷰이 이름은 1글자 이상 %d글자 이하여야 해요.".formatted(maxLength));
public InvalidRevieweeNameLengthException(int revieweeNameLength, int minLength, int maxLength) {
super("리뷰이 이름은 %d글자 이상 %d글자 이하여야 해요.".formatted(minLength, maxLength));
log.info("RevieweeName is out of bound - revieweeNameLength:{}, minLength:{}, maxLength: {}",
revieweeNameLength, minLength, maxLength);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.NotFoundException;

@Slf4j
public class ReviewGroupNotFoundByGroupAccessCodeException extends NotFoundException {

public ReviewGroupNotFoundByGroupAccessCodeException(String groupAccessCode) {
super("리뷰 그룹을 찾을 수 없어요.");
log.info("ReviewGroup not found by groupAccessCode - groupAccessCode: {}", groupAccessCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.NotFoundException;

@Slf4j
public class ReviewGroupNotFoundByRequestReviewCodeException extends NotFoundException {

public ReviewGroupNotFoundByRequestReviewCodeException(String requestReviewCode) {
super("리뷰 요청 코드에 대한 리뷰 그룹을 찾을 수 없어요.");
log.info("ReviewGroup not found by requestReviewCode - requestReviewCode: {}", requestReviewCode);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;

@Slf4j
public class ReviewIsNotInReviewGroupException extends BadRequestException {

public ReviewIsNotInReviewGroupException() {
public ReviewIsNotInReviewGroupException(long reviewId, long reviewGroupId) {
super("리뷰 그룹에 해당하는 리뷰가 아니에요.");
log.info("Review is not in review group - reviewId: {}, reviewGroupId: {}", reviewId, reviewGroupId);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package reviewme.review.domain.exception;

import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.NotFoundException;

@Slf4j
public class ReviewNotFoundException extends NotFoundException {

public ReviewNotFoundException() {
public ReviewNotFoundException(long id) {
super("리뷰가 존재하지 않아요.");
log.info("ReviewNotFoundException is occurred - id: {}", id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
public interface QuestionRepository extends JpaRepository<Question, Long> {

default Question getQuestionById(long id) {
return findById(id).orElseThrow(QuestionNotFoundException::new);
return findById(id).orElseThrow(() -> new QuestionNotFoundException(id));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public interface ReviewRepository extends JpaRepository<Review, Long> {
Optional<Review> findByIdAndReviewGroupId(long reviewId, long reviewGroupId);

default Review getReviewById(Long id) {
return findById(id).orElseThrow(ReviewNotFoundException::new);
return findById(id).orElseThrow(() -> new ReviewNotFoundException(id));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import org.springframework.stereotype.Component;
import reviewme.keyword.domain.exception.DuplicateKeywordException;
import reviewme.keyword.domain.exception.KeywordLimitExceedException;
import reviewme.keyword.domain.exception.KeywordNotFoundException;
import reviewme.keyword.repository.KeywordRepository;

@Component
@RequiredArgsConstructor
Expand All @@ -15,12 +13,9 @@ public class ReviewCreationKeywordValidator {
private static final int MIN_KEYWORD_COUNT = 1;
private static final int MAX_KEYWORD_COUNT = 5;

private final KeywordRepository keywordRepository;

void validate(List<Long> selectedKeywordIds) {
validateKeywordCount(selectedKeywordIds.size());
validateUniqueKeyword(selectedKeywordIds);
validateExistsKeyword(selectedKeywordIds);
}

private void validateUniqueKeyword(List<Long> selectedKeywordIds) {
Expand All @@ -29,15 +24,7 @@ private void validateUniqueKeyword(List<Long> selectedKeywordIds) {
.distinct()
.count();
if (keywordsCount != distinctCount) {
throw new DuplicateKeywordException();
}
}

private void validateExistsKeyword(List<Long> selectedKeywordIds) {
boolean doesKeywordExist = selectedKeywordIds.stream()
.anyMatch(keywordRepository::existsById);
if (!doesKeywordExist) {
throw new KeywordNotFoundException();
throw new DuplicateKeywordException(selectedKeywordIds);
}
}

Expand Down
Loading

0 comments on commit e62a91a

Please sign in to comment.