| name | logback-mdc-tracing |
| description | Spring Boot 애플리케이션의 구조화된 로깅과 분산 추적 - Logback + MDC + Spring Cloud Sleuth(SB 2.5, 레거시) / Micrometer Tracing(SB 3.x, 모던) 통합 |
Logback + MDC + 분산 추적 통합
소스:
검증일: 2026-04-22
주의: 이 문서는 Logback 1.5.x, Spring Boot 3.4+/3.5(Micrometer Tracing 1.3.x~1.5.x), Spring Boot 2.5.x(Sleuth 3.0.x / 3.1.x)를 기준으로 작성되었습니다. Sleuth는 Spring Boot 3.x에서 동작하지 않으며, 레거시 유지보수 프로젝트에만 사용합니다.
언제 이 스킬을 사용하는가
- 운영 중인 Spring Boot 서비스에 구조화된 로그(logback-spring.xml) 설정이 필요할 때
- 분산 환경에서 traceId/spanId 기반 요청 추적이 필요할 때
- Spring Boot 2.x(레거시) 기반 서비스와 Spring Boot 3.x(신규) 서비스가 혼재하는 팀에서 규칙을 맞출 때
@Async, 메시지 큐 등 비동기 경계에서 로그 컨텍스트가 끊기는 이슈를 해결할 때
공통: Logback 기본 설정
파일 위치와 이름
src/main/resources/logback-spring.xml — 권장. Spring Boot 확장(<springProfile>, <springProperty>) 사용 가능.
src/main/resources/logback.xml — 비권장. Logback이 Spring 초기화 전에 로드되어 Spring 확장 기능을 쓸 수 없음.
최소 구성 (logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId:-}/%X{spanId:-}] [%thread] %-5level %logger{36} - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
핵심 포인트:
%X{traceId:-} — MDC에서 traceId 키를 읽고, 없으면 빈 문자열 출력 (-는 기본값 구분자)
scan="true" — 설정 파일 변경 감지 후 자동 리로드 (운영에서는 성능 영향 없는 수준)
defaults.xml include — Spring Boot가 제공하는 기본 변환 규칙(CONSOLE_LOG_PATTERN 등) 활용
Spring Profile별 설정 분리
<springProfile name="local | dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
</springProfile>
<springProfile>은 logback.xml에서는 동작하지 않는다. 반드시 logback-spring.xml을 사용한다.
공통: Appender 구성
ConsoleAppender
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
RollingFileAppender + TimeBasedRollingPolicy
<property name="LOG_PATH" value="${LOG_PATH:-./logs}"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/archived/application-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
옵션 의미:
| 옵션 | 설명 |
|---|
fileNamePattern | 롤링 주기는 패턴 자체에서 유추 (%d{yyyy-MM-dd} → 일별) |
maxHistory | 보관할 최대 아카이브 파일 수(일 단위 롤링이면 일 수) |
totalSizeCap | 아카이브 전체 크기 상한. 초과 시 오래된 파일부터 삭제 |
.gz / .zip | fileNamePattern 확장자로 자동 압축 활성화 |
maxFileSize | 같은 날 안에서 크기로 추가 롤링 |
AsyncAppender
I/O가 핫패스를 블록하지 않도록 운영 환경에서 파일 appender 앞에 둔다.
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>false</neverBlock>
<appender-ref ref="FILE"/>
</appender>
주의: discardingThreshold 기본값은 queueSize / 5 이며 큐가 80% 이상 찬 상태에서 TRACE/DEBUG/INFO를 자동으로 버린다. 전량 보존이 필요하면 0으로 설정한다.
공통: 로그 레벨 제어
application.yml에서 패키지별 레벨 오버라이드.
logging:
level:
root: INFO
com.example.myapp: DEBUG
org.springframework.web: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE
환경변수로도 조정 가능: LOGGING_LEVEL_COM_EXAMPLE_MYAPP=DEBUG.
공통: SLF4J 사용법
Lombok @Slf4j
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class UserService {
public User findById(Long id) {
log.info("finding user: id={}", id);
}
}
파라미터화 로깅 (중요)
log.debug("user found: id={}, name={}", id, name);
log.debug("user found: id=" + id + ", name=" + name);
예외 로깅 — 예외는 마지막 인자
try {
userService.save(user);
} catch (DuplicateKeyException e) {
log.error("failed to save user: key={}", user.getEmail(), e);
}
SLF4J 1.6.0+는 "placeholder 개수보다 인자가 하나 더 많고 그 마지막 인자가 Throwable"이면 스택 트레이스로 취급한다. placeholder를 추가로 쓰면 일반 객체로 다뤄져 스택이 출력되지 않는다.
공통: MDC 직접 사용
MDC(Mapped Diagnostic Context)는 스레드 로컬이다. 반드시 요청 종료 시 clean up.
try-with-resources (권장)
import org.slf4j.MDC;
public void handleRequest(String userId) {
try (MDC.MDCCloseable ignored = MDC.putCloseable("userId", userId)) {
log.info("processing request");
}
}
수동 put/remove (try/finally 필수)
MDC.put("userId", userId);
MDC.put("requestId", requestId);
try {
} finally {
MDC.remove("userId");
MDC.remove("requestId");
}
MDC.put() 후 remove()를 빠뜨리면 스레드 풀 재사용 시 다른 요청의 로그에 이전 값이 남는다. 반드시 try-with-resources 또는 try/finally로 감싼다.
로그 패턴에 MDC 노출
<pattern>%d [%X{userId:-anonymous}] %msg%n</pattern>
레거시: Spring Cloud Sleuth (Spring Boot 2.5.x)
주의: Sleuth는 Spring Boot 3.x에서 동작하지 않는다. Sleuth 3.1.x가 최종 마이너 버전이며 현재 유지보수 모드다. 신규 프로젝트는 Micrometer Tracing(아래 섹션)을 사용한다.
의존성 (Spring Boot 2.5.x + Sleuth 3.0.x)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin
주의: Spring Boot 2.6.0+는 Sleuth 3.0.x와 호환성 이슈(circular dependency)가 있었다. 2.5.x 라인 유지 또는 Sleuth 3.1.x로 업그레이드 필요.
자동 MDC 주입
Sleuth는 의존성을 추가하는 것만으로 traceId, spanId를 MDC에 자동 주입한다. 로그 패턴에 %X{traceId} / %X{spanId}를 넣으면 즉시 출력된다.
기본 설정
spring:
application:
name: my-service
sleuth:
sampler:
probability: 1.0
zipkin:
base-url: http://zipkin:9411
커스텀 Span 생성 — @NewSpan, @SpanTag
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.cloud.sleuth.annotation.SpanTag;
@Service
public class PaymentService {
@NewSpan("process-payment")
public Receipt process(@SpanTag("orderId") Long orderId, BigDecimal amount) {
return ...;
}
}
HTTP 클라이언트 자동 인스트루먼테이션
RestTemplate, WebClient는 Spring 빈으로 주입하면 Sleuth가 자동으로 trace 헤더(B3)를 전파한다. new RestTemplate()으로 직접 생성하면 전파되지 않는다.
모던: Micrometer Tracing (Spring Boot 3.x)
의존성 — Brave(Zipkin) 기반
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
</dependencies>
의존성 — OpenTelemetry 기반
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
두 bridge를 동시에 포함하지 않는다. 하나만 선택. 버전은 Spring Boot BOM이 관리하므로 명시하지 않는다.
기본 설정
spring:
application:
name: my-service
management:
tracing:
enabled: true
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
logging:
pattern:
correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] "
include-application-name: false
MDC 키 호환성
Micrometer Tracing도 MDC 키로 traceId, spanId를 사용한다. Sleuth 시절과 동일한 키이므로 로그 패턴 변경이 필요 없다.
주의: Sleuth는 기본적으로 B3 전파 포맷을, Micrometer Tracing은 W3C Trace Context 포맷을 사용한다. Sleuth와 Micrometer 기반 서비스를 혼합 운용하면 traceId가 이어지지 않을 수 있다. 양측 포맷을 맞추거나 운영 마이그레이션을 일시에 수행한다.
커스텀 Span — Tracer API 직접 사용
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.Span;
@Service
@RequiredArgsConstructor
public class PaymentService {
private final Tracer tracer;
public Receipt process(Long orderId, BigDecimal amount) {
Span span = tracer.nextSpan().name("process-payment").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
span.tag("orderId", String.valueOf(orderId));
return doProcess(orderId, amount);
} catch (Exception e) {
span.error(e);
throw e;
} finally {
span.end();
}
}
}
@NewSpan / @Observed (애노테이션 기반)
Spring Boot 3.0 초기에는 @NewSpan에 해당하는 AOP가 빠져 있었고 Spring Boot 3.1 + Micrometer Tracing 1.1.0부터 지원된다. 활성화하려면:
management:
observations:
annotations:
enabled: true
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
import io.micrometer.observation.annotation.Observed;
@Service
public class PaymentService {
@Observed(name = "payment.process", contextualName = "process-payment")
public Receipt process(Long orderId) { ... }
}
@Observed가 권장 방향이다. @NewSpan/@SpanTag는 Sleuth에서 이식되었으나 Observation 생태계에 더 적합한 @Observed가 신규 API다.
HTTP 클라이언트 자동 인스트루먼테이션
RestTemplateBuilder로 주입받은 RestTemplate
WebClient.Builder로 주입받은 WebClient
- Spring 6.1+의
RestClient.Builder
는 자동으로 trace 헤더를 전파한다. 직접 new로 생성하면 전파되지 않는다.
상세 레퍼런스 (예제·고급 패턴·흔한 실수) → references/REFERENCE.md