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
좋은 로그의 기준은 “많이 남기는 것”이 아니라
원인을 바로 찾을 수 있게 남기는 것입니다.