| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- 일본어공부
- SWEA
- 자바
- 가벼운학습지후기
- 근로기준법
- 마이라이트
- javascript
- 코테
- React Native
- spring
- 에러해결
- 일본어학습지
- java
- 가벼운학습지
- 코딩
- SpringBoot4
- js
- 마이그레이션
- restapi
- 프로그래머스
- 성인학습지
- 알고리즘
- springboot
- 코딩테스트
- 생활정보
- 자바스크립트
- 삼성
- 일본어독학
- jpa
- 백엔드
- Today
- Total
개발에 AtoZ까지
[Spring] Micrometer로 갈까 OpenTelemetry 에이전트를 붙일까 — 둘 다 켜면 두 번 찍힙니다 본문
[Spring] Micrometer로 갈까 OpenTelemetry 에이전트를 붙일까 — 둘 다 켜면 두 번 찍힙니다
AtoZ 개발자 2026. 9. 12. 21:54쿠버네티스 매니페스트에 OTEL_EXPORTER_OTLP_ENDPOINT가 걸려 있는 서비스가 있습니다. OTel Java 에이전트를 붙여 두고 몇 달째 잘 쓰던 설정이죠. 그 상태로 Spring Boot를 4.1로 올리면 같은 요청 하나에 SERVER 스팬이 두 개씩 찍힙니다.
설정을 잘못 건드린 게 아닙니다. Spring Boot 4.1이 OTEL_* 환경변수를 자기 프로퍼티로 자동 매핑하기 때문입니다. 에이전트용으로 걸어 둔 엔드포인트를 앱도 자기 것으로 읽고, 두 개의 OpenTelemetry SDK가 같은 주소로 각각 내보내기 시작하죠.
에이전트와 앱을 동시에 띄워 중복 스팬을 재현한 결과가 아니라, Spring Boot 4.1.1 레퍼런스와 릴리스 노트, OpenTelemetry 공식 문서, Maven Central의 POM, 이슈 트래커를 대조해 정리한 내용입니다(확인일 2026년 9월 5일).

계측을 누가 만드느냐에서 두 경로가 갈립니다
왜 이번 분기에 정해야 하나요
미루기 어려운 날짜가 겹쳐 있습니다.
| 세대 | 무상(OSS) 지원 종료 | 비고 |
|---|---|---|
| Spring Boot 3.5.x | 2026-06-30 (종료) | 마지막 릴리스 3.5.16 (2026-06-25) |
| Spring Boot 4.0.x | 2026-12-31 | 상용 2027-12-31 |
| Spring Boot 4.1.x | 2027-07-31 | 상용 2028-07-31 |
| Spring Boot 4.2.x | 2027-12-31 | 초기 릴리스 2026-11-30 예정 |
3.5는 3.x의 마지막 세대라 6월 30일부로 3.x 전 라인이 무상 보안 패치 밖입니다. 3.5에 붙은 2032년 6월 30일은 유료 계약이 있을 때만 해당되죠.
Micrometer도 같은 주기입니다. 1.15.x는 2026년 6월에 OSS 지원이 끝나 상용 구독(2032년 6월)만 남았고, 무상 패치를 받는 라인은 1.17.x(2027년 7월까지)와 1.16.x(2026년 12월까지) 둘뿐입니다.
Spring Boot 4.1 트레이싱 문서는 OpenTelemetry With Zipkin을 deprecated로 표시하고 "The auto-configuration for it will be removed in Spring Boot 4.2."라고 못 박았습니다. 4.2.0이 2026년 11월 30일 예정이니 남은 시간이 석 달이 안 되죠.
목표는 4.0이 아니라 4.1입니다. 4.0으로 올리면 12월 31일에 같은 작업을 다시 하게 되거든요. 이 기준은 3의 무료 지원이 6월에 끝났습니다에 정리해 두었습니다.
Spring Boot 4가 Micrometer를 버렸다는 오해부터
"Boot 4로 가면 Micrometer 대신 OpenTelemetry를 쓴다"는 말이 도는데, spring-boot-starter-opentelemetry 4.1.1의 POM을 열면 반대입니다.
io.micrometer:micrometer-registry-otlp:1.17.1
io.micrometer:micrometer-tracing-bridge-otel:1.7.1
io.opentelemetry:opentelemetry-exporter-otlp:1.62.0
Micrometer가 여전히 한가운데 있습니다. 그리고 Micrometer 2.x는 존재하지 않습니다. 최신 안정판이 1.17.1(2026-08-20)이고 다음 라인도 1.18.0-M1이라 "2.0 나오면 그때 정리하자"는 계획은 성립하지 않죠.
그렇다고 Spring Boot 4가 OTel SDK를 안 쓰는 것도 아닙니다. spring-boot-opentelemetry 모듈의 OpenTelemetrySdkAutoConfiguration이 OpenTelemetrySdk 빈을 직접 만듭니다. 갈리는 건 SDK 유무가 아니라 계측의 출처입니다. 에이전트는 바이트코드를 고쳐 라이브러리를 계측하고, Spring 공식 경로는 Micrometer Observation을 브리지로 OTel SDK에 이어 붙이죠.
레퍼런스는 에이전트와 OTel 스타터를 "supported by the OTel community"로 묶고 컨벤션도 OTel 라이브러리 쪽을 쓴다고 적은 뒤, Spring 팀 공식 경로를 이렇게 구분합니다.
"This documentation describes OpenTelemetry as officially supported by the Spring team, using Micrometer and the OTLP exporter; the metrics and traces use the semantic conventions described in the Spring projects documentation."
지원 주체와 시맨틱 컨벤션이 한 묶음으로 갈립니다. 뒤에 나올 대시보드 문제의 원인이기도 하죠.
둘 다 켜면 왜 알아서 합쳐지지 않나요
에이전트는 자기가 GlobalOpenTelemetry를 설정합니다. OTel 문서 표현이 "The Java agent is a special case where GlobalOpenTelemetry is set by the agent."죠. Boot 4는 그것과 별개로 자기 SDK 빈을 만듭니다. SDK가 둘, 익스포터도 둘이죠.
"Spring Boot가 에이전트의 global 인스턴스를 재사용하게 해달라"는 요청은 실제로 있었습니다. spring-boot 이슈 #46230(작성자 quaff, 2025년 6월 30일 등록)이 2025년 7월 2일 status: declined로 닫혔죠. 거절 사유가 중요합니다. Brian Clozel은 "this change will cause duplicate metrics and will disrupt OTel Java agent users."라고 했고, 이슈를 닫은 Andy Wilkinson은 "We don't want users to be able to opt into having duplicate metrics."라고 적었습니다.
팀이 중복을 의도한 게 아니라, 중복이 싫어서 재사용 옵션을 안 만든 쪽입니다. 그래서 Boot는 자기 SDK를 따로 유지하고, 두 스택을 동시에 켜는 판단은 사용자 몫으로 남았죠. 방치하면 그대로 두 줄이 나갑니다.
에이전트에 중복 억제 기능이 있으니 괜찮다고 보실 수도 있는데, 여기도 오해입니다. otel.instrumentation.experimental.span-suppression-strategy(semconv 기본 / span-kind / none)의 사례로 문서가 드는 건 Tomcat과 Servlet, Reactor Netty와 Netty, AWS SDK 내부 HTTP 클라이언트예요. 전부 에이전트가 계측한 라이브러리끼리의 중첩이고, 앱이 ObservationRegistry로 만든 스팬까지 걸러 준다는 서술은 없습니다.
환경변수 하나가 앱 익스포터를 조용히 켭니다
Spring Boot 4.1은 OTel SDK 환경변수를 자기 프로퍼티로 매핑합니다.
| 환경변수 | 매핑되는 Spring 프로퍼티 |
|---|---|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
management.opentelemetry.tracing.export.otlp.endpoint |
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
management.otlp.metrics.export.url |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT |
management.opentelemetry.logging.export.otlp.endpoint |
OTEL_EXPORTER_OTLP_ENDPOINT 하나만 걸어 두면 세 신호 모두의 fallback이 되고 v1/traces·v1/metrics·v1/logs 경로가 자동으로 붙습니다. management.opentelemetry.map-environment-variables=false로 끌 수 있지만 기본값이 활성이죠. 3.5에는 이 매핑이 없었으니 버전을 올리는 순간 없던 익스포터가 생겨나는 셈입니다.

Spring Boot 4.1.1 레퍼런스의 OTEL_* 환경변수 매핑 표 (확인일 2026-09-05)
반대로 안심해도 되는 쪽도 있습니다. 에이전트의 Micrometer 브리지 계측(micrometer-1.5)과 spring-boot-actuator-autoconfigure-2.0 계측은 둘 다 기본 비활성입니다. 계측 목록 파일에 disabled_by_default: true로 적혀 있고, 이유도 "다른 계측이 이미 수집한 메트릭과 겹칠 수 있어서"라고 명시돼 있죠. 실무의 중복은 대개 스팬 쪽입니다.
대시보드는 왜 통째로 깨지나요
두 경로는 지표 이름부터 다릅니다.
| Spring / Micrometer | OTel 시맨틱 컨벤션 | |
|---|---|---|
| 서버 지표 | http.server.requests |
http.server.request.duration |
| 경로 | uri |
http.route |
| 상태 코드 | status |
http.response.status_code |
| 메서드 | method |
http.request.method |
| 그 외 | outcome, error |
url.scheme, error.type |
이름이 바뀌면 PromQL이, 태그가 바뀌면 알림 룰의 라벨 매처가 안 맞습니다. 에러로 터지지 않고 조용히 무음이 된다는 게 문제죠. 알림이 안 울리는 형태라 발견이 늦습니다.

OTel 시맨틱 컨벤션의 http.server.request.duration 속성 표 (확인일 2026-09-05)
바꾸는 길은 있습니다. Spring Framework 7이 OpenTelemetryServerRequestObservationConvention(컨벤션 v1.36.0 준수)을 제공하는데, 프로퍼티가 아니라 ObservationConvention 빈 등록으로 바꾸고 기본값은 여전히 http.server.requests 쪽입니다. 강제되는 변경이 아니니 대시보드 개편 사이클에 맞춰 하세요.
3.5에서 쓰던 프로퍼티 키는 그대로 안 먹습니다
| 용도 | Spring Boot 3.5 | Spring Boot 4.x |
|---|---|---|
| OTLP 트레이스 | management.otlp.tracing.* |
management.opentelemetry.tracing.export.otlp.* |
| 내보내기 스위치 | management.tracing.enabled |
management.tracing.export.enabled |
| OTLP 메트릭 | management.otlp.metrics.export.* |
그대로 |
트레이스만 접두사가 옮겨 가고 메트릭은 제자리라는 비대칭이 실수를 부릅니다. management.opentelemetry.metrics...를 찾다가 없어서 헤매기 쉽죠.
애너테이션도 @ConditionalOnEnabledTracing이 @ConditionalOnEnabledTracingExport로 바뀌었습니다. 반면 의존성은 단순해져서, 3.5에서 세 개를 나열하던 것이 4.x에서는 spring-boot-starter-opentelemetry 하나로 끝납니다. 다만 이 스타터가 actuator 스타터를 끌어오지 않으니 엔드포인트가 필요하면 따로 추가하세요.
중복을 끄는 설정은 어느 쪽에 넣나요
에이전트를 남기고 앱 내보내기를 끄는 경우 — application.yml에 넣습니다.
management:
opentelemetry:
enabled: false # 앱 SDK를 no-op으로 (OTEL_SDK_DISABLED와 동등)
map-environment-variables: false # OTEL_* 를 흡수하지 않음
tracing:
export:
enabled: false # 트레이싱 내보내기만 끌 때
앱 쪽을 남기고 에이전트 계측을 줄이는 경우 — 이건 yml이 아니라 JVM 인자나 환경변수로만 됩니다. OTel 문서가 properties·yml 설정은 스타터에만 통하고 에이전트에는 안 통한다고 명시해 뒀거든요.
-Dotel.javaagent.enabled=false # 에이전트 전체 끄기
-Dotel.instrumentation.common.default-enabled=false # 모든 계측 끄기
-Dotel.instrumentation.spring-webmvc.enabled=false # 개별 계측만 끄기
-Dotel.javaagent.exclude-classes=... # 클래스 단위 제외
환경변수로는 OTEL_JAVAAGENT_ENABLED, OTEL_INSTRUMENTATION_SPRING_WEBMVC_ENABLED 형태가 됩니다. 샘플링도 같이 보세요. 기본이 10%라 management.tracing.sampling.probability로 조정하고, 샘플러는 management.opentelemetry.tracing.sampler로 고릅니다(기본 parent-based-trace-id-ratio).

네 가지 질문이면 갈 길이 갈립니다
우리 팀은 지금 옮겨야 하나요
지금 정리해야 하는 팀입니다.
- 3.5 이하에 남아 있는 팀. 어차피 키와 의존성을 손봐야 하니 버전 업과 관측성 정리를 한 번에 하는 게 쌉니다. 목표는 4.1.x.
- 에이전트를 붙인 채 Boot 4로 올릴 팀. 두 SDK가 각각 내보내므로 올리기 전에 한쪽을 정하세요.
http.server.requests대시보드·알림에 묶여 있는 팀.spring-boot-starter-opentelemetry로 가면 컨벤션이 유지된 채 OTLP로 나갑니다. 대시보드를 안 건드리고 OTLP만 얻는 경로죠.- GraalVM 네이티브 이미지 팀. OTel 문서가 "Spring Boot Native image applications for which the OpenTelemetry Java agent does not work"라고 적어 뒀습니다.
OpenTelemetry With Zipkin을 쓰는 팀. 4.2에서 제거됩니다.
기다려도 되는 팀입니다.
- 4.0·4.1에서 이미 한쪽으로 통일돼 중복이 안 보이는 팀. 4.1.x는 2027년 7월 31일까지 여유가 있고, 컨벤션 전환은 재작성 비용만 만듭니다.
- Datadog·New Relic에 Micrometer 레지스트리로 붙어 있는 팀. 벤더 레지스트리는 그대로 살아 있으니 OTLP는 백엔드 요구가 생겼을 때 가도 됩니다.
- 에이전트만 쓰면서 Actuator 메트릭을 OTel로 안 보내는 팀. 계측 둘 다 기본 비활성이라 메트릭 중복은 안 납니다. 스팬만 점검하세요.
하면 안 되는 것들입니다.
- 두 스택을 켠 채 "OTLP끼리 알아서 합쳐지겠지"로 방치하기. 수집기 비용이 두 배가 되고 트레이스 구조가 왜곡됩니다.
- 매니페스트에
OTEL_EXPORTER_OTLP_ENDPOINT를 남긴 채 4.1로 올리기. - 이중 기록 기간 없이 컨벤션 바꾸기. 알림이 조용히 안 울립니다.
- "Micrometer 2.0을 기다리자"로 미루기. 그런 릴리스는 없습니다.
- 잘 돌던 에이전트를 "alpha라 위험하다"만 보고 급히 걷어내기. 에이전트는 여전히 OTel이 Spring Boot의 기본 선택으로 문서화한 방식이고 커버리지도 넓습니다.
덧붙이면 "OTel 스타터는 아직 Boot 4를 지원 안 한다"는 절반만 맞습니다. 구현은 들어갔습니다(#15459가 2025년 12월 8일, #14906이 2026년 1월 30일 종료). 다만 opentelemetry.io의 시작하기 문서는 여전히 "Spring Boot 2.6+ and 3.1+"로만 적혀 있어 문서상 선언이 없죠.
옮기기 전 확인 목록
[ ] 목표를 4.1.x 로 잡았는가 (4.0 은 2026-12-31 에 OSS 종료)
[ ] 배포 매니페스트에 OTEL_EXPORTER_OTLP_* 환경변수가 있는가
[ ] 있다면 map-environment-variables 를 끌지 앱 익스포터를 끌지 정했는가
[ ] 에이전트와 spring-boot-starter-opentelemetry 가 동시에 있는가
[ ] 트레이스 키를 management.opentelemetry.tracing.export.otlp.* 로 바꿨는가
[ ] management.tracing.enabled 를 management.tracing.export.enabled 로 바꿨는가
[ ] 대시보드·알림이 http.server.requests 와 uri/status 태그에 묶여 있는가
[ ] GraalVM 네이티브 이미지로 배포하는가 (그렇다면 에이전트 불가)
[ ] OpenTelemetry With Zipkin 을 쓰는가 (4.2 에서 제거)
[ ] 런타임 Java 17 이상이고 샘플링 확률(기본 10%)을 정했는가
다음에 볼 글
같은 이관 흐름의 앞 단계는 3의 무료 지원이 6월에 끝났습니다에 있습니다. 목표 세대를 4.0으로 잡을지 4.1로 잡을지가 이 글의 전제죠. 로그까지 OTLP로 보낼 계획이라면 Logback과 Log4j2 중 무엇을 남길까와 함께 보시면 메트릭·트레이스·로그 세 신호의 경로를 한 번에 정할 수 있습니다.
관측성 스택은 한번 정하면 대시보드와 알림까지 딸려 오는 결정이라 되돌리기가 번거롭습니다. 버전을 올리는 김에 한쪽으로 정리해 두시면 나중이 편해집니다.
이 글은 공식 문서와 릴리스 노트, Maven Central의 실제 POM, 이슈 트래커를 기준으로 정리했습니다(확인일 2026년 9월 5일). 4.2.0이 11월 30일 예정이라 Zipkin 관련 서술은 그 이후 다시 확인해 주세요. 잘못된 내용이나 보충이 필요한 부분이 있으면 댓글로 알려 주시면 반영하겠습니다.
참고한 자료
- Spring Boot 4.1.1 레퍼런스 Observability — 지원 주체 구분, OTEL_* 환경변수 매핑 (확인일 2026-09-05)
- Spring Boot 4.1.1 및 3.5 레퍼런스 Tracing — 키 변화, 샘플러, Zipkin deprecated (확인일 2026-09-05)
- Spring Boot 4.0·4.1 릴리스 노트와 4.0 마이그레이션 가이드 (확인일 2026-09-05)
- Maven Central spring-boot-starter-opentelemetry 4.1.1 POM (확인일 2026-09-05)
- spring-boot 이슈 #46230 — 에이전트 global OpenTelemetry 재사용 요청 declined (확인일 2026-09-05)
- Spring Framework 7 레퍼런스 Observability — 두 갈래 컨벤션 (확인일 2026-09-05)
- OpenTelemetry 시맨틱 컨벤션 HTTP metrics (확인일 2026-09-05)
- OpenTelemetry Java 에이전트·스타터 문서와 계측 목록 — 비활성화 키, 스팬 억제 (확인일 2026-09-05)
- Micrometer 지원 정책 및 GitHub 릴리스 목록 (확인일 2026-09-05)
- Spring 공식 지원 일정 API — 세대별 OSS·상용 지원 종료일 (확인일 2026-09-05)
'백엔드 > Spring' 카테고리의 다른 글
| [Spring] 가상 스레드가 왔는데 WebFlux를 계속 써야 하나요 — JDK 24가 경계를 다시 그었습니다 (0) | 2026.09.13 |
|---|---|
| [Spring] Boot가 고정한 Logback은 아직 패치 전입니다 — Log4j2로 갈아탈 이유가 될까요 (0) | 2026.09.11 |
| [Spring] Boot 4로 올리면 JUnit 6이 딸려옵니다 — 그런데 그 6.0.3은 보안 지원 밖입니다 (0) | 2026.09.10 |
| [Spring] Boot 4로 올리면 Jackson이 3으로 갈립니다 — 지금 옮길 팀과 springdoc 때문에 못 옮기는 팀 (0) | 2026.09.09 |
| [Spring] 로컬 DB를 뭘로 띄울까 — Docker Compose와 Testcontainers 나눠 쓰기 (0) | 2026.09.08 |
| [MySQL] 8.0을 그냥 두면 RDS 요금이 붙습니다 — 8.4와 9.7 사이에서 고르기 (0) | 2026.09.02 |
| [Spring] Security 7로 올리면 컴파일부터 깨집니다 — 6.x에서 미리 지울 코드 (0) | 2026.09.01 |