Skip to content

feat: STOMP 에러 로그 구조화#145

Open
parkjuyeong0312 wants to merge 12 commits into
devfrom
refactor/logging-add-stackTrace
Open

feat: STOMP 에러 로그 구조화#145
parkjuyeong0312 wants to merge 12 commits into
devfrom
refactor/logging-add-stackTrace

Conversation

@parkjuyeong0312

@parkjuyeong0312 parkjuyeong0312 commented Jun 26, 2026

Copy link
Copy Markdown
Member

문제 상황

기존 STOMP 에러 로그는 Spring 내부 StompSubProtocolHandler가 다음과 같은 형태로만 기록하고 있었습니다.

Failed to send message to MessageChannel in session ...

이 로그만으로는 실제 예외가 인증 실패인지, 토큰 만료인지, 방 접근 권한 문제인지, 서버 내부 오류인지 구분하기 어려웠습니다.
또한 다음과 같은 진단 정보가 부족했습니다.

  • 실제 원인 예외 및 errorCode
  • STOMP command
  • destination
  • WebSocket sessionId
  • userId
  • traceId
  • stack trace

특히 기존 MdcStompInterceptorbeforeHandle 시점에 traceId를 설정하고 있어, 그보다 먼저 실행되는 인증·권한 인터셉터의 preSend 단계에서 예외가 발생하면 traceId가 로그에 포함되지 않는 문제가 있었습니다.

이외에도 운영 로그 설정에서 다음 문제를 확인했습니다.

  • 4xx 성격의 예상 가능한 STOMP 오류도 모두 ERROR로 기록됨
  • Spring 기본 STOMP send-failure ERROR 로그와 커스텀 로그가 중복될 가능성
  • prod 콘솔 로그가 일반 텍스트로 출력되어 Loki에서 JSON 필드 기반 조회가 어려움
  • LogstashEncoder의 @version, level_value, tags 등 현재 운영에서 사용하지 않는 필드가 포함됨
  • 일반 로그 파일과 ERROR 전용 파일에 ERROR 로그가 중복 기록됨

변경 내용

  • STOMP 처리 중 발생하는 예외를 StompProtocolErrorHandler에서 공통 처리하도록 추가했습니다.
    • 4xx 성격의 CustomException은 WARN으로 기록
    • 5xx 및 예상하지 못한 일반 예외는 ERROR와 stack trace를 함께 기록
    • errorCode, stompCommand, destination, sessionId, userId, traceId를 구조화 필드로 기록
  • MdcStompInterceptor의 traceId 설정 시점을 beforeHandle에서 preSend로 앞당겼습니다.
    • 인증·권한 인터셉터에서 예외가 발생하더라도 traceId가 유지되도록 메시지 헤더에 저장
    • 실제 실패 메시지의 컨텍스트를 추출할 수 있도록 STOMP 로그 컨텍스트 헬퍼 추가
  • Spring의 기존 STOMP send-failure 로그가 커스텀 로그와 중복되지 않도록 필터를 추가했습니다.
  • logback-spring.xml 설정을 정리했습니다.
    • prod 콘솔 로그를 JSON 형식으로 통일
    • @version, level_value, tags 등 불필요한 필드 제거
    • ERROR 로그의 파일 중복 기록 방지
  • 관련 설계 스펙, 실행 계획, ADR을 추가하거나 갱신했습니다.

변경 이유

기존 구조에서는 STOMP 예외가 모두 동일한 Spring ERROR 로그로 기록되어 운영 중 장애 원인을 빠르게 구분하기 어려웠습니다.
이번 변경을 통해 사용자 요청으로 발생하는 예상 가능한 오류와 실제 서버 장애를 로그 레벨과 stack trace 포함 여부로 분리하고, STOMP 요청 컨텍스트를 구조화해 운영 로그의 검색성과 추적성을 높이고자 했습니다.
또한 traceId를 메시지 처리 초기 단계부터 유지해 인증·권한 검사 과정에서 발생한 예외도 동일한 요청 흐름으로 추적할 수 있도록 개선했습니다.

테스트

  • ./gradlew build
  • /review-code-against-docs 스킬로 검증
  • 그 외 수동 검증: ./gradlew checkstyleMain checkstyleTest, /checking-md-conflicts
    스킬로 Markdown 참조/중복 규칙/충돌 점검

체크리스트

  • PR 제목이 커밋 컨벤션 형식을 따른다.
  • 변경 사유를 PR 설명에 기록했다.
  • 테스트 방법과 결과를 기록했다.
  • 문서 변경이 필요한 경우 반영했다.

하네스 변경 체크리스트

  • CLAUDE.md(AGENTS.md) 변경이 포함되어 있는가? 포함되지 않음
  • 변경 사유가 PR 설명에 기록되어 있는가?
  • 기존 규칙과 충돌하지 않는가?
  • 팀원에게 변경 사항을 공유했는가? PR로 공유 예정

Summary by CodeRabbit

  • New Features

    • STOMP/WebSocket 오류가 더 구조화된 형태로 기록되어, 문제 원인과 연결 정보 추적이 쉬워졌습니다.
    • 운영 환경의 콘솔 출력이 JSON 형식으로 정리되어 로그 수집 및 분석이 편해졌습니다.
  • Bug Fixes

    • 예외 발생 시에도 추적 ID가 더 안정적으로 유지되어, 관련 로그를 한 흐름으로 확인할 수 있습니다.
    • 중복되거나 불필요한 오류 로그가 줄어들어 로그 노이즈가 감소했습니다.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

STOMP 오류 로깅과 MDC traceId 보존 흐름을 정리하고, /ws 엔드포인트에 프로토콜 에러 핸들러를 직접 연결하도록 WebSocket 설정과 logback 구성을 바꿨다. 로그 컨텍스트 헬퍼, TurboFilter, 관련 테스트와 설계 문서도 함께 추가됐다.

Changes

STOMP 에러 로깅 정리

Layer / File(s) Summary
로그백 출력과 노이즈 필터
docs/ai/decisions/20260626-1443-stomp-error-logging.md, docs/superpowers/plans/2026-06-26-stomp-error-logging.md, docs/superpowers/specs/2026-06-26-stomp-error-logging-design.md, src/main/resources/logback-spring.xml, src/main/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilter.java, src/test/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilterTest.java
CONSOLE_TEXT/CONSOLE_JSON 분리, encoder 필드 정리, prod FILE/ERROR_FILE 라우팅, 그리고 STOMP send-failure TurboFilter와 검증 문서가 추가된다.
MDC traceId 생명주기
docs/superpowers/plans/2026-06-26-stomp-error-logging.md, docs/superpowers/specs/2026-06-26-stomp-error-logging-design.md, src/main/java/com/howaboutus/backend/common/filter/MdcStompInterceptor.java, src/test/java/com/howaboutus/backend/common/filter/MdcStompInterceptorTest.java
MdcStompInterceptorpreSend에서 traceId를 MDC에 넣고 afterSendCompletion에서 제거하며, 관련 테스트가 생성·재사용·정리 동작을 확인한다.
STOMP 로그 컨텍스트 추출
docs/superpowers/plans/2026-06-26-stomp-error-logging.md, docs/superpowers/specs/2026-06-26-stomp-error-logging-design.md, src/main/java/com/howaboutus/backend/realtime/config/StompLogContext.java, src/test/java/com/howaboutus/backend/realtime/config/StompLogContextTest.java
StompLogContextcommand, destination, sessionId, userId, traceId를 메시지와 MDC에서 추출하고, null 및 fallback 경로를 테스트한다.
에러 핸들러와 엔드포인트 연결
docs/superpowers/plans/2026-06-26-stomp-error-logging.md, docs/superpowers/specs/2026-06-26-stomp-error-logging-design.md, src/main/java/com/howaboutus/backend/realtime/config/StompProtocolErrorHandler.java, src/main/java/com/howaboutus/backend/realtime/config/WebSocketConfig.java, src/test/java/com/howaboutus/backend/realtime/config/StompProtocolErrorHandlerTest.java, src/test/java/com/howaboutus/backend/realtime/config/WebSocketConfigTest.java
StompProtocolErrorHandler가 4xx를 warn, 5xx/기타를 error로 기록하고 MessagingException의 failed message를 우선 사용하며, WebSocketConfig가 이를 /ws에 연결한다.

Sequence Diagram(s)

MDC traceId 흐름

sequenceDiagram
  participant MdcStompInterceptor
  participant MessageChannel
  participant MDC
  MdcStompInterceptor->>MDC: put(traceId) in preSend
  MdcStompInterceptor->>MessageChannel: return message with traceId header
  MessageChannel->>MdcStompInterceptor: afterSendCompletion(sent, ex)
  MdcStompInterceptor->>MDC: remove(traceId)
Loading

STOMP 오류 처리 흐름

sequenceDiagram
  participant StompProtocolErrorHandler
  participant StompLogContext
  participant StompSubProtocolErrorHandler
  StompProtocolErrorHandler->>StompLogContext: from(clientMessage)
  StompProtocolErrorHandler->>StompProtocolErrorHandler: log.warn/log.error with context
  StompProtocolErrorHandler->>StompSubProtocolErrorHandler: handleClientMessageProcessingError(...)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

나는 토끼, 깡총깡총 로그 숲을 달려요 🐰
traceId 당근이 MDC 바구니에 쏙
warn 은 살랑, error 는 번쩍
JSON 밤하늘에 반짝반짝
오늘의 STOMP는 더 가지런해요

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 STOMP 에러 로그 구조화라는 핵심 변경을 간결하게 요약합니다.
Description check ✅ Passed 필수 섹션인 변경 내용/이유/테스트/체크리스트/하네스 변경 체크리스트를 모두 포함해 템플릿을 충족합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/test/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilterTest.java (1)

54-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mockito 사용 방식이 코딩 지침과 어긋납니다.

Logger 목을 mock(Logger.class)로 직접 생성하는 대신 @ExtendWith(MockitoExtension.class)@Mock 사용이 권장됩니다. 다만 테스트마다 다른 로거 이름이 필요하므로 @Mock Logger logger; 선언 후 각 테스트에서 given(logger.getName()).willReturn(...)로 스텁하는 형태가 적합합니다.

As per coding guidelines: "In unit tests using Mockito, prefer @ExtendWith(MockitoExtension.class) with @Mock/@InjectMocks over direct Mockito.mock(...) creation".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilterTest.java`
around lines 54 - 58, The test helper currently creates the Logger with direct
Mockito mocking, which violates the Mockito usage guideline. Update
StompSubProtocolHandlerErrorLogFilterTest to use MockitoExtension with a
class-level `@Mock` Logger instead of mock(Logger.class), and keep the per-test
logger name customization by stubbing logger.getName() inside each test or
setup. Preserve the existing logger(String name) helper behavior by adapting it
to work with the injected mock rather than constructing a new one.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/test/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilterTest.java`:
- Around line 54-58: The test helper currently creates the Logger with direct
Mockito mocking, which violates the Mockito usage guideline. Update
StompSubProtocolHandlerErrorLogFilterTest to use MockitoExtension with a
class-level `@Mock` Logger instead of mock(Logger.class), and keep the per-test
logger name customization by stubbing logger.getName() inside each test or
setup. Preserve the existing logger(String name) helper behavior by adapting it
to work with the injected mock rather than constructing a new one.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: abb9d44b-77c4-430d-9792-f5a7af40dbe7

📥 Commits

Reviewing files that changed from the base of the PR and between 20c6c8f and 8bc6701.

📒 Files selected for processing (14)
  • docs/ai/decisions/20260626-1443-stomp-error-logging.md
  • docs/superpowers/plans/2026-06-26-stomp-error-logging.md
  • docs/superpowers/specs/2026-06-26-stomp-error-logging-design.md
  • src/main/java/com/howaboutus/backend/common/filter/MdcStompInterceptor.java
  • src/main/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilter.java
  • src/main/java/com/howaboutus/backend/realtime/config/StompLogContext.java
  • src/main/java/com/howaboutus/backend/realtime/config/StompProtocolErrorHandler.java
  • src/main/java/com/howaboutus/backend/realtime/config/WebSocketConfig.java
  • src/main/resources/logback-spring.xml
  • src/test/java/com/howaboutus/backend/common/filter/MdcStompInterceptorTest.java
  • src/test/java/com/howaboutus/backend/common/logging/StompSubProtocolHandlerErrorLogFilterTest.java
  • src/test/java/com/howaboutus/backend/realtime/config/StompLogContextTest.java
  • src/test/java/com/howaboutus/backend/realtime/config/StompProtocolErrorHandlerTest.java
  • src/test/java/com/howaboutus/backend/realtime/config/WebSocketConfigTest.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant