[로아투두] 에러 로그 출력 변경

2025. 3. 5. 17:15·Project/LOATODO

1. 서론

기존에 서버에서 발생한 에러를 디스코드 웹훅으로 받고 있었음

 

여기서 불편한점

  1. 따로 에러가 발생하지 않고 메시지만 출력되는 내용을 굳이 디스코드를 받아야할까?
    • ex) "상위 관문을 먼저 제거하여 주십시오."
  2.  누가, 어느 API를 호출해서 발생한 에러인지 모른다.

 

이 문제를 해결하고자 한다.

 

 

2. 개발

관련 커밋

  • fix: 기존 에러 처리 변경
 

fix: 기존 에러 처리 변경 · minhyeok2487/LostarktTodoBackend@b12e28d

- 간단한 에러는 webhook을 보내지 않음

github.com

  • fix: 에러에 헤더 수집
 

fix: 에러에 헤더 수집 · minhyeok2487/LostarktTodoBackend@4046729

minhyeok2487 committed Mar 5, 2025

github.com

 

@RestControllerAdvice 를 이용한 글로벌 예외 핸들러

// 공통 예외 처리 메서드
private ResponseEntity<ErrorResponse> handleExceptionInternal(Exception ex, HttpServletRequest request, boolean sendWebHook) {
    String requestInfo = String.format("%s %s", request.getMethod(), request.getRequestURI());

    if (sendWebHook) {
        // 에러 로그 먼저 호출
        log.error("{} - {}", requestInfo, ex.getMessage());

        // requestInfo에 헤더를 추가하기 위한 StringBuilder
        StringBuilder headerDetails = new StringBuilder();

        // 헤더 수집 및 로그 출력
        Collections.list(request.getHeaderNames())
                .forEach(headerName -> {
                    String headerValue = request.getHeader(headerName);
                    headerDetails.append(String.format("Header [%s] = %s%n", headerName, headerValue));
                });

        // 기존 requestInfo에 헤더 정보 추가
        String updatedRequestInfo = requestInfo + "\n" + headerDetails;

        // callEvent 호출
        webHookService.callEvent(ex, updatedRequestInfo);
    } else {
        log.warn("{} - {}", requestInfo, ex.getMessage());
    }

    ErrorResponse errorResponse = ErrorResponse.of(
            HttpStatus.BAD_REQUEST.value(), ex.getClass().getSimpleName(), ex.getLocalizedMessage()
    );

    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

 

먼저 공통 예외 처리 메서드를 만들었다.

sendWebHook 변수를 활용하여

치명적인 에러인 경우 API 정보와 함께 로그를 출력하고, 헤더의 내용을 더해 웹훅으로 전송한다.

그렇지 않은 경우 로그만 출력하는 형식이다.

 

여기서 치명적인 것과 아닌 것을 구분하는 것은

// 요구 조건이 충족되지 않아서 발생하는 예외처리 (web hook 미전송)
@ExceptionHandler(ConditionNotMetException.class)
public ResponseEntity<ErrorResponse> handlerConditionNotMetException(ConditionNotMetException ex, HttpServletRequest request) {
    return handleExceptionInternal(ex, request, false);
}

// 그 외 오류 (web hook 전송)
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handlerException(Exception ex, HttpServletRequest request) {
    return handleExceptionInternal(ex, request, true);
}

 

ConditionNotMetException이라고 하는 새로 만든 Exception이다.

이 exception으로 들어가는 것들은 치명적이지 않은 에러이다.

 

예시

  • 게시판 - "게시글 작성 후 15분이 지나 수정할 수 없습니다."
  • 캐릭터 - "골드 획득 지정 캐릭터는 서버별로 6캐릭까지 가능합니다."
  • 깐부 - "깐부 권한이 없습니다."

 

[전체 코드]

@RestControllerAdvice
@RequiredArgsConstructor
@Slf4j
public class ExControllerAdvice {

    private final WebHookService webHookService;

    // 요구 조건이 충족되지 않아서 발생하는 예외처리 (web hook 미전송)
    @ExceptionHandler(ConditionNotMetException.class)
    public ResponseEntity<ErrorResponse> handlerConditionNotMetException(ConditionNotMetException ex, HttpServletRequest request) {
        return handleExceptionInternal(ex, request, false);
    }

    // 유효성 검사 예외 처리 (web hook 미전송)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorListResponse> handleValidationException(MethodArgumentNotValidException ex, HttpServletRequest request) {
        String requestInfo = String.format("%s %s", request.getMethod(), request.getRequestURI());

        log.warn("{} - {}", requestInfo, ex.getMessage());

        List<String> messages = ex.getBindingResult().getFieldErrors().stream()
                .map(fieldError -> String.format("%s 입력된 값: [%s]", fieldError.getDefaultMessage(), fieldError.getRejectedValue()))
                .toList();

        ErrorListResponse errorListResponse = ErrorListResponse.of(
                HttpStatus.BAD_REQUEST.value(), ex.getClass().getSimpleName(), messages
        );

        return new ResponseEntity<>(errorListResponse, HttpStatus.BAD_REQUEST);
    }

    // 그 외 오류 (web hook 전송)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handlerException(Exception ex, HttpServletRequest request) {
        return handleExceptionInternal(ex, request, true);
    }

    // 공통 예외 처리 메서드
    private ResponseEntity<ErrorResponse> handleExceptionInternal(Exception ex, HttpServletRequest request, boolean sendWebHook) {
        String requestInfo = String.format("%s %s", request.getMethod(), request.getRequestURI());

        if (sendWebHook) {
            // 에러 로그 먼저 호출
            log.error("{} - {}", requestInfo, ex.getMessage());

            // requestInfo에 헤더를 추가하기 위한 StringBuilder
            StringBuilder headerDetails = new StringBuilder();

            // 헤더 수집 및 로그 출력
            Collections.list(request.getHeaderNames())
                    .forEach(headerName -> {
                        String headerValue = request.getHeader(headerName);
                        headerDetails.append(String.format("Header [%s] = %s%n", headerName, headerValue));
                    });

            // 기존 requestInfo에 헤더 정보 추가
            String updatedRequestInfo = requestInfo + "\n" + headerDetails;

            // callEvent 호출
            webHookService.callEvent(ex, updatedRequestInfo);
        } else {
            log.warn("{} - {}", requestInfo, ex.getMessage());
        }

        ErrorResponse errorResponse = ErrorResponse.of(
                HttpStatus.BAD_REQUEST.value(), ex.getClass().getSimpleName(), ex.getLocalizedMessage()
        );

        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

}

 

3. 결과

 

어떤 API에서 어떠한 문제점이 있었는지 확인가능!

 

아쉬운점이 있나면 RequestBody에 담긴 내용도 함께 전송했으면 하는데...

그렇게 하려면 캐시에 먼저 body값을 저장해야하기 때문에 메모리 부하가 커지게 된다.

현재 t4g.micro를 쓰고 있는 상황에서 메모리에 여유가 있지는 않기 때문에 그부분은 넘어가려고한다.

 

 

저작자표시 (새창열림)
'Project/LOATODO' 카테고리의 다른 글
  • [로아투두] Spring Security JWT 관리자 API 뚫려있었네...?
  • [로아투두] 로그 저장 최적화 작업
  • [로아투두] bucket4j를 이용해 트래픽 요청 제한하기
  • [로아투두] 2025년 기획
마볼링
마볼링
개발과 게임에 관한 내용을 읽기 쉽게 정리합니다.
  • 마볼링
    게임을 좋아하는 개발자의 블로그
    마볼링
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Project
        • LOATODO
        • 인스타그램 클론코딩(중단)
      • Language
        • Java
        • PHP
        • Javascript
      • Framework & Library
        • Spring
        • Vue
      • Computer Science
        • Web
        • Linux
      • CodingTest
        • Algorithm
        • Kotlin으로 푼 코딩 테스트
        • Java로 푼 코딩 테스트
        • Sorting & Thinking
        • BFS
      • 책&강의 정리
      • 정보처리기사
      • 개인
        • 팰월드(PALWORLD)
        • 마인크래프트
  • 블로그 메뉴

    • 링크

      • GitHub
      • Threads
    • 공지사항

    • 인기 글

    • 태그

      이터널 모드
      error
      코딩테스트
      java
      아크 서바이벌
      티스토리챌린지
      jsp
      php
      CS
      로아투두
      springboot
      운영체제
      codingtest
      오블완
      LoaTodo
      Spring
      네트워크
      프로그래머스
      JPA
      Database
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.1
    마볼링
    [로아투두] 에러 로그 출력 변경
    상단으로

    티스토리툴바