본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 22. · 3 Views
Micrometer Tracing 완벽 가이드
분산 시스템에서 요청 흐름을 추적하는 Micrometer Tracing의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 실무 스토리와 비유로 풀어낸 완벽 가이드입니다.
목차
1. Micrometer Tracing 소개
어느 날 김개발 씨는 주문 서비스에서 발생한 장애 원인을 찾고 있었습니다. 주문이 10초나 걸린다는 고객 불만이 접수됐지만, 어느 서비스에서 지연이 발생했는지 도무지 알 수 없었습니다.
선배 박시니어 씨가 다가와 물었습니다. "트레이싱은 설정해뒀어요?"
Micrometer Tracing은 분산 시스템에서 요청이 여러 서비스를 거쳐가는 과정을 추적하는 라이브러리입니다. 마치 택배 배송 조회처럼 요청이 어디를 거쳐갔는지 기록합니다.
Spring Boot 3부터 공식 지원되며, Zipkin이나 Jaeger 같은 트레이싱 시스템과 연동됩니다.
다음 코드를 살펴봅시다.
// build.gradle - Micrometer Tracing 기본 구성
dependencies {
// Micrometer Tracing 브릿지
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
// Zipkin으로 데이터 전송
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
// Spring Boot Actuator (선택)
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 최근 회사의 모놀리식 시스템이 마이크로서비스로 전환되면서 새로운 고민이 생겼습니다.
하나의 주문 요청이 주문 서비스, 결제 서비스, 재고 서비스, 배송 서비스를 거쳐가는데, 어디선가 병목이 발생해도 원인을 찾기가 너무 어려웠습니다. 선배 개발자 박시니어 씨가 김개발 씨의 고민을 듣고 말했습니다.
"그럴 때 필요한 게 바로 트레이싱이에요. Micrometer Tracing을 설정해보세요." 그렇다면 트레이싱이란 정확히 무엇일까요?
쉽게 비유하자면, 트레이싱은 마치 택배 배송 조회 시스템과 같습니다. 택배를 보낼 때 송장번호가 부여되고, 그 번호로 물류센터를 거쳐 배송되는 전 과정을 추적할 수 있습니다.
집화 완료, A물류센터 도착, B물류센터 통과, 배송 출발, 배송 완료처럼 각 단계가 기록됩니다. 트레이싱도 마찬가지로 요청에 고유한 ID를 부여하고, 그 요청이 여러 서비스를 거쳐가는 과정을 기록합니다.
트레이싱이 없던 시절에는 어땠을까요? 개발자들은 각 서비스의 로그를 일일이 뒤져야 했습니다.
주문 서비스 로그를 확인하고, 결제 서비스 로그를 열어보고, 재고 서비스 로그를 또 찾아봐야 했습니다. 문제는 이 로그들을 연결할 방법이 없다는 것이었습니다.
어떤 주문 로그가 어떤 결제 로그와 연결되는지 확신할 수 없었습니다. 서비스가 5개, 10개로 늘어나면 상황은 더욱 악화됐습니다.
바로 이런 문제를 해결하기 위해 분산 트레이싱이 등장했습니다. Micrometer Tracing을 사용하면 하나의 요청이 여러 서비스를 거쳐가는 전체 흐름을 시각적으로 확인할 수 있습니다.
각 서비스에서 얼마나 시간이 걸렸는지, 어디서 에러가 발생했는지 한눈에 파악됩니다. 무엇보다 모든 서비스의 로그를 하나의 추적 ID로 연결할 수 있다는 큰 이점이 있습니다.
Micrometer는 원래 메트릭 수집 라이브러리로 유명했습니다. Spring Boot 2부터 기본 메트릭 라이브러리로 채택됐고, 개발자들에게 친숙한 이름이 되었습니다.
Spring Boot 3부터는 트레이싱 기능까지 통합되면서 Micrometer Tracing이 탄생했습니다. 핵심 개념을 이해해봅시다.
트레이싱에는 세 가지 중요한 개념이 있습니다. 첫째, Trace는 하나의 요청이 시작부터 끝까지 거쳐가는 전체 여정입니다.
둘째, Span은 그 여정 중 하나의 작업 단위입니다. 예를 들어 데이터베이스 조회, HTTP 요청, 메시지 전송 같은 것들이 각각 하나의 Span이 됩니다.
셋째, Trace ID는 전체 여정을 식별하는 고유 번호입니다. 위의 코드를 살펴보겠습니다.
첫 번째 의존성인 micrometer-tracing-bridge-brave는 Micrometer와 Brave 라이브러리를 연결하는 브릿지입니다. Brave는 Zipkin의 Java 클라이언트 라이브러리로, 실제 트레이싱 데이터를 생성하는 역할을 합니다.
두 번째 zipkin-reporter-brave는 생성된 트레이싱 데이터를 Zipkin 서버로 전송합니다. Zipkin은 분산 트레이싱 시스템으로, 트레이스를 저장하고 시각화하는 도구입니다.
실제 현업에서는 어떻게 활용할까요? 대규모 이커머스 서비스를 운영하는 회사를 생각해봅시다.
사용자가 상품을 주문하면 주문 서비스, 결제 서비스, 포인트 서비스, 쿠폰 서비스, 재고 서비스, 배송 서비스가 순차적으로 호출됩니다. 어느 날 주문이 평소보다 3초 더 걸린다는 모니터링 알람이 발생했습니다.
Micrometer Tracing이 설정돼 있다면 Zipkin 대시보드에서 해당 추적 ID를 검색하고, 어느 서비스에서 지연이 발생했는지 즉시 확인할 수 있습니다. 네이버, 카카오, 쿠팡 같은 대기업들은 이미 분산 트레이싱을 적극 활용하고 있습니다.
수백 개의 마이크로서비스가 유기적으로 연결된 환경에서 트레이싱 없이는 장애 대응이 불가능하기 때문입니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 트레이싱 데이터를 모든 요청에 대해 100% 수집하려는 것입니다. 트래픽이 많은 서비스에서 모든 요청을 추적하면 성능 오버헤드가 발생하고 저장 공간도 빠르게 소진됩니다.
따라서 샘플링을 적절히 설정해야 합니다. 예를 들어 10% 샘플링으로 설정하면 10개 요청 중 1개만 추적합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 Micrometer Tracing을 설정한 김개발 씨는 Zipkin 대시보드를 열어봤습니다.
느린 요청의 추적 ID를 검색하자 재고 서비스에서 5초나 걸렸다는 것이 한눈에 보였습니다. "아, 재고 서비스의 데이터베이스 쿼리가 문제였군요!" 트레이싱을 제대로 이해하면 분산 시스템의 복잡한 문제를 빠르게 해결할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - Spring Boot 3 이상에서는 Micrometer Tracing이 자동 설정되므로 의존성만 추가하면 됩니다
- 프로덕션 환경에서는 샘플링 비율을 0.1(10%) 정도로 설정하는 것이 일반적입니다
- Zipkin 외에 Jaeger, Tempo 같은 다른 백엔드도 사용할 수 있습니다
2. 의존성 설정
김개발 씨는 Micrometer Tracing을 실제로 프로젝트에 적용하기로 했습니다. 하지만 Gradle 파일을 열어보니 어떤 라이브러리를 추가해야 할지 막막했습니다.
박시니어 씨가 말했습니다. "트레이싱 설정은 세 가지만 기억하면 돼요.
브릿지, 리포터, 백엔드입니다."
Micrometer Tracing을 사용하려면 세 가지 핵심 의존성이 필요합니다. 트레이싱 브릿지는 Micrometer API와 실제 트레이싱 구현체를 연결하고, 리포터는 추적 데이터를 외부 시스템으로 전송하며, 백엔드 클라이언트는 Zipkin이나 Jaeger 같은 시각화 도구와 통신합니다.
다음 코드를 살펴봅시다.
// build.gradle - 완전한 Micrometer Tracing 설정
dependencies {
// Spring Boot 웹 스타터
implementation 'org.springframework.boot:spring-boot-starter-web'
// Brave 기반 트레이싱 브릿지
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
// Zipkin 리포터 (HTTP 전송)
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
// 선택: OTel 사용 시
// implementation 'io.micrometer:micrometer-tracing-bridge-otel'
}
김개발 씨는 새로운 스프링 부트 프로젝트를 시작했습니다. 회사에서 모든 신규 서비스에 트레이싱을 필수로 적용하기로 방침이 정해졌기 때문입니다.
하지만 인터넷을 검색해보니 Micrometer Tracing 관련 라이브러리가 너무 많아서 어떤 것을 선택해야 할지 헷갈렸습니다. 박시니어 씨가 김개발 씨의 화면을 보더니 웃으며 말했습니다.
"복잡해 보이지만 구조를 이해하면 간단해요. 레고 블록 조립하듯이 필요한 부품만 끼워 맞추면 됩니다." 의존성 구조를 이해해봅시다.
Micrometer Tracing은 마치 어댑터 패턴처럼 설계되어 있습니다. Micrometer는 추상화 계층만 제공하고, 실제 트레이싱 작업은 Brave나 OpenTelemetry 같은 구현체가 담당합니다.
이렇게 설계된 이유는 특정 벤더에 종속되지 않기 위해서입니다. 필요에 따라 Brave에서 OpenTelemetry로 전환할 수도 있습니다.
의존성이 없던 시절에는 어땠을까요? Spring Cloud Sleuth 시대에는 설정이 훨씬 복잡했습니다.
Sleuth 자체를 추가하고, Brave를 별도로 설정하고, Zipkin 클라이언트도 따로 추가해야 했습니다. 버전 충돌도 자주 발생했고, 어떤 조합이 호환되는지 확인하는 것만 해도 시간이 오래 걸렸습니다.
Spring Boot 2.x 시절의 악몽 같은 기억입니다. Spring Boot 3부터는 상황이 크게 개선됐습니다.
Micrometer Tracing이 공식 스펙으로 채택되면서 의존성 관리가 훨씬 단순해졌습니다. Spring Boot BOM이 호환되는 버전을 자동으로 관리해주기 때문입니다.
버전을 명시하지 않아도 Spring Boot가 알아서 맞는 버전을 선택합니다. 이것은 개발자의 인지 부담을 크게 줄여줍니다.
코드를 하나씩 분석해보겠습니다. 첫 번째 줄의 spring-boot-starter-web은 기본 웹 애플리케이션 의존성입니다.
트레이싱과 직접 관련은 없지만 대부분의 마이크로서비스가 REST API를 제공하므로 포함했습니다. 두 번째 핵심 의존성인 micrometer-tracing-bridge-brave가 등장합니다.
여기서 bridge라는 단어가 중요합니다. Micrometer API와 Brave 구현체 사이의 다리 역할을 합니다.
코드에서는 Micrometer API만 사용하고, 실제 트레이싱은 Brave가 처리합니다. 세 번째 zipkin-reporter-brave는 생성된 트레이스 데이터를 Zipkin 서버로 전송하는 역할을 합니다.
기본적으로 HTTP를 통해 전송하며, 배치 처리와 재시도 로직도 내장되어 있습니다. 네트워크 문제로 전송이 실패해도 자동으로 재시도합니다.
주석 처리된 부분을 보면 OpenTelemetry 옵션도 있습니다. OpenTelemetry는 CNCF의 표준 관측성 프로젝트로, 최근 많은 기업이 전환하고 있습니다.
Brave 대신 OpenTelemetry를 사용하려면 micrometer-tracing-bridge-otel과 opentelemetry-exporter-zipkin을 추가하면 됩니다. 실제 현업에서는 어떻게 선택할까요?
스타트업이나 중소기업에서는 보통 Brave와 Zipkin 조합을 선택합니다. 설정이 간단하고, 문서가 풍부하며, 커뮤니티 지원도 좋기 때문입니다.
Zipkin은 단일 바이너리로 실행할 수 있어 로컬 개발 환경 구축도 쉽습니다. 대기업이나 클라우드 네이티브 환경에서는 OpenTelemetry와 Jaeger를 선호하는 추세입니다.
OpenTelemetry는 메트릭, 로그, 트레이스를 통합 관리할 수 있고, 여러 벤더를 동시에 지원합니다. AWS X-Ray, Google Cloud Trace, Datadog 같은 상용 서비스와도 쉽게 연동됩니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 모든 의존성을 다 추가하는 것입니다.
Brave와 OpenTelemetry를 동시에 추가하면 충돌이 발생합니다. 둘 중 하나만 선택해야 합니다.
또한 리포터도 하나만 있으면 됩니다. zipkin-reporter-brave와 zipkin-reporter-otel을 동시에 추가할 필요는 없습니다.
Maven을 사용하는 경우에는 어떻게 할까요? Gradle 대신 Maven을 사용한다면 pom.xml에 동일한 의존성을 추가하면 됩니다.
artifactId와 groupId만 맞춰주면 됩니다. Spring Boot의 의존성 관리 덕분에 버전은 생략할 수 있습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 자신의 build.gradle을 수정했습니다.
Brave 브릿지와 Zipkin 리포터 두 개만 추가하니 깔끔했습니다. "생각보다 간단하네요!" 의존성 설정을 제대로 이해하면 프로젝트를 빠르게 시작할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - Spring Boot 3.x 이상에서는 버전을 명시하지 않아도 BOM이 자동으로 관리합니다
- Brave는 설정이 간단하고 OpenTelemetry는 확장성이 좋으므로 프로젝트 규모에 따라 선택하세요
- 로컬 개발 시에는 docker run -d -p 9411:9411 openzipkin/zipkin 명령으로 Zipkin을 쉽게 실행할 수 있습니다
3. 자동 계측
의존성을 추가한 김개발 씨는 이제 코드를 작성할 차례라고 생각했습니다. 하지만 박시니어 씨가 웃으며 말했습니다.
"코드 작성은 필요 없어요. 이미 트레이싱이 동작하고 있을 거예요." 김개발 씨는 의아한 표정을 지었습니다.
"아무 코드도 안 썼는데요?"
자동 계측은 개발자가 별도 코드를 작성하지 않아도 Spring Boot가 자동으로 트레이싱 데이터를 생성하는 기능입니다. RestTemplate, WebClient, JDBC 같은 주요 라이브러리에 대한 추적이 자동으로 이루어집니다.
설정 파일에 Zipkin 엔드포인트만 지정하면 모든 HTTP 요청과 데이터베이스 쿼리가 추적됩니다.
다음 코드를 살펴봅시다.
# application.yml - 자동 계측 설정
management:
tracing:
sampling:
probability: 1.0 # 100% 샘플링 (개발용)
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
# 프로덕션 환경 설정 예시
# management:
# tracing:
# sampling:
# probability: 0.1 # 10% 샘플링
김개발 씨는 트레이싱을 위해 복잡한 코드를 작성해야 한다고 생각했습니다. 모든 컨트롤러 메서드마다 추적 로직을 추가하고, 데이터베이스 호출마다 Span을 생성하는 코드를 작성해야 할 것 같았습니다.
하지만 박시니어 씨의 말을 듣고 당황했습니다. "Spring Boot의 자동 구성 덕분에 대부분의 작업은 자동으로 처리돼요.
이게 바로 자동 계측의 마법입니다." 자동 계측이란 정확히 무엇일까요? 쉽게 비유하자면, 자동 계측은 마치 자동차의 블랙박스와 같습니다.
운전자가 별도로 녹화 버튼을 누르지 않아도 시동을 켜는 순간부터 모든 주행 과정이 자동으로 기록됩니다. 급가속, 급정거, 충격 감지까지 알아서 처리됩니다.
자동 계측도 마찬가지로 애플리케이션이 실행되는 순간부터 모든 주요 작업을 자동으로 추적합니다. 자동 계측이 없던 시절에는 어땠을까요?
개발자들은 모든 메서드 시작과 끝에 추적 코드를 직접 작성해야 했습니다. 컨트롤러에 들어올 때 Span을 시작하고, 서비스 레이어로 전달할 때 새로운 Span을 만들고, 데이터베이스를 호출할 때 또 Span을 생성했습니다.
코드가 지저분해지고, 실수로 Span을 닫지 않으면 메모리 누수가 발생했습니다. 더 큰 문제는 코드 한 줄 바꿀 때마다 추적 로직도 함께 수정해야 한다는 것이었습니다.
바로 이런 문제를 해결하기 위해 자동 계측이 등장했습니다. Spring Boot는 AOP와 필터 체인을 활용해 자동으로 트레이싱 로직을 주입합니다.
RestTemplate이나 WebClient로 HTTP 요청을 보낼 때 인터셉터가 자동으로 추적 헤더를 추가합니다. JdbcTemplate으로 데이터베이스를 조회할 때도 프록시가 쿼리 실행 시간을 자동으로 측정합니다.
개발자는 평소처럼 코드를 작성하기만 하면 됩니다. 설정 파일을 분석해보겠습니다.
첫 번째 핵심 설정인 management.tracing.sampling.probability는 샘플링 비율을 결정합니다. 값이 1.0이면 모든 요청을 추적하고, 0.1이면 10개 중 1개만 추적합니다.
개발 환경에서는 1.0으로 설정해 모든 요청을 확인하는 것이 좋습니다. 하지만 프로덕션에서 1.0으로 설정하면 성능 문제가 발생할 수 있습니다.
두 번째 설정인 management.zipkin.tracing.endpoint는 Zipkin 서버의 주소를 지정합니다. 기본값은 http://localhost:9411/api/v2/spans입니다.
로컬에서 Zipkin을 Docker로 실행했다면 이 주소 그대로 사용하면 됩니다. 쿠버네티스 환경이라면 서비스 DNS 주소를 사용합니다.
어떤 것들이 자동으로 계측될까요? Spring Boot는 다양한 라이브러리에 대한 자동 계측을 제공합니다.
RestTemplate과 WebClient는 HTTP 클라이언트 요청을 추적합니다. JdbcTemplate과 R2DBC는 데이터베이스 쿼리를 추적합니다.
Kafka, RabbitMQ, Redis 같은 메시징 시스템도 자동으로 계측됩니다. Spring WebMVC와 WebFlux의 모든 컨트롤러 엔드포인트도 자동 추적 대상입니다.
실제 현업에서는 어떻게 동작할까요? 사용자가 주문 API를 호출했다고 가정해봅시다.
Spring WebMVC 필터가 요청을 받는 순간 새로운 Trace ID와 Span ID를 생성합니다. 컨트롤러 메서드가 실행되고, 서비스 레이어에서 RestTemplate으로 결제 서비스를 호출하면 자동으로 새로운 Span이 생성됩니다.
이때 HTTP 헤더에 부모 Span 정보가 자동으로 추가됩니다. 결제 서비스는 이 헤더를 읽어 동일한 Trace에 포함됩니다.
로그를 확인하면 자동 계측을 실감할 수 있습니다. Spring Boot 애플리케이션을 실행하고 API를 호출하면 로그에 [appName,traceId,spanId] 형태의 정보가 자동으로 출력됩니다.
예를 들어 [order-service,a1b2c3d4e5f6,1234567890ab]처럼 보입니다. 이것은 MDC 자동 설정 덕분입니다.
Logback이나 Log4j2가 자동으로 추적 정보를 로그에 포함시킵니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 자동 계측이 만능이라고 생각하는 것입니다. 자동 계측은 주요 라이브러리만 지원합니다.
커스텀 HTTP 클라이언트나 네이티브 JDBC 코드는 자동으로 추적되지 않습니다. 이런 경우 수동으로 Span을 생성해야 합니다.
또한 비동기 처리나 멀티스레드 환경에서는 컨텍스트 전파가 자동으로 되지 않을 수 있습니다. 샘플링 비율 설정도 중요합니다.
트래픽이 초당 1000건인 서비스에서 샘플링을 100%로 설정하면 Zipkin 서버가 감당하지 못할 수 있습니다. 일반적으로 프로덕션에서는 1~10% 사이로 설정합니다.
중요한 점은 샘플링된 요청만 Zipkin에 전송될 뿐, 추적 ID 자체는 모든 요청에 부여된다는 것입니다. 로그를 통해서는 모든 요청을 추적할 수 있습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. application.yml에 설정을 추가하고 애플리케이션을 재시작한 김개발 씨는 API를 호출해봤습니다.
Zipkin 대시보드를 열어보니 요청이 자동으로 기록되어 있었습니다. "정말 아무 코드도 안 썼는데 동작하네요!" 자동 계측을 제대로 이해하면 트레이싱 도입이 훨씬 쉬워집니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 로컬 개발 시에는 샘플링을 1.0으로, 프로덕션에서는 0.1 정도로 설정하세요
- 로그에 자동으로 추적 ID가 출력되는지 확인하면 자동 계측이 잘 동작하는지 알 수 있습니다
- Zipkin UI는 기본적으로 http://localhost:9411에서 확인할 수 있습니다
4. 수동 Span 생성
자동 계측에 만족하던 김개발 씨는 곧 한계에 부딪혔습니다. 주문 처리 로직에서 외부 재고 확인 API를 호출하는데, 이 부분이 Zipkin에 나타나지 않았습니다.
박시니어 씨가 코드를 보더니 말했습니다. "아, 이건 커스텀 HTTP 클라이언트라서 자동 계측이 안 돼요.
수동으로 Span을 만들어야 해요."
수동 Span 생성은 자동 계측이 지원하지 않는 작업을 추적하기 위해 개발자가 직접 Span을 만드는 방법입니다. Tracer 인터페이스를 주입받아 새로운 Span을 시작하고, 작업이 끝나면 종료합니다.
비즈니스 로직의 특정 구간이나 외부 시스템 호출을 명시적으로 추적할 때 사용됩니다.
다음 코드를 살펴봅시다.
@Service
@RequiredArgsConstructor
public class InventoryService {
private final Tracer tracer; // Micrometer Tracer 주입
public boolean checkStock(String productId, int quantity) {
// 새로운 Span 생성
Span span = tracer.nextSpan().name("inventory.check").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
// 실제 재고 확인 로직
span.tag("product.id", productId);
span.tag("quantity", String.valueOf(quantity));
boolean available = callExternalInventoryAPI(productId, quantity);
span.tag("stock.available", String.valueOf(available));
return available;
} catch (Exception e) {
span.error(e); // 에러 기록
throw e;
} finally {
span.end(); // Span 종료 (필수!)
}
}
}
김개발 씨는 주문 서비스를 개발하면서 외부 파트너사의 재고 확인 API를 호출해야 했습니다. 문제는 이 API가 자체 개발한 HTTP 클라이언트를 사용한다는 것이었습니다.
RestTemplate도 아니고 WebClient도 아닌, 회사 공통 라이브러리로 만든 CustomHttpClient였습니다. Zipkin에서 확인해보니 이 부분이 블랙박스처럼 보이지 않았습니다.
박시니어 씨가 설명했습니다. "자동 계측은 스프링이 알고 있는 빈에만 적용돼요.
커스텀 로직에는 우리가 직접 추적 코드를 넣어야 합니다." 수동 Span 생성이란 정확히 무엇일까요? 쉽게 비유하자면, 수동 Span은 마치 일기를 쓰는 것과 같습니다.
자동 계측은 스마트워치가 자동으로 기록하는 걸음 수나 심박수 같은 것입니다. 하지만 "오늘 중요한 회의를 했다"거나 "친구와 저녁을 먹었다" 같은 특별한 이벤트는 스마트워치가 자동으로 알 수 없습니다.
직접 일기장에 기록해야 합니다. 수동 Span도 마찬가지로 비즈니스적으로 중요한 작업을 명시적으로 기록하는 것입니다.
수동 Span이 없던 시절에는 어땠을까요? 개발자들은 커스텀 로직의 성능을 측정하기 위해 System.currentTimeMillis()로 시작 시간을 기록하고, 끝날 때 경과 시간을 계산했습니다.
그리고 이 정보를 로그로 출력했습니다. 문제는 이 로그가 분산 트레이싱 시스템과 연결되지 않는다는 것이었습니다.
Zipkin에서 전체 요청 흐름을 볼 때 이 부분만 빠져 있어서 완전한 그림을 그릴 수 없었습니다. 바로 이런 문제를 해결하기 위해 수동 Span 생성이 필요합니다.
Micrometer Tracing은 Tracer 인터페이스를 제공합니다. 이것을 스프링 빈으로 주입받아 원하는 시점에 Span을 생성할 수 있습니다.
생성된 Span은 현재 활성화된 Trace에 자동으로 연결되므로, Zipkin에서 부모-자식 관계로 표시됩니다. 코드를 한 줄씩 분석해보겠습니다.
첫 번째 줄에서 Tracer를 생성자 주입으로 받습니다. Tracer는 Span을 만드는 팩토리 역할을 합니다.
두 번째로 tracer.nextSpan()을 호출해 새로운 Span을 준비하고, name() 메서드로 이름을 지정합니다. 이름은 Zipkin에서 표시될 레이블입니다.
inventory.check처럼 점으로 구분하면 계층 구조를 표현할 수 있습니다. 세 번째 중요한 부분은 try-with-resources 구문입니다.
tracer.withSpan(span)은 현재 스레드에 이 Span을 활성화합니다. 이것이 중요한 이유는 이 블록 안에서 호출되는 다른 자동 계측 코드들이 이 Span을 부모로 인식하기 때문입니다.
SpanInScope는 AutoCloseable이므로 블록이 끝나면 자동으로 정리됩니다. 네 번째로 span.tag() 메서드로 메타데이터를 추가합니다.
태그는 키-밸류 형태로 저장되며, Zipkin에서 검색하거나 필터링할 때 사용됩니다. productId나 quantity 같은 비즈니스 정보를 태그로 남기면 나중에 특정 상품의 재고 확인 추적만 찾아볼 수 있습니다.
다섯 번째로 catch 블록에서 span.error(e)를 호출합니다. 이것은 에러가 발생했다는 것을 Span에 기록하고, 예외 스택트레이스를 저장합니다.
Zipkin에서 이 Span은 빨간색으로 표시되어 에러를 한눈에 알 수 있습니다. 마지막으로 finally 블록에서 span.end()를 반드시 호출해야 합니다.
이것을 빼먹으면 Span이 종료되지 않아 메모리 누수가 발생하고, Zipkin에도 전송되지 않습니다. 가장 흔한 실수 중 하나이므로 주의해야 합니다.
실제 현업에서는 어떻게 활용할까요? 대규모 배치 작업을 생각해봅시다.
매일 밤 100만 건의 주문 데이터를 집계하는 배치 작업이 있습니다. 이 작업은 여러 단계로 나뉘는데, 데이터 읽기, 검증, 변환, 저장 단계가 있습니다.
각 단계마다 수동 Span을 생성하면 어느 단계에서 시간이 오래 걸리는지 명확히 알 수 있습니다. 또 다른 예는 복잡한 비즈니스 로직입니다.
대출 심사 시스템에서 신용 평가, 소득 확인, 담보 평가 등 여러 단계를 거칩니다. 각 단계마다 Span을 생성하고 결과를 태그로 기록하면, 어떤 고객의 대출이 어느 단계에서 거부됐는지 추적할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 Span을 너무 많이 만드는 것입니다.
모든 메서드마다 Span을 만들면 오히려 성능이 저하되고 Zipkin 화면이 복잡해집니다. 의미 있는 비즈니스 작업 단위로만 Span을 생성하는 것이 좋습니다.
일반적으로 하나의 Trace당 10~20개 정도의 Span이 적당합니다. 또 다른 실수는 span.end()를 호출하지 않는 것입니다.
try-finally 패턴을 항상 사용하거나, Spring AOP로 추상화하는 것이 안전합니다. 실제로 많은 회사에서 @Traced 같은 커스텀 어노테이션을 만들어 사용합니다.
비동기 환경에서는 주의가 더 필요합니다. CompletableFuture나 @Async를 사용할 때는 컨텍스트가 자동으로 전파되지 않습니다.
이런 경우 TraceContext를 수동으로 전달하거나, Spring의 TaskDecorator를 설정해야 합니다. 그렇지 않으면 비동기 작업이 부모 Trace와 연결되지 않습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 가이드대로 Tracer를 주입받고 수동 Span을 추가한 김개발 씨는 Zipkin을 다시 확인했습니다.
이번에는 재고 확인 구간이 명확히 보였고, 얼마나 시간이 걸렸는지도 알 수 있었습니다. "이제 완벽하네요!" 수동 Span 생성을 제대로 이해하면 자동 계측의 한계를 넘어설 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 수동 Span은 의미 있는 비즈니스 작업 단위로만 생성하세요
- span.end()를 빼먹지 않도록 try-finally 패턴을 항상 사용하세요
- 태그를 적극 활용하면 나중에 특정 조건의 요청만 검색할 수 있습니다
5. 추적 ID 로깅
Zipkin 대시보드를 활용하던 김개발 씨는 문득 궁금해졌습니다. "장애가 발생했을 때 고객이 제공한 요청 정보로 어떻게 Zipkin에서 찾을 수 있을까요?" 박시니어 씨가 답했습니다.
"그럴 때는 로그에 추적 ID를 남기면 됩니다. 로그와 트레이싱을 연결하는 거죠."
추적 ID 로깅은 애플리케이션 로그에 Trace ID와 Span ID를 자동으로 포함시켜 로그와 분산 트레이싱을 연결하는 방법입니다. Spring Boot는 MDC를 통해 자동으로 추적 정보를 로그에 추가하며, 로그 패턴만 수정하면 모든 로그에 추적 ID가 출력됩니다.
다음 코드를 살펴봅시다.
# application.yml - 로깅 패턴 설정
logging:
pattern:
# 콘솔 로그에 추적 정보 포함
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - [%X{traceId:-},%X{spanId:-}] - %msg%n"
level:
root: INFO
org.springframework.web: DEBUG
# Logback 설정 파일 (logback-spring.xml) 예시
# <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - [%X{traceId:-},%X{spanId:-}] - %msg%n</pattern>
김개발 씨는 운영 중인 서비스에서 장애 알람을 받았습니다. 고객센터에서 "방금 주문했는데 오류가 났어요"라는 문의가 왔습니다.
하지만 정확히 언제, 어떤 주문인지 특정하기 어려웠습니다. Zipkin에서 시간대별로 에러를 검색해봤지만 해당 시간대에 수백 건의 요청이 있어서 어떤 것이 그 고객의 요청인지 알 수 없었습니다.
박시니어 씨가 조언했습니다. "로그에 추적 ID가 있으면 이런 문제를 쉽게 해결할 수 있어요.
로그에서 에러를 찾고, 그 로그의 추적 ID로 Zipkin을 검색하면 됩니다." 추적 ID 로깅이란 정확히 무엇일까요? 쉽게 비유하자면, 추적 ID 로깅은 마치 택배 송장번호를 모든 문서에 적는 것과 같습니다.
접수증에도 송장번호, 운송장에도 송장번호, 배송 완료 문자에도 송장번호가 있습니다. 어느 문서를 보든 송장번호만 있으면 전체 배송 과정을 조회할 수 있습니다.
추적 ID 로깅도 마찬가지로 모든 로그 라인에 Trace ID와 Span ID를 포함시켜 언제든 전체 요청 흐름을 찾을 수 있게 합니다. 추적 ID 로깅이 없던 시절에는 어땠을까요?
개발자들은 로그를 보고 에러를 찾은 후, 그 시간대의 모든 Zipkin 트레이스를 하나씩 클릭해가며 확인해야 했습니다. 운이 좋으면 금방 찾았지만, 트래픽이 많은 시간대라면 수십 분이 걸리기도 했습니다.
더 큰 문제는 여러 서비스를 거쳐간 요청의 경우 각 서비스의 로그를 연결할 방법이 없다는 것이었습니다. "이 주문 서비스 로그와 저 결제 서비스 로그가 같은 요청일까?" 추측만 할 뿐이었습니다.
바로 이런 문제를 해결하기 위해 추적 ID 로깅이 필수가 되었습니다. Spring Boot는 MDC를 활용해 자동으로 추적 정보를 로그에 포함시킵니다.
MDC는 Mapped Diagnostic Context의 약자로, 스레드 로컬 기반으로 동작하는 로깅 컨텍스트입니다. Micrometer Tracing은 요청이 시작될 때 자동으로 traceId와 spanId를 MDC에 추가하고, 요청이 끝나면 자동으로 제거합니다.
로그 패턴을 분석해보겠습니다. logging.pattern.console 설정을 보면 %X{traceId:-}와 %X{spanId:-}라는 패턴이 있습니다.
%X는 MDC 값을 읽어오는 Logback의 패턴입니다. traceId는 MDC 키 이름이고, :- 부분은 기본값을 의미합니다.
만약 traceId가 없으면 빈 문자열이 출력됩니다. 이렇게 설정하면 추적 컨텍스트가 없는 로그에서도 에러가 발생하지 않습니다.
실제 로그 출력을 살펴봅시다. 설정을 적용하고 애플리케이션을 실행하면 다음과 같은 로그가 출력됩니다.
"2025-01-15 10:30:45 [http-nio-8080-exec-1] INFO c.e.OrderController - [a1b2c3d4e5f6g7h8,1234567890abcdef] - 주문 접수 완료". 여기서 대괄호 안의 첫 번째 값이 Trace ID, 두 번째 값이 Span ID입니다.
이 ID를 복사해서 Zipkin 검색창에 붙여넣으면 해당 요청의 전체 흐름이 나타납니다. 여러 서비스를 거쳐가는 요청을 추적해봅시다.
주문 서비스에서 결제 서비스를 호출하는 상황을 생각해봅시다. 주문 서비스의 로그에는 "[a1b2c3d4e5f6g7h8,1234567890abcdef]"가 출력됩니다.
결제 서비스의 로그에는 "[a1b2c3d4e5f6g7h8,fedcba0987654321]"이 출력됩니다. Trace ID는 동일하지만 Span ID는 다릅니다.
이것은 동일한 요청이지만 다른 작업 단위라는 뜻입니다. Trace ID로 Zipkin을 검색하면 두 서비스의 Span이 모두 표시됩니다.
실제 현업에서는 어떻게 활용할까요? 장애 대응 시나리오를 생각해봅시다.
고객이 "방금 주문했는데 오류가 났어요"라고 연락했습니다. 우선 Kibana나 CloudWatch 같은 로그 집계 시스템에서 최근 5분간의 ERROR 레벨 로그를 검색합니다.
로그에서 추적 ID를 찾아 복사합니다. Zipkin에서 이 ID로 검색하면 어느 서비스에서 에러가 발생했는지, 어떤 외부 API 호출이 실패했는지, 데이터베이스 쿼리는 정상이었는지 전체 흐름을 볼 수 있습니다.
또 다른 활용 방법은 성능 분석입니다. 느린 요청의 로그를 찾고, 추적 ID로 Zipkin을 조회해 병목 구간을 파악합니다.
로그만으로는 "결제 처리가 느리다"는 것만 알 수 있지만, Zipkin을 함께 보면 "결제 서비스의 외부 PG사 API 호출이 5초 걸렸다"는 구체적인 원인을 알 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 로그 패턴을 잘못 설정하는 것입니다. %X{traceId} 처럼 기본값 없이 설정하면, 추적 컨텍스트가 없는 스레드에서 로그를 출력할 때 null이 출력됩니다.
%X{traceId:-}처럼 기본값을 지정하는 것이 안전합니다. 또 다른 주의점은 비동기 환경입니다.
@Async나 CompletableFuture로 비동기 작업을 실행하면 새로운 스레드가 생성되는데, MDC는 스레드 로컬이므로 자동으로 전파되지 않습니다. Spring의 TaskDecorator를 설정하거나, MDC를 수동으로 복사해야 합니다.
JSON 형식 로그를 사용하는 경우에는 어떻게 할까요? Logstash나 Fluentd를 사용해 JSON 로그를 수집한다면, traceId와 spanId를 별도 필드로 추출하는 것이 좋습니다.
Logback Encoder를 사용하면 {"timestamp": "...", "level": "INFO", "traceId": "...", "spanId": "...", "message": "..."}처럼 구조화된 로그를 출력할 수 있습니다. 이렇게 하면 로그 시스템에서 추적 ID로 검색하기 더 쉽습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 로그 패턴을 수정하고 재시작한 김개발 씨는 이제 모든 로그에 추적 ID가 포함된 것을 확인했습니다.
다음에 장애가 발생하면 로그에서 추적 ID를 찾아 Zipkin으로 바로 검색할 수 있게 되었습니다. "로그와 트레이싱이 이렇게 연결되는구나!" 추적 ID 로깅을 제대로 이해하면 장애 대응 시간을 크게 줄일 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 로그 패턴에 %X{traceId:-}와 %X{spanId:-}를 항상 포함시키세요
- JSON 로그를 사용한다면 traceId를 별도 필드로 추출하면 검색이 쉽습니다
- 비동기 환경에서는 TaskDecorator를 설정해 MDC를 전파하세요
6. Baggage 전파
트레이싱을 활용하던 김개발 씨는 새로운 요구사항을 받았습니다. "고객 타입별로 요청을 분류해서 추적하고 싶어요.
VIP 고객과 일반 고객을 구분해서 모니터링할 수 있나요?" 박시니어 씨가 웃으며 답했습니다. "그럴 때 사용하는 게 바로 Baggage입니다.
비즈니스 컨텍스트를 트레이스에 실어 보낼 수 있어요."
Baggage는 분산 시스템에서 비즈니스 메타데이터를 전체 트레이스를 따라 전파하는 메커니즘입니다. 사용자 ID, 고객 등급, 테넌트 ID 같은 정보를 추가하면 모든 서비스에서 이 값을 읽고 사용할 수 있습니다.
HTTP 헤더로 자동 전파되며, 로그와 트레이스에 모두 포함됩니다.
다음 코드를 살펴봅시다.
# application.yml - Baggage 설정
management:
tracing:
baggage:
enabled: true
remote-fields:
- userId
- customerType
- tenantId
correlation:
fields:
- userId
- customerType
// Java 코드 - Baggage 사용
@RestController
@RequiredArgsConstructor
public class OrderController {
private final Tracer tracer;
@PostMapping("/orders")
public OrderResponse createOrder(@RequestBody OrderRequest request) {
// Baggage에 비즈니스 컨텍스트 추가
tracer.currentTraceContext().context()
.baggageField("userId").updateValue(request.getUserId());
tracer.currentTraceContext().context()
.baggageField("customerType").updateValue(request.getCustomerType());
// 이후 모든 서비스 호출에서 자동으로 전파됨
return orderService.createOrder(request);
}
}
김개발 씨는 이커머스 서비스를 운영하면서 고민이 생겼습니다. VIP 고객과 일반 고객의 주문 처리 속도를 별도로 모니터링하고 싶었습니다.
VIP 고객에게는 더 빠른 서비스를 제공해야 하기 때문입니다. 하지만 트레이싱 데이터만으로는 어떤 요청이 VIP 고객의 것인지 구분할 수 없었습니다.
박시니어 씨가 설명했습니다. "그럴 때는 Baggage를 사용하면 돼요.
고객 타입 정보를 Baggage에 담으면 주문 서비스부터 결제 서비스, 배송 서비스까지 자동으로 전파됩니다." Baggage란 정확히 무엇일까요? 쉽게 비유하자면, Baggage는 마치 여행 가방의 네임 태그와 같습니다.
비행기를 탈 때 가방에 네임 태그를 붙이면, 출발 공항, 환승 공항, 도착 공항 모두에서 이 태그를 보고 누구의 가방인지 알 수 있습니다. 가방이 어디를 거쳐가든 태그는 계속 따라갑니다.
Baggage도 마찬가지로 요청이 여러 서비스를 거쳐갈 때 비즈니스 정보를 계속 실어 나릅니다. Baggage가 없던 시절에는 어땠을까요?
개발자들은 HTTP 헤더에 커스텀 값을 직접 추가하고, 각 서비스마다 이 헤더를 읽어서 다음 서비스로 전달하는 코드를 작성해야 했습니다. 서비스가 10개면 10번 반복해야 했고, 실수로 헤더를 전달하지 않으면 체인이 끊어졌습니다.
또한 로그에 이 정보를 포함시키려면 MDC에 수동으로 추가해야 했습니다. 코드가 복잡하고 유지보수가 어려웠습니다.
바로 이런 문제를 해결하기 위해 Baggage가 표준으로 채택되었습니다. W3C Trace Context 표준에서 Baggage를 정의했고, Micrometer Tracing도 이를 지원합니다.
한 번 Baggage에 값을 넣으면 자동으로 HTTP 헤더로 전파되고, MDC에도 추가되며, Zipkin 태그로도 기록됩니다. 개발자가 신경 쓸 부분이 크게 줄어들었습니다.
설정 파일을 분석해보겠습니다. management.tracing.baggage.enabled는 Baggage 기능을 활성화합니다.
remote-fields는 HTTP 헤더로 전파할 필드 이름을 지정합니다. userId, customerType, tenantId를 추가하면 baggage-userId, baggage-customerType, baggage-tenantId라는 HTTP 헤더로 자동 전달됩니다.
correlation.fields는 MDC에 추가할 필드를 지정합니다. 이렇게 설정하면 로그에서 %X{userId}나 %X{customerType}으로 값을 읽을 수 있습니다.
별도 코드 없이 설정만으로 로그에 비즈니스 정보가 포함됩니다. Java 코드를 살펴봅시다.
tracer.currentTraceContext().context()는 현재 활성화된 트레이스 컨텍스트를 가져옵니다. baggageField("userId")로 Baggage 필드를 지정하고, updateValue()로 값을 설정합니다.
이 코드가 실행되는 순간 userId는 현재 트레이스에 연결되고, 이후 모든 서비스 호출에 자동으로 전파됩니다. 실제 전파 과정을 추적해봅시다.
주문 서비스에서 userId를 Baggage에 추가하고 결제 서비스를 호출합니다. Spring의 RestTemplate이나 WebClient가 HTTP 요청을 보낼 때 자동으로 baggage-userId 헤더를 추가합니다.
결제 서비스는 이 헤더를 받아 자동으로 Baggage에 복원합니다. 결제 서비스에서 재고 서비스를 호출할 때도 동일한 헤더가 자동으로 전달됩니다.
개발자는 아무 코드도 작성하지 않았지만 userId는 모든 서비스를 따라갑니다. 실제 현업에서는 어떻게 활용할까요?
멀티테넌트 SaaS 서비스를 생각해봅시다. 하나의 애플리케이션을 여러 기업이 사용합니다.
각 기업을 구분하기 위해 tenantId를 Baggage에 담습니다. 그러면 로그를 볼 때 "이 로그는 A회사의 요청이구나", "저 로그는 B회사의 요청이구나"를 즉시 알 수 있습니다.
Zipkin에서도 tenantId로 필터링해 특정 기업의 요청만 모아볼 수 있습니다. 또 다른 활용 사례는 A/B 테스트입니다.
신규 기능을 테스트할 때 사용자를 A그룹과 B그룹으로 나눕니다. 그룹 정보를 Baggage에 담으면, 각 그룹별로 성능과 에러율을 분석할 수 있습니다.
"A그룹은 평균 응답시간이 500ms인데 B그룹은 700ms네요. 신규 기능이 느린 것 같습니다." 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 Baggage에 너무 많은 데이터를 담는 것입니다. Baggage는 모든 HTTP 요청 헤더에 포함되므로 크기가 커지면 네트워크 오버헤드가 발생합니다.
일반적으로 Baggage는 5~10개 필드 정도로 제한하고, 각 값도 짧게 유지하는 것이 좋습니다. 사용자 이름 전체보다는 사용자 ID만 담는 식입니다.
또 다른 주의점은 보안입니다. Baggage는 HTTP 헤더로 전파되므로 민감한 정보를 담으면 안 됩니다.
비밀번호, 신용카드 번호, 개인 식별 정보 같은 것은 절대 Baggage에 넣어서는 안 됩니다. 사용자 ID나 세션 토큰처럼 식별자만 담는 것이 안전합니다.
Baggage와 Span 태그의 차이를 이해해야 합니다. Span 태그는 현재 Span에만 기록되고 다른 서비스로 전파되지 않습니다.
반면 Baggage는 전체 트레이스를 따라 전파됩니다. 예를 들어 주문 서비스에서 Span 태그로 productId를 기록하면 주문 서비스 Span에만 나타납니다.
하지만 Baggage로 넣으면 결제 서비스, 배송 서비스 모두에서 productId를 읽을 수 있습니다. 실전 패턴을 하나 소개합니다.
많은 기업에서 인증 필터나 인터셉터에서 Baggage를 설정합니다. JWT 토큰을 검증할 때 사용자 정보를 추출해 Baggage에 담는 것입니다.
그러면 모든 컨트롤러와 서비스 레이어에서 별도 인증 로직 없이 Baggage에서 사용자 정보를 읽을 수 있습니다. 코드가 깔끔해지고 보안도 강화됩니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. Baggage 설정을 추가하고 customerType을 담은 김개발 씨는 Zipkin에서 VIP 고객의 요청만 필터링해봤습니다.
VIP 고객의 평균 응답시간이 일반 고객보다 짧다는 것을 확인할 수 있었습니다. "이제 고객 등급별로 모니터링할 수 있겠네요!" Baggage를 제대로 이해하면 비즈니스 컨텍스트를 분산 시스템 전체에 일관되게 전파할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - Baggage는 5~10개 필드로 제한하고 값은 짧게 유지하세요
- 민감한 정보는 절대 Baggage에 담지 마세요
- 인증 필터에서 Baggage를 설정하면 모든 레이어에서 사용자 정보를 쉽게 접근할 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
관찰 가능한 마이크로서비스 완벽 가이드
마이크로서비스 환경에서 시스템의 상태를 실시간으로 관찰하고 모니터링하는 방법을 배웁니다. Resilience4j, Zipkin, Prometheus, Grafana, EFK 스택을 활용하여 안정적이고 관찰 가능한 시스템을 구축하는 실전 가이드입니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
Prometheus 메트릭 수집 완벽 가이드
Spring Boot 애플리케이션의 메트릭을 Prometheus로 수집하고 모니터링하는 방법을 배웁니다. Actuator 설정부터 PromQL 쿼리까지 실무에 필요한 모든 내용을 다룹니다.
스프링 관찰 가능성 완벽 가이드
Spring Boot 3.x의 Observation API를 활용한 애플리케이션 모니터링과 추적 방법을 초급 개발자 눈높이에서 쉽게 설명합니다. 실무에서 바로 적용할 수 있는 메트릭 수집과 분산 추적 기법을 다룹니다.