Logback MDC 중첩 폴백, 안 되는 문법을 쓰고 있었다
Logback MDC 중첩 폴백, 안 되는 문법을 쓰고 있었다
→ Logback은 자바 애플리케이션에서 로그를 출력하는 라이브러리이고, SLF4J(Simple Logging Facade for Java)라는 로깅 표준 인터페이스의 구현체다. SLF4J가 로깅의 "규격"을 정의하고, Logback이 실제로 로그를 찍는 "엔진" 역할을 한다.
2026-02-27
발단
로그에 요청 ID를 찍고 싶었다. HTTP 요청이면 requestId, Kafka 메시지면 messageId를 쓴다.
Logback 패턴을 이렇게 설정했다:
<pattern>%d{HH:mm:ss} [%X{requestId:-%X{messageId}}] %msg%n</pattern>
의도는 이거였다:
requestId가 있으면 → requestId 출력- 없으면 →
messageId출력 (폴백)
그런데 Kafka 메시지를 처리할 때 로그에 이렇게 찍혔다:
14:30:15 [%X{messageId}] FCM 발송 성공
messageId 값이 아니라 리터럴 문자열 %X{messageId} 가 그대로 나온 것이다.
MDC가 뭔데?
MDC(Mapped Diagnostic Context)는 스레드별로 key-value를 저장해두고, 로그에 자동으로 출력할 수 있는 장치다. 예를 들어 요청 ID를 MDC에 넣어두면, 해당 요청을 처리하는 동안 찍히는 모든 로그에 자동으로 요청 ID가 포함된다. 수많은 로그 중에서 특정 요청의 흐름만 추적할 때 매우 유용하다.
// HTTP 필터에서
MDC.put("requestId", UUID.randomUUID().toString());
// Kafka 리스너에서
MDC.put("messageId", record.key());
Logback 패턴에서 %X{key}로 꺼내 쓴다:
%X{requestId} → "abc-123" (값이 있으면)
%X{requestId} → "" (값이 없으면 빈 문자열)
폴백 문법의 함정
Logback의 MDC에는 기본값(default, 값이 없을 때 대신 사용하는 값) 문법이 있다:
%X{key:-defaultValue}
key가 없으면 defaultValue를 출력한다. 여기서 defaultValue는 리터럴 문자열이다.
%X{requestId:-unknown} → requestId가 없으면 "unknown" 출력 ✅
%X{requestId:-%X{messageId}} → requestId가 없으면 "%X{messageId}" 출력 ❌
중첩 %X{}는 지원하지 않는다. 그냥 문자열로 취급해버린다.
해결
HTTP 요청은 requestId만, Kafka 메시지는 messageId만 설정한다. 둘은 동시에 존재하지 않는다.
그러면 그냥 나란히 쓰면 된다.
<pattern>%d{HH:mm:ss} [%X{requestId}%X{messageId}] %msg%n</pattern>
HTTP 요청: 14:30:15 [req-abc-123] 알림 목록 조회
Kafka: 14:30:16 [msg-456] FCM 발송 성공
requestId가 있으면 messageId는 비어있고, 반대도 마찬가지니까 둘을 나란히 놓으면 항상 하나만 출력된다.
배운 것
- Logback
%X{key:-default}의 default는 리터럴만 가능하다. 중첩%X{}는 안 된다. - 두 MDC 키가 상호 배타적(한쪽이 있으면 다른 쪽은 반드시 없는 관계)이면 그냥 나란히 쓰는 게 가장 심플하다.
- 로그 패턴은 반드시 실제 출력을 눈으로 확인하자. 문법 오류여도 에러 없이 리터럴로 출력된다.