Skip to content

feat: 예외 로그 누락 보완 및 Slack 에러 알림 개선 - #483

Merged
pooreumjung merged 6 commits into
developfrom
feat/#479-improve-exception-logging
Sep 24, 2026
Merged

pooreumjung merged 6 commits into
developfrom
feat/#479-improve-exception-logging

Conversation

@pooreumjung

Copy link
Copy Markdown
Member

#️⃣ Issue Number

📝 요약(Summary)

  • 컨트롤러 밖 예외 로그 기록 + MDC에 요청 URI 추가
    • 문제: @Valid 실패, JSON 파싱 실패, 응답 직렬화 실패는 ControllerLogAspect 밖에서 발생해 아무 로그도 남지 않았습니다.
    • prod/dev에 spring.mvc.log-resolved-exception: true를 추가했습니다. @ExceptionHandler가 처리한 예외가 스택트레이스 없이 Resolved [...] WARN 한 줄로 남습니다.
    • 이 예외들은 REQUEST 로그가 없어서 어느 API인지 알 수 없었습니다. 그래서 MDCLoggingFilter에서 MDC에 uri(예: POST /v2/reviews)를 넣도록 했습니다. JSON 파일 로그의 모든 줄에 uri 필드가 붙습니다.
  • Slack 전송 비동기화 + 비동기 스레드로 MDC 전파
    • 문제: Slack 전송이 요청 스레드를 약 1.7초 붙잡았습니다. 에러 알림뿐 아니라 문의 작성과 리뷰 신고 API에서도 매번 Slack 응답을 기다렸습니다.
    • SlackService.sendSlackMessage에 @Async를 붙였습니다. 메시지는 요청 스레드에서 먼저 만들어지므로 Grafana 링크의 requestId는 그대로 유지됩니다.
    • AsyncConfig에 MDC를 복사하는 TaskDecorator 빈을 추가했습니다. @Async로 도는 LogEventListener의 비즈니스 로그와 Slack 전송 로그에도 requestId와 uri가 붙습니다.
  • Slack의 Grafana 로그 링크 수정
  • X-Request-ID 응답 헤더 반환 + 형식 검증
    • 모든 응답(JWT 필터의 401 포함)에 X-Request-ID를 넣습니다. 앱에서 에러를 문의할 때 이 값으로 서버 로그를 바로 찾을 수 있습니다.
    • 요청 헤더 이름을 비표준 X-RequestID에서 X-Request-ID로 바꿨습니다.
    • 클라이언트가 보낸 값은 영문, 숫자, 하이픈으로 된 1~64자만 받고, 조건에 맞지 않으면 새로 만듭니다.
  • 콘솔 로그 시각을 KST로 표기
    • prod/dev JVM이 UTC라서 콘솔 로그(CloudWatch)가 UTC로 찍혔습니다. 패턴에 Asia/Seoul을 지정했습니다. JVM 시간대는 바꾸지 않았습니다.
  • GlobalExceptionHandler 미사용 코드 제거
    • 주입만 받고 쓰지 않던 SlackErrorNotifier, serverEnv, @Slf4j를 제거했습니다. 생성자가 바뀌어서 이를 사용하던 테스트 9개 파일도 함께 수정했습니다.

💬 공유사항 to 리뷰어

  • 요청 헤더 이름이 바뀝니다 (X-RequestID → X-Request-ID).
    • GitHub에서 iOS, Android, Admin-Web 저장소를 검색했고, 이 헤더를 보내는 코드는 없었습니다(기본 브랜치 기준).
    • 혹시 보내는 클라이언트가 있더라도 에러는 나지 않습니다. 서버가 새 ID를 만들 뿐입니다.
    • 앱 팀에는 "에러 문의 시 응답 헤더 X-Request-ID 값을 같이 전달해 달라"고 공유할 예정입니다.
  • Slack 전송이 비동기로 바뀌었습니다. 문의나 신고의 Slack 전송이 실패해도 사용자는 성공 응답을 받습니다. 기존에도 실패하면 로그만 남기고 성공을 반환했기 때문에 동작은 같습니다.
  • 비동기 실행기는 Spring Boot 기본값을 씁니다. 스레드 8개에 대기열은 무제한입니다. 에러가 폭주하면 Slack 전송 작업이 메모리에 쌓일 수 있습니다. 필요해지면 spring.task.execution.pool.queue-capacity로 상한을 두면 됩니다.
  • 로그가 중복으로 찍힙니다. 컨트롤러 안에서 난 예외는 Aspect의 EXCEPTION ERROR 로그와 Resolved WARN이 둘 다 남습니다. 같은 requestId로 묶이는 한 줄짜리라서 감수하기로 했습니다.
  • 테스트:
    • 전체 636개가 통과했습니다.
    • 추가한 테스트는 MDCLoggingFilterTest(uri, 응답 헤더, 형식 검증), AsyncConfigTest(MDC 전파와 정리), SlackMessageFormatTest(링크 쿼리)입니다.
    • 전체 애플리케이션 컨텍스트에서 @Async 실행기가 Boot 기본값(applicationTaskExecutor) 하나뿐이고, 여기에 decorator가 적용된다는 것도 임시 테스트로 확인했습니다.
    • log-resolved-exception은 prod/dev 프로필에만 켜져 있어서 테스트로 검증되지 않습니다.
  • dev 배포 후 확인할 것:
    1. 잘못된 요청(빈 body 등)을 보낸 뒤 {service_name="eatssu-dev"} | json | level="WARN"으로 조회해서 Resolved 로그에 uri가 붙는지 확인합니다.
    2. 아무 API나 호출해서 응답 헤더에 X-Request-ID가 있는지 확인합니다.
    3. 에러가 날 때 응답이 즉시 오는지 확인합니다(Slack 전송을 기다리지 않음).
  • 범위 밖 발견 사항: JVM이 UTC라서 LocalDateTime.now()로 DB에 저장되는 신고 시각 등이 UTC일 수 있습니다. 로그와는 별개라 이번 PR에서는 다루지 않았습니다.

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

🤖 Generated with Claude Code

@pooreumjung pooreumjung added the feat 개발, 구현 label Sep 24, 2026
@pooreumjung pooreumjung self-assigned this Sep 24, 2026
@github-actions

Copy link
Copy Markdown

📊 테스트 커버리지 리포트

Overall Project 95.62% 🍏
Files changed 100% 🍏

File Coverage
SlackService.java 100% 🍏
MDCLoggingFilter.java 100% 🍏
GlobalExceptionHandler.java 100% 🍏
AsyncConfig.java 100% 🍏
SlackMessageFormat.java 99.19% 🍏

@pooreumjung
pooreumjung merged commit 8e8e865 into develop Sep 24, 2026
1 check passed
@pooreumjung
pooreumjung deleted the feat/#479-improve-exception-logging branch September 24, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 개발, 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: 예외 로그 누락 보완 및 Slack 에러 알림 개선

1 participant