Spring Boot Observability 2026 완벽 가이드: OpenTelemetry, 분산 추적, 면접 질문
Spring Boot 3.4의 Observation API, OpenTelemetry 연동, 분산 추적 구현 방법을 상세히 설명합니다. Micrometer Tracing과 OTLP 내보내기 설정, 실무 면접 질문도 다룹니다.

Spring Boot 관측 가능성(Observability)은 로깅, 메트릭, 분산 추적을 통합하여 마이크로서비스 전체에서 요청이 어떻게 흐르는지 시각화하는 시스템입니다. Spring Boot 3부터 프레임워크는 Micrometer Tracing(Spring Cloud Sleuth 후속)을 채택하고 Observation API를 도입했습니다. 이 API는 단일 계측 지점을 제공하며 메트릭과 트레이스를 모두 생성합니다.
Spring Boot에서는 OpenTelemetry를 직접 호출하는 대신 Observation.observe() 사용을 권장합니다. 한 번의 계측 호출로 Micrometer를 통한 메트릭과 OpenTelemetry 브리지를 통한 트레이스가 생성되어 코드 중복을 줄이고 시그널 간 태그 명명의 일관성을 보장합니다.
Observation API가 Micrometer와 OpenTelemetry를 연결하는 방식
Observation API는 메트릭과 추적 모두에 대한 파사드로 작동합니다. 코드가 Observation.createNotStarted()를 호출하면 Spring Boot는 관측을 등록된 핸들러로 라우팅합니다. 구체적으로 Micrometer 메트릭용 MeterObservationHandler와 분산 트레이스용 TracingObservationHandler입니다. 이 아키텍처를 통해 한 번 계측하면 모든 곳으로 내보낼 수 있습니다.
브리지 의존성 micrometer-tracing-bridge-otel은 Micrometer Tracing을 OpenTelemetry SDK에 연결합니다. 트레이스는 OpenTelemetry의 SdkTracerProvider를 통해 흐르고 OTLP를 통해 Jaeger, Tempo 또는 OpenTelemetry 호환 수집기로 내보내집니다.
@Configuration
public class ObservabilityConfig {
@Bean
public ObservationRegistryCustomizer<ObservationRegistry> addLowCardinalityTags() {
return registry -> registry.observationConfig()
.observationHandler(new ObservationTextPublisher()); // Logs observations to console
}
}위 설정은 모든 관측을 로그에 기록하는 ObservationHandler를 등록합니다. 프로덕션 환경에서는 자동 구성된 핸들러가 추가 코드 없이 Micrometer 레지스트리와 OpenTelemetry 내보내기로 데이터를 전송합니다.
Spring Boot 3.4 추적에 필요한 의존성
Spring Boot 3.4에서는 분산 추적에 명시적인 의존성이 필요합니다. spring-boot-starter-actuator는 Observation API를 제공하지만 추적에는 OpenTelemetry 브리지와 내보내기가 필요합니다.
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
</dependencies>micrometer-tracing-bridge-otel 아티팩트는 Micrometer Tracing을 OpenTelemetry API에 연결합니다. opentelemetry-exporter-otlp는 HTTP 또는 gRPC를 통해 OTLP 프로토콜을 사용하여 수집기에 스팬을 전송합니다. Spring Boot는 의존성 관리를 통해 버전 정렬을 관리합니다.
Jaeger 또는 Tempo로 OTLP 내보내기 구성
OTLP 의존성이 있으면 Spring Boot는 OtlpHttpSpanExporter를 자동 구성합니다. 내보내기는 application.yml에 지정된 엔드포인트로 트레이스를 전송합니다.
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 100% sampling for dev, reduce in production
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
opentelemetry:
resource-attributes:
service.name: order-service
deployment.environment: stagingresource-attributes는 모든 스팬에 메타데이터를 첨부합니다. Grafana Tempo와 Jaeger 같은 백엔드는 이러한 속성을 사용하여 서비스와 환경별로 트레이스를 그룹화합니다. 개발 중에는 sampling.probability를 1.0으로 설정하여 모든 요청을 캡처하지만, 프로덕션 시스템에서는 일반적으로 스토리지 비용을 제어하기 위해 1%에서 10% 사이에서 샘플링합니다.
낮은 카디널리티와 높은 카디널리티 태그를 사용한 사용자 정의 관측 생성
관측은 두 가지 유형의 태그를 지원합니다. 메트릭용 낮은 카디널리티(HTTP 메서드나 상태 코드 같은 제한된 값)와 트레이스용 높은 카디널리티(사용자 ID나 요청 ID 같은 무제한 값)입니다.
@Service
public class PaymentService {
private final ObservationRegistry observationRegistry;
public PaymentService(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
public PaymentResult processPayment(PaymentRequest request) {
return Observation.createNotStarted("payment.process", observationRegistry)
.lowCardinalityKeyValue("payment.method", request.getMethod().name()) // enum: CARD, BANK_TRANSFER
.lowCardinalityKeyValue("currency", request.getCurrency()) // bounded: USD, EUR, GBP
.highCardinalityKeyValue("payment.id", request.getPaymentId()) // unique per request
.highCardinalityKeyValue("customer.id", request.getCustomerId()) // high cardinality
.observe(() -> executePayment(request));
}
private PaymentResult executePayment(PaymentRequest request) {
// Payment gateway call
return new PaymentResult(true, "TXN-" + UUID.randomUUID());
}
}낮은 카디널리티 태그는 메트릭과 트레이스 모두에 나타납니다. 높은 카디널리티 태그는 트레이스에만 나타납니다. 무제한 차원을 가진 메트릭은 스토리지를 폭발적으로 증가시키기 때문입니다. 이 구분은 트레이스 세부 정보를 풍부하게 유지하면서 Prometheus 카디널리티 폭발을 방지합니다.
면접관은 특정 태그를 메트릭에서 제외해야 하는 이유를 자주 묻습니다. 답은 카디널리티와 관련이 있습니다. 메트릭의 사용자 ID 태그는 사용자당 하나의 시계열을 생성하여 잠재적으로 수백만 개의 시계열이 될 수 있습니다. 모니터링 백엔드는 높은 카디널리티로 인해 메모리 고갈과 쿼리 지연이 발생합니다. 트레이스는 샘플링을 통해 높은 카디널리티를 처리하므로 요청별 식별자를 배치할 올바른 위치입니다.
Spring Boot 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
비동기 경계를 넘어 트레이스 컨텍스트 전파
분산 추적에는 컨텍스트 전파가 필요합니다. HTTP 헤더는 서비스 간에 트레이스 ID를 전달하지만 @Async 메서드나 CompletableFuture 체인 같은 비동기 작업은 구성되지 않으면 컨텍스트를 잃을 수 있습니다.
Spring Boot 3.4는 구성 시 리액티브 스트림과 @Async 메서드에 대한 자동 전파를 제공합니다:
# application.yml
spring:
reactor:
context-propagation: auto
task:
execution:
propagate-context: true사용자 정의 TaskExecutor 빈의 경우 ContextPropagatingTaskDecorator로 래핑합니다:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
executor.initialize();
return executor;
}
}데코레이터는 현재 Observation과 트레이스 컨텍스트를 비동기 스레드에 복사합니다. 이것이 없으면 비동기 태스크에서 시작된 스팬에 부모가 없어 트레이스 트리가 깨집니다.
@Observed와 @NewSpan 어노테이션 사용
Spring Boot 3.4는 어노테이션을 통한 선언적 관측 가능성을 지원합니다. AspectJ 위버를 추가하여 어노테이션 처리를 활성화합니다:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency># application.yml
management:
observations:
annotations:
enabled: true@Service
public class InventoryService {
@Observed(name = "inventory.check", contextualName = "checkStock")
public StockLevel checkStock(String productId) {
// Database query
return new StockLevel(productId, 42);
}
@NewSpan("inventory.reserve") // Creates a child span under the current trace
public ReservationResult reserveStock(String productId, int quantity) {
// Update inventory
return new ReservationResult(true, "RES-" + System.currentTimeMillis());
}
}@Observed는 메트릭(타이머)과 스팬 모두를 생성합니다. @NewSpan은 스팬만 생성하며 작업이 다른 곳에서 메트릭을 가지고 있을 때 유용합니다. 두 어노테이션 모두 예외 기록과 스팬 상태를 자동으로 처리합니다.
Spring Boot 3.4의 자동 계측 컴포넌트
Spring Boot 3.4는 코드 변경 없이 여러 컴포넌트를 자동 계측합니다:
| 컴포넌트 | 관측 이름 | 캡처 내용 |
|---|---|---|
| Spring MVC | http.server.requests | 요청 경로, 메서드, 상태, 예외 |
| WebClient | http.client.requests | 아웃바운드 HTTP 호출 |
| RestClient | http.client.requests | 아웃바운드 HTTP 호출(Spring 6.1+) |
| Spring Kafka | spring.kafka.listener | 컨슈머 그룹, 토픽, 파티션 |
| Spring Data JPA | spring.data.repository | 리포지토리 메서드, 쿼리 시간 |
| Scheduled Tasks | spring.scheduling | 태스크 이름, 실행 시간 |
Spring Boot Actuator 문서에 모든 자동 계측 컴포넌트가 나열되어 있습니다. Datasource Micrometer 같은 서드파티 라이브러리는 JDBC 쿼리 추적을 추가합니다.
노이즈를 줄이기 위한 관측 필터링
헬스 체크, 레디니스 프로브, 정적 리소스는 트레이스에 노이즈를 생성합니다. 조건자를 사용하여 필터링합니다:
@Configuration
public class ObservationFilterConfig {
@Bean
public ObservationPredicate noHealthChecks() {
return (name, context) -> !name.equals("http.server.requests")
|| !isHealthEndpoint(context);
}
private boolean isHealthEndpoint(Observation.Context context) {
if (context instanceof ServerRequestObservationContext http) {
String path = http.getCarrier().getRequestURI();
return path.startsWith("/actuator/health") || path.startsWith("/actuator/ready");
}
return false;
}
}또는 구성에서 이름으로 관측을 비활성화합니다:
# application.yml
management:
observations:
enable:
spring.security: false # Disable Spring Security observations
http.server.requests.actuator: false # Custom predicate name샘플링은 모든 엔드포인트에서 수집되는 트레이스의 비율을 줄입니다. 필터링은 특정 엔드포인트를 완전히 제거합니다. 진단 가치가 없는 엔드포인트(헬스 체크, 메트릭 스크래핑)에는 필터링을 사용합니다. 대표 데이터를 유지하면서 비용을 제어하려면 샘플링을 사용합니다.
트레이스 ID와 로그 상관관계
Micrometer Tracing이 활성화되면 Spring Boot 3.4는 트레이스 ID와 스팬 ID를 MDC(Mapped Diagnostic Context)에 자동으로 추가합니다. Logback과 Log4j2는 로그 출력에 이러한 ID를 포함할 수 있습니다:
<!-- logback-spring.xml -->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} spanId=%X{spanId} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>이 패턴을 사용하면 모든 로그 라인에 트레이스 ID가 포함됩니다. 트레이스 ID로 로그를 검색하면 모든 서비스에 걸쳐 단일 요청의 모든 메시지가 반환되어 Jaeger나 Tempo의 스팬과 로그가 상관됩니다.
Spring Boot 관측 가능성에 대한 일반적인 면접 질문
면접관은 관측 가능성에 대한 개념적 이해와 실무 경험 모두를 평가합니다. 이러한 질문은 시니어 백엔드 역할에서 자주 등장합니다.
Q: Micrometer Tracing과 OpenTelemetry의 차이점은 무엇입니까?
Micrometer Tracing은 분산 추적을 위한 벤더 중립적 API로, Micrometer가 메트릭을 추상화하는 것과 유사합니다. OpenTelemetry는 특정 구현과 프로토콜입니다. Spring Boot는 API로 Micrometer Tracing을 사용하고 micrometer-tracing-bridge-otel을 통해 내보내기용으로 OpenTelemetry에 브리지합니다. 이 계층화를 통해 애플리케이션은 코드 변경 없이 백엔드를 전환할 수 있습니다.
Q: Spring이 직접 OpenTelemetry 계측보다 Observation API를 권장하는 이유는 무엇입니까?
Observation API는 메트릭과 트레이스 모두를 생성하는 단일 계측 지점을 제공합니다. 직접 OpenTelemetry 호출은 트레이스만 생성합니다. Observation.observe()를 사용하면 동일한 코드에서 타이머 메트릭과 스팬이 생성되어 중복을 줄이고 일관된 명명을 보장합니다.
Q: 스팬 사이에 간격이 있는 트레이스를 어떻게 디버깅합니까?
간격은 계측 누락 또는 컨텍스트 전파 중단을 나타냅니다. 코드가 ContextPropagatingTaskDecorator 없이 비동기 작업을 사용하는지 확인합니다. HTTP 클라이언트(WebClient, RestClient 또는 수동으로 래핑된 RestTemplate)가 계측되었는지 확인합니다. 메시지 큐의 경우 트레이스 헤더가 메시지 속성을 통해 전파되는지 확인합니다.
Q: 높은 카디널리티 태그를 메트릭에 추가하면 어떻게 됩니까?
각 고유 태그 값은 새 시계열을 생성합니다. 수백만 사용자가 있는 사용자 ID 태그는 수백만 시계열을 생성하여 Prometheus 또는 기타 TSDB 백엔드의 메모리를 고갈시킵니다. 수정 방법은 lowCardinalityKeyValue() 대신 highCardinalityKeyValue()를 사용하여 높은 카디널리티가 예상되는 트레이스에만 태그를 추가하는 것입니다.
Docker Compose를 사용한 분산 추적 파이프라인 구축
로컬 관측 가능성 스택은 프로덕션 배포 전에 계측을 검증하는 데 도움이 됩니다. 이 예제에서는 OpenTelemetry Collector와 Jaeger를 사용합니다:
# docker-compose.yml
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:0.102.0
command: ["--config", "/etc/otel-collector-config.yml"]
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- "4318:4318" # OTLP HTTP
- "4317:4317" # OTLP gRPC
jaeger:
image: jaegertracing/jaeger:2.3
ports:
- "16686:16686" # UI
environment:
- COLLECTOR_OTLP_ENABLED=true
app:
build: .
environment:
- MANAGEMENT_OTLP_TRACING_ENDPOINT=http://otel-collector:4318/v1/traces
depends_on:
- otel-collector# otel-collector-config.yml
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 1s
send_batch_size: 1024
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger]콜렉터는 Spring Boot 애플리케이션에서 스팬을 수신하고 Jaeger로 전달합니다. 이 아키텍처를 통해 애플리케이션 코드를 변경하지 않고 내보내기(Tempo, Zipkin, 클라우드 백엔드)를 추가할 수 있습니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
Spring Boot 관측 가능성의 핵심 요점
- Observation API는 메트릭과 추적을 통합합니다. 한 번의
Observation.observe()호출로 타이머 메트릭과 스팬 모두가 생성되어 이중 계측이 필요 없습니다. - OTLP 내보내기를 활성화하려면
micrometer-tracing-bridge-otel과opentelemetry-exporter-otlp를 추가합니다. Spring Boot 3.4는management.otlp.tracing.endpoint의 엔드포인트로 HTTP 내보내기를 자동 구성합니다. - 메트릭에 나타나는 차원(제한된 값)에는
lowCardinalityKeyValue()를 사용합니다. 트레이스 전용 데이터(사용자 ID, 요청 ID)에는highCardinalityKeyValue()를 사용합니다. spring.task.execution.propagate-context=true로 비동기 코드의 컨텍스트 전파를 활성화하고 사용자 정의 실행기를ContextPropagatingTaskDecorator로 래핑합니다.ObservationPredicate빈을 사용하여/actuator/health같은 노이즈가 많은 엔드포인트를 필터링하여 트레이스를 비즈니스 작업에 집중시킵니다.- 로그 패턴에는 분산 트레이스와 로그 라인을 상관시키기 위해
%X{traceId}를 포함해야 합니다. - 관측 가능성에 대한 면접 질문은 카디널리티, 컨텍스트 전파 실패, Observation API와 직접 OpenTelemetry 사용의 구분에 초점을 맞춥니다.
Spring Boot 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 9월 19일 업데이트
공유
관련 기사

Spring Boot YAML vs Properties: 설정 파일 비교 및 면접 질문 2026
Spring Boot의 YAML과 Properties 파일 차이점을 상세히 비교합니다. application.yml과 application.properties 선택 기준, 모범 사례, 기술 면접 대비 가이드를 다룹니다.

2026년 Spring Boot 구조화 로깅: Logback과 OpenTelemetry로 운영 JSON 출력
Spring Boot 4.x 구조화 로깅 완벽 가이드입니다. 네이티브 JSON 출력, OpenTelemetry Starter, MDC 추적, 운영 옵저버빌리티를 위한 ELK Stack 연동을 다룹니다.

Spring Kafka: 회복탄력성을 갖춘 컨슈머로 구축하는 이벤트 기반 아키텍처
이벤트 기반 아키텍처를 위한 완전한 Spring Kafka 가이드. 설정, 회복탄력성을 갖춘 컨슈머, 재시도 정책, Dead Letter Queue, 분산 애플리케이션을 위한 운영 패턴.