← 개발일지

Spring 글로벌 예외 처리 로그 제대로 남기는 방법


문제: 로그는 있는데 원인을 못 찾는다

운영 환경에서 에러 로그는 남았는데,
“어디서 터졌는지”를 알 수 없는 경우가 많습니다.

log.error("에러 발생");

log.error(e.getMessage());

이런 로그만 남아 있다면, 실제로는 디버깅이 거의 불가능한 상태입니다.


환경: Spring + Global Exception Handler

Spring에서는 보통 @RestControllerAdvice 기반으로
글로벌 예외 처리를 구성합니다.

문제는 이 지점이 에러 발생 지점이 아니라는 것입니다.


원인: Exception을 잘못 사용하고 있다

Exception 객체는 단순한 메시지가 아니라
에러 발생 시점의 call stack 전체 정보를 포함하고 있습니다.

즉:

  • 어떤 클래스에서
  • 어떤 메소드에서
  • 몇 번째 라인에서

에러가 발생했는지 이미 들어 있습니다.

그런데 이를 출력하지 않으면
→ 원인 정보가 사라집니다.


해결: StackTrace + TraceId 기반 로깅

1. 반드시 이렇게 로그를 남긴다

log.error("stacktrace ↓↓↓", e);

이 한 줄로:

  • 전체 StackTrace
  • root cause
  • 호출 흐름

이 모두 출력됩니다.


2. 원인 위치를 따로 추출

private String getRootStack(Exception e) {  
    StackTraceElement ste = e.getStackTrace()[0];  
    return ste.getClassName() + "." +  
           ste.getMethodName() +  
           "(" + ste.getFileName() + ":" + ste.getLineNumber() + ")";  
}

3. 운영용 로그 구조

log.error("""  
[GLOBAL_EXCEPTION]  
traceId   = {}  
uri       = {}  
method    = {}  
errorType = {}  
errorAt   = {}  
message   = {}  
""",  
traceId,  
request.getRequestURI(),  
request.getMethod(),  
e.getClass().getSimpleName(),  
getRootStack(e),  
e.getMessage(),  
e  
);

4. traceId로 요청 단위 추적

MDC.put("traceId", UUID.randomUUID().toString());

이 값을 로그에 포함하면:

하나의 요청에서 발생한 모든 로그를 연결할 수 있습니다.


실전 효과

이 구조를 적용하면 로그가 이렇게 바뀝니다:

traceId=ab12cd34  
errorAt=OrderService.createOrder(OrderService.java:42)

→ 바로 코드 위치로 이동 가능
→ 디버깅 시간 대폭 감소


핵심 정리

  • log.error(..., e)는 필수
  • Exception은 이미 모든 정보를 가지고 있음
  • ControllerAdvice는 기록 지점일 뿐
  • traceId 없으면 장애 분석 난이도 급상승

Takeaway

좋은 로그의 기준은 “많이 남기는 것”이 아니라
원인을 바로 찾을 수 있게 남기는 것입니다.