Files
llm-wiki/raw/branch-notes/feature-distributed-tracing-contract.md

46 KiB

title, source_type, status, branch, parent_branch, governing_docs, related_projects, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch governing_docs related_projects tags created target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-distributed-tracing-contract branch-note raw feature-distributed-tracing-contract
wiki/projects/ca-tmpl/observability-log-metric-trace-runbook
ca-skeleton
branch
ca-skeleton
tracing
observability
2026-05-22 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-027 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-027
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-ERROR-ENVELOPE-001@1
1 7b6718a5912304437453bc70ffbbaba27bced681a98e7ed246687a83b38f67fa

branch: feature-distributed-tracing-contract

Layer: raw/branch-notes/ — HTTP, async, messaging, outbound 경계에서 trace context가 끊기지 않도록 distributed tracing 기준을 정의합니다.

부모 (필수)

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: request·trace correlation contract test가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-ERROR-ENVELOPE-001@1 foundation이 envelope schema와 error.category enum의 단일 owner다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

structured log만으로는 운영 장애의 흐름을 끝까지 추적하기 어렵습니다. traceId/requestId/correlationId/spanId의 의미와 전파 경계를 고정해서 어떤 adapter를 붙여도 같은 방식으로 원인을 추적할 수 있게 합니다.

  • 이슈:
  • PR:

범위

포함 범위

  • traceId/requestId/correlationId/spanId 의미 정의.
  • inbound HTTP, outbound HTTP, async job, message publish/consume 전파 기준.
  • MDC와 trace context 동기화 기준.
  • sampling/exporter/env 설정 기준.
  • baggage 금지 정보 기준.

제외 범위

  • 특정 APM vendor 종속 설정.
  • business event tracing.
  • provider별 dashboard 구현.

근거 (필수, 최소 1개+)

본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.

Source 정당화하는 결정
raw/official-docs/tracing-w3c-trace-context-spec.md W3C Recommendation, OTel default propagator
raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md head-based + force-sample boost가 SDK 기본 기능만으로 구현 가능
raw/official-docs/tracing-b3-propagation-zipkin-spec.md legacy, 64-bit mode는 W3C 비호환
raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry.md auto-instrumentation 광범위하나 vendor lock-in
raw/official-docs/baggage-otel-baggage-api-spec D2: SDK-level escape hatch (untrusted process 로의 모든 baggage entry 제거 MUST); D8: spec 에 allowlist 정의 없음 — restriction 은 Propagator/application 위임 (내부 governance 정책 확인)
raw/official-docs/tracing-micrometer-observation-introduction D12 — Observation#error(exception) 호출이 error lifecycle event를 발생시킨다는 API 계약 (MICR-OBS-C1, MICR-OBS-C3)
raw/official-docs/baggage-w3c-baggage-spec D2 — baggage 에 PII/기밀 정보 금지 + trust-boundary 제거 의무 (W3C-BAG-C1). D8 — allowlist 정책은 spec 에 없는 application 결정 (W3C-BAG-C2, W3C-BAG-C3).
raw/official-docs/tracing-otel-trace-api-spec D4 — SDK noop 시 all-zero TraceId (OTEL-TAPI-C2/C3), "disabled but meaningful traceId" = SDK-on + exporter-off 로만 가능; D12 — RecordException 은 Event 기록만 (OTEL-TAPI-C4), status=ERROR 는 별도 SetStatus 호출 필요
raw/official-docs/tracing-spring-boot-3-actuator-tracing-reference D1 — Spring Boot Actuator 가 Micrometer Tracing (OTel+OTLP 와 Brave+Zipkin 두 tracer 공식 지원) 을 auto-configure 함; vendor-neutral OTLP 채택의 공식 근거 (SB-TRAC-C1 ~ C4)

외부 근거 / 대안 조사 (2026-05-22 — Group G-A: Distributed tracing)

채택 결정 + 뒷받침

검토 대안 + source

비교 핵심 1줄

W3C + OTel + head-based는 vendor-neutral + SDK 기본 기능만으로 구현 가능 + Spring Boot 3 + Micrometer 통합이 강점, tail-based는 trace 완성도, vendor APM은 빠른 시작 + vendor lock-in trade-off.

TODO

TODO drained — 결정은 error.category enum 표 / MDC Key Standard 표 / error.details JSON shape 참조

Work Item Contract

각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.

field required rule
Decision yes 구현자가 선택해야 하는 기본값
Allowed yes 허용되는 예외와 조건
Forbidden yes 절대 금지되는 구현/문서 상태
Required registry update conditional error/env/header/log/metric/capability 변경 시 필수
Required contract test yes 계약 위반 시 실패해야 하는 테스트
Failure condition yes review/build에서 실패로 판정할 상태
Canonical extraction target yes wiki/projects 승급 위치

진행 중 메모

  • 2026-06-14 (/branch-spec 게이트): 6개 UNSUPPORTED_DECISION 중 4건을 자동조사로 해소 — D1(SB-TRAC-C1~C4), D2(W3C-BAG-C1 + OTEL-BAG-C3), D4(OTEL-TAPI-C2/C3 — 부분 해소 + 핵심 정정), D8(W3C-BAG-C2/C3 + OTEL-BAG-C4 — policy 재framing), D12(MICR-OBS-C1/C3 + OTEL-TAPI-C4). 잔여 UNSUPPORTED_DECISION 은 D3·D9 (내부 운영 정책 — 외부 표준 인용 대상 아님). official-doc raw 5개 신규 등록(spring-boot actuator tracing / micrometer observation / otel trace api / w3c baggage / otel baggage api).
  • 2026-06-14 ground-truth 재검증(ca-tmpl @HEAD): env/metric/header registry row 의 owner_branch 가 본 branch 임을 확인(OTEL_EXPORTER_OTLP_ENDPOINT·APP_TRACING_ENABLED·APP_TRACING_SAMPLE_RATE·tracing.sampling.rate·traceparent·tracestate). 단 Micrometer Tracing config 클래스는 src/ 에 미존재 — 계약(registry)은 등록됐으나 구현은 planned. async context 전파 코드(AsyncContextTaskDecorator)에서 carrier 표 drift 발견 → §Audit & Findings 참조.
  • 2026-06-14 Slice 1 (Scope C — contract mechanics) 구현 완료 (actually-implemented, locally-verified): 3개 pure Java stdlib 타입을 dev.caskeleton.shared.tracing 패키지 (src/shared-contract) 에 신규 생성. TDD red→green 확인 (58 tests, 0 failures). Spring/OTel/Jackson import 없음 확인.
  • 2026-06-14 전체 구현 완료 (Scope C — 계약 메커니즘; tracer 런타임은 fork-activated seam) (actually-implemented, locally-verified). 사용자 결정: OTel/Micrometer/Actuator deps 미추가, 계약 메커니즘만 코드+테스트로 실현. 슬라이스:
    • Slice 1 (shared-contract): TraceParent(W3C parse/validate/render, all-zero 거부 — D5/D7), BaggageAllowlist(allow=tenant_id/request_id, header filter — D2/D8), SpanErrorRecorder+NOOP(D12 seam). 58 tests.
    • Slice 2 (adapter-web): RequestLoggingFilter 가 inbound traceparent accept/생성(부재·무효 시 32hex/16hex root) → MDC trace_id/span_idResponseMetaFactory meta.traceId 항상 non-null (D4 disabled-fallback = request_id mirror 제거하고 실 W3C id 로 교체). GlobalExceptionHandlerSpanErrorRecorder.recordException(throwable, errorCode) 호출(catch-all + persistence + dependency 경로). @Autowired ObjectProvider<SpanErrorRecorder> self-default → 모든 컨텍스트(@WebMvcTest 슬라이스 포함)에서 bean 없이 wiring, fork 가 bean 기여 시 override.
    • Slice 3 (adapter-outbound): TraceContextPropagationInterceptor 가 MDC → outbound traceparent/X-Request-Id/X-Correlation-Id/allowlisted baggage 주입, OutboundHttpClient.baseline(...) buffered+streaming 양쪽 배선. MDC 키는 mdc-keys.yaml SSOT 리터럴(adapter-web 의존 금지). sampled=00(seam — tracer 가 실 sampled 소유).
    • Slice 4+5 (app-bootstrap): .env+application.yml 3키 배선(verifyEnvKeys 통과), TracingProperties(@Validated, float_between_0_and_1 + url_or_empty 시작시 검증), TracingSampleRateResolver(prod .01/staging .1/dev·local 1.0 — D6), TracingSamplingRateGauge(tracing.sampling.rate, profile tag, ObjectProvider no-op), 6개 required_test 전부 + §테스트계약 5종.
    • 검증: ./gradlew check = BUILD SUCCESSFUL, 1091/1091 tests, 0 failures (verifyCleanArchitectureDependencies + verifyEnvKeys + ArchUnit CleanArchitectureTest 포함). 리뷰 체인: architect-sentinel PASS, spec-reviewer 19/19 요구사항 MET, quality-reviewer 0 Critical(3 Important·4 Minor 반영).
    • 여전히 planned(과장 금지): 실 OTel SDK/Micrometer Tracing/OTLP exporter 런타임, 실 head/force-sample sampler, Observation scope async 재establish, B3 edge translation, tracestate 한계 모니터링. 이들은 fork-activated seam — 면접/포트폴리오에 "OTel 로 추적을 구현/운영했다" 금지. 실현된 것은 계약 메커니즘(전파 형식·disabled fallback·baggage allowlist·span-error seam·sampling-rate gauge·env/header/metric 배선·계약 테스트).

결정 사항

  • 2026-05-22: Micrometer Tracing + OpenTelemetry exporter를 기본 기준으로 둠.
  • 2026-05-22: baggage에는 PII, token, user raw identifier, request body derived value를 넣지 않음.
  • 2026-05-22: trace/request/correlation ID 의미의 SSOT는 feature-operational-error-observability-foundation; 이 branch는 propagation mechanics만 소유.
  • 2026-05-22: tracing disabled profile에서도 envelope meta.traceId와 log traceId는 유지. exporter/sampling만 비활성화 가능.
  • 2026-05-22: propagation header는 W3C traceparent default.
  • 2026-05-22: trace sampling rate default = prod 1%, staging 10%, dev/local 100%. force-sample = error response, slow request (p99 초과), retry exhausted.
  • 2026-05-22: propagation format = W3C traceparent + tracestate only. B3 propagation은 forbidden (외부 통합 시 edge에서 변환).
  • 2026-05-22: baggage allowlist = tenant_id, request_id 만 허용. 그 외 baggage 사용 forbidden.
  • 2026-05-22: trace sampling rate(prod 1%) < log sampling rate(prod 10%)는 의도된 분리. log-management branch와 정합.
  • 2026-05-22: identifier 표기는 layer별 분리. MDC/log field는 snake_case (request_id/trace_id/correlation_id), JSON response envelope는 camelCase (meta.requestId/meta.traceId/meta.correlationId), HTTP header는 kebab-case (X-Request-Id/X-Correlation-Id). foundation MDC SSOT와 envelope SSOT의 mapping은 본 branch의 Propagation Defaults 표가 보장.

결정-근거 매핑

각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. Decision ID 는 이 branch-note 안에서 안정적으로 유지한다.

Decision ID Decision Supporting Claims Evidence Strength Open Risk
D1 Micrometer Tracing + OpenTelemetry exporter 기본 채택 raw/official-docs/tracing-spring-boot-3-actuator-tracing-reference#SB-TRAC-C1 (Actuator auto-configures Micrometer Tracing facade), #SB-TRAC-C2 (OTel+OTLP 와 Brave+Zipkin 두 tracer 모두 공식 지원), #SB-TRAC-C3 (두 조합 모두 dedicated starters 존재), #SB-TRAC-C4 (spring-boot-starter-opentelemetry 공식 starter) official-vendor-doc (Spring Boot 공식 reference — 2026-06-14 fetch 검증) "OTel 이 유일한 default" 는 증명 안 됨 — Spring Boot 는 OTel+OTLP 와 Brave+Zipkin 둘 다 지원. D1 의 framing 은 "두 tracer 중 OTel+OTLP 를 채택" 임을 명시할 것. vendor-neutral OTLP export 의 Spring Boot 공식 지원 근거로만 사용
D2 baggage 에 PII/token/user raw identifier/body 금지 raw/official-docs/baggage-w3c-baggage-spec#W3C-BAG-C1 (baggage may carry sensitive information — trust-boundary 제거 의무, baggage spec 직접 근거) + raw/official-docs/baggage-otel-baggage-api-spec#OTEL-BAG-C3 (untrusted process 로의 모든 baggage entry 전송 방지 MUST — SDK-level escape hatch) official-standard + official-vendor-doc (W3C Candidate Recommendation Snapshot 2024-05-30 + OTel stable spec) W3C Baggage spec §4.1 이 기밀/소유 정보 금지 + trust-boundary 제거 의무 직접 규정. OTel Baggage spec 은 untrusted process 전송 방지 MUST. 구체적 금지 항목(PII/token 형태)은 application 정책. 이전 인용 tracing-w3c-trace-context-spec#W3C-TC-C5(tracestate 대상)는 baggage 직접 근거가 아니었으므로 W3C-BAG-C1 로 교체.
D3 trace/request/correlation ID 의미 SSOT = feature-operational-error-observability-foundation consume UNSUPPORTED_DECISION (SSOT 분할은 내부 운영 정책) N/A branch ownership 분할은 외부 표준 인용 대상 아님
D4 tracing disabled profile 에서도 envelope meta.traceId + log traceId 유지, exporter/sampling 만 비활성화 raw/official-docs/tracing-otel-trace-api-spec#OTEL-TAPI-C1 (SDK 부재 시 Trace API = no-op), #OTEL-TAPI-C2 (noop + 부모 Span 없으면 SpanContext = all-zero Trace/Span IDs), #OTEL-TAPI-C3 (noop 상태 새 SpanContext 미생성) official-standard (OTel Trace API spec — 2026-06-14 fetch) 핵심 정정: SDK 자체를 noop 으로 두면 traceId=all-zeros(의미 없음). 따라서 "disabled but keep meta.traceId" = SDK-on + exporter-off(sampling.probability=0)로만 구현 가능. UNSUPPORTED_IMPL_DECISION: 정확한 Spring property 조합(exporter bean exclusion + sampling 0) 또는 app-generated UUID fallback 은 ca-tmpl 운영 결정 — 단일 source 없음
D5 propagation header = W3C traceparent default raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C1, raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C2, raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C3 official-standard (W3C TR — HTTP header + 4-field format + canonical example, 2026-05-27 verified verbatim) C4 (tracestate name/value vs key/value 표현 차이) 는 needs-confirmation 유지
D6 trace sampling rate default = prod 1%, staging 10%, dev/local 100% + force-sample (error/slow/retry-exhausted) raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md#OTEL-SAMP-C1, raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md#OTEL-SAMP-C3, raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md#OTEL-SAMP-C4 official-vendor-doc (head sampling 정의/장점/단점) OTEL-SAMP-C3 Usage Boundary: 효율의 정량값 없음. 1%/10%/100% 비율 자체는 ca-tmpl 운영 가정 (OTEL-SAMP-C7 같은 권장값 spec 부재). force-sample 메커니즘은 OTEL-SAMP-C4 Does not prove 에 따르면 별도 SDK 구현 필요
D7 propagation format = W3C traceparent + tracestate only. B3 propagation forbidden raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C1, raw/official-docs/tracing-b3-propagation-zipkin-spec.md#B3-C6 (B3 trace-id 64-bit/128-bit 양쪽 허용 — W3C 128-bit only 와 호환 한계) official-standard (양쪽 spec) B3-C6 Does not prove: W3C 호환 결론은 본 인용으로 직접 증명되지 않음. ca-tmpl 의 "forbidden" 결정은 W3C 채택 + 운영 단순화 정책
D8 baggage allowlist = tenant_id, request_id 만 허용 raw/official-docs/baggage-w3c-baggage-spec#W3C-BAG-C2 + raw/official-docs/baggage-w3c-baggage-spec#W3C-BAG-C3 (W3C Baggage spec 은 64 list-members / 8192 bytes wire 제약만 정의, allowlist 메커니즘 없음) + raw/official-docs/baggage-otel-baggage-api-spec#OTEL-BAG-C4 (OTel spec 도 allowlist 미정의 — restriction 은 Propagator/application 위임) official-standard + official-vendor-doc (W3C Candidate Recommendation Snapshot 2024-05-30 + OTel stable spec) 두 spec 모두 wire-format 제약만 정의하고 어떤 key 를 허용/금지할지 규정하지 않음. tenant_id/request_id 구체 key 선택은 ca-tmpl 운영 정책 — UNSUPPORTED_IMPL_DECISION 유지. W3C-BAG-C2/C3 는 "spec 에 allowlist 없음" 을 W3C 층에서 추가 확인.
D9 identifier 표기 layer 별 분리 (MDC snake_case / envelope camelCase / HTTP header kebab-case) UNSUPPORTED_DECISION (layer 별 표기 컨벤션은 내부 결정 — 외부 raw 표준 없음). owner = raw/branch-notes/feature-operational-error-observability-foundation D19 (mdc-keys.yaml / headers.yaml SSOT) — 본 row 는 그 결정의 consume pointer, 재진술 아님 (§Audit RESTATED_FOREIGN_DECISION 참조) N/A foundation branch 의 MDC Key Standard 표 와 envelope SSOT 정합으로만 정당화
D10 Datadog APM / AWS X-Ray native tracer 거부 (vendor lock-in) raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry.md#DD-OTEL-C1, raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry.md#DD-OTEL-C2, raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry.md#DD-OTEL-C3 company-case-study (Datadog 의 OTel vendor-neutrality 인정 + 자체 dd-trace-java 자동 계측) company-tech-blog 는 official best practice 아님. ca-tmpl 의 "out-of-scope" 결정은 vendor 평가 trade-off 로만 표현. DD-OTEL-C4/C5/C6needs-confirmation — verbatim 미확인
D11 Tail-based / Adaptive sampling 거부 (collector overhead) raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md#OTEL-SAMP-C5 (tail sampling = trace 의 모든/대부분 span 고려) official-vendor-doc OTEL-SAMP-C5 Usage Boundary: decision_wait window 길이 / missing span 처리 의 trade-off 본 인용 범위 밖. ca-tmpl 의 운영 비용 평가는 내부 판단
D12 span 예외 발생 시 Observation.error(throwable) + error.code 부착 + sampled span 만 stack trace attach raw/official-docs/tracing-micrometer-observation-introduction#MICR-OBS-C1 (Observation#error(exception) 호출 → error lifecycle event), #MICR-OBS-C3 (ObservationHandler 가 lifecycle event 로 span 생성), raw/official-docs/tracing-otel-trace-api-spec#OTEL-TAPI-C4 (RecordException = AddEvent 변형, status 변경 없음 → SetStatus 별도 호출) official-vendor-doc (Micrometer Observation reference + OTel Trace API spec) Observation.error()OtelSpan.error()recordException() + setStatus(ERROR) 체인은 소스코드 검증(공식 docs 산문 부재). error.code 는 ca-tmpl registry attribute 명 — OTel semantic-convention 표준 아님(표준 = exception.type/exception.message/exception.stacktrace). UNSUPPORTED_IMPL_DECISION: "sampled span 만 stack trace / unsampled = error.code only" 정책은 ca-tmpl 운영 결정 — source 없음. 코드 미구현(plannedsrc/ 에 Observation error handler 부재)

구현 가이드

결정 (Decisions) 이 "무엇 을 할 것인가" 라면, 본 §는 "어디에 어떻게 구현될 것인가" 의 사전 명세다. 아래 sub-section 은 모두 본 branch 의 결정 + 근거에서 도출되며, 각 표는 Trace 헤더로 Decision ID + Supporting Claim ID 를 reference 한다.

코드 구현 상태: 본 branch 의 결정은 registry(env/metric/header)에는 등록됐으나, Micrometer Tracing config / Observation error handler 클래스는 ca-tmpl src/아직 없음 (planned). 아래 명세는 구현될 때의 사전 계약 이다 (§Audit & Findings IMPL_STATUS 참조).

1. Boundary Propagation Defaults

Trace: D5 (traceparent default — W3C-TC-C1/C2/C3) + D7 (W3C only, B3 forbidden) + D4 (disabled → exporter-off — OTEL-TAPI-C2/C3).

  • UNSUPPORTED_IMPL_DECISION: disabled tracing → generated opaque trace id 행 — OTel SDK 를 noop 으로 두면 traceId = all-zeros(OTEL-TAPI-C2/C3)이므로 "meaningful opaque id 유지" 는 SDK-on + exporter-off(sampling 0) 또는 app-generated UUID 로만 가능. 정확한 메커니즘은 ca-tmpl 운영 결정(단일 source 없음).
boundary default
inbound HTTP accept/generate W3C trace context
outbound HTTP propagate traceparent, requestId, correlationId
async/job capture and restore context wrapper
messaging include trace context and correlationId in metadata
disabled tracing generated opaque trace id, exporter off (SDK-on + exporter-off — noop 은 all-zeros 라 사용 불가, 위 UNSUPPORTED_IMPL_DECISION)

2. Async / Messaging Carrier Keys

Trace: D5/D7 (W3C carrier — traceparent/tracestate) + §Claims To Verify (TaskDecorator / Kafka·Rabbit consumer-side auto-extract = planned).

  • UNSUPPORTED_IMPL_DECISION: Kafka traceparent (binary value) 인코딩 + Spring scheduler per-trigger 생성은 OTel instrumentation 모듈 동작 가정 — 본 branch 인용에 직접 spec 없음(planned, §Claims To Verify).
  • CARRIER_DRIFT (코드 실측): @Async TaskDecorator 행은 ca-tmpl 코드와 어긋남 — 실제 AsyncContextTaskDecoratorplain MDC copy(MDC.getCopyOfContextMap())이며 Micrometer Observation scope 를 worker thread 에 재establish 하지 않는다(io.micrometer:context-propagation + ContextPropagatingTaskDecorator 는 documented future enhancement, 미구현). 또한 이 decorator 의 owner 는 raw/branch-notes/feature-background-job-async-contract / raw/branch-notes/feature-runtime-context-propagation-contract 이지 본 branch 가 아니다. §Audit & Findings 참조.
carrier key
HTTP traceparent, tracestate (W3C)
Kafka header traceparent (binary value)
RabbitMQ header traceparent
@Async TaskDecorator (실측 정정) MDC trace_id/span_id 문자열 thread-local copy via AsyncContextTaskDecorator. Observation scope 재establish 는 미구현(future enhancement)
Spring scheduler traceparent generated per trigger

3. Span Error Recording

Trace: D12 — Observation#error lifecycle (MICR-OBS-C1/C3) + RecordException ≠ status 변경(OTEL-TAPI-C4, SetStatus 별도).

  • UNSUPPORTED_IMPL_DECISION: sampled span 만 stack trace attach / unsampled = error.code attribute only 는 ca-tmpl 운영 정책(source 없음). error.code 는 ca-tmpl registry attribute 명이며 OTel semantic-convention 표준 아님(표준 = exception.type/exception.message/exception.stacktrace).
  • 예외 발생 시 Observation.error(throwable) 호출 강제.
  • span attribute error.code (registry value) 부착 + status=ERROR.
  • exception stack trace는 sampled span에만 attach. unsampled span은 error.code attribute만 남기고 stack trace 부착 금지.

4. Registry anchors (env / metric / header — ca-tmpl SSOT)

Trace: D1 (exporter endpoint) + D4 (tracing enabled toggle) + D6 (sample rate + sampling metric) + D5/D7 (header). 아래 값은 ca-tmpl docs/registries/*.yaml실재 row 로, owner_branch 가 본 branch 임을 2026-06-14 확인했다.

  • IMPL_STATUS: registry row 는 등록됨(계약 존재). 이를 읽어 적용하는 Micrometer Tracing config / OTLP exporter / 커스텀 sampler 클래스는 src/미존재(planned). registry ≠ 구현 — 면접/포트폴리오에 "구현했다" 금지(§Audit IMPL_STATUS).
registry key 값 (registry 실측) required_test owner
env-keys.yaml OTEL_EXPORTER_OTLP_ENDPOINT type url, default null, public-config, restart-only, validation url_or_empty tracing-contract:exporter-endpoint-resolvable 본 branch
env-keys.yaml APP_TRACING_ENABLED boolean, default true, public-config, restart-only, boolean_strict tracing-contract:meta-traceid-when-disabled 본 branch
env-keys.yaml APP_TRACING_SAMPLE_RATE string, default "1.0", public-config, restart-only, float_between_0_and_1 tracing-contract:sample-rate-per-profile 본 branch
metrics.yaml tracing.sampling.rate gauge, tag profile(cardinality 4 — prod/staging/dev/local) contract-verification:metrics-cardinality 본 branch
headers.yaml traceparent direction both, generated_if_missing true, mdc_key trace_id, envelope meta.traceId contract-verification:trace-propagation 본 branch
headers.yaml tracestate direction both, generated_if_missing false, mdc_key null contract-verification:trace-propagation 본 branch
mdc-keys.yaml trace_id/span_id/correlation_id/request_id snake_case, http_header_mapping + envelope_field 등록 contract-verification:log-mdc-keys foundation (consume only — §엣지·실패·의존)

엣지·실패·의존

R4(깊이 게이트) 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존을 미리 열거.

  • 실패·엣지 경로:
    • disabled profile → all-zeros: OTel SDK 를 noop 으로 두면 meta.traceId = 00000000...(OTEL-TAPI-C2/C3). 기대: exporter-off + SDK-on 으로 meaningful id 유지. all-zeros 가 envelope/log 에 노출되면 실패(테스트 계약 meta.traceId 누락 항목과 같은 실패군).
    • force-sample 한계: head sampler 단독으로는 error/slow/retry-exhausted boost 불가(OTEL-SAMP-C4) → ParentBased + 커스텀 sampler 별도 구현 필요(§Claims To Verify, needs-confirmation).
    • B3 inbound (외부 시스템): 본 branch 는 B3 forbidden(D7)이나 외부 호출자가 B3 헤더를 보낼 수 있음 → edge 에서 multi-propagator(tracecontext,b3) 변환, receiver precedence(B3-C5/C6). 미구현 시 trace 단절.
    • tracestate 한계 초과: List-Members/length 제한(W3C-TC-C4, planned) 초과 시 partial drop. vendor tracestate 누적 monitoring 필요.
    • baggage trust-boundary: untrusted process 호출 전 baggage remove-all(OTEL-BAG-C3) 미적용 시 D2 위반(PII 유출).
  • 다른 계약 의존:

검증해야 할 주장

Claim Why uncertain How to verify Status
W3C traceparent 의 trace-flags LSB = sampled (01 = sampled) 비트 의미 W3C-TC-C2 Does not prove: trace-flags 의 sampled bit 의미는 spec 동일 섹션 추가 인용 필요 spec 재 fetch + ca-tmpl 의 sampling 결정이 01 flag 로 downstream 에 전파되는지 wire-level capture planned
W3C tracestate entry 의 List-Members 32개 / total length 제한 W3C-TC-C4 Usage Boundary: tracestate entry 개수 / 크기 제한 spec 별도 섹션 추가 인용 필요 spec 재 fetch + vendor 별 tracestate 사용 크기 monitoring planned
Micrometer Tracing TaskDecorator 가 @Async / Scheduled 경계에서 trace context 자동 전파 본 branch 인용 자료에 Micrometer Tracing TaskDecorator 직접 spec 없음. 실측: AsyncContextTaskDecorator 는 MDC copy 만 — Observation scope 미재establish (§Audit CARRIER_DRIFT) @Async 호출 → child thread 에서 Span.current() 또는 MDC trace_id 확인 test planned
Kafka / RabbitMQ 의 traceparent header 가 consumer side 에서 자동 extract OTel Java instrumentation 의 Kafka / Rabbit Spring 모듈 spec 별도 raw 없음 producer/consumer e2e test — trace span 이 연결되는지 Jaeger / Tempo UI 확인 planned
force-sample on error/slow/retry-exhausted 가 head sampler 단독으로 구현 가능 OTEL-SAMP-C4 Usage Boundary: head sampler 는 trace 전체 데이터 기반 결정 불가 — force-sample 은 별도 SDK 구현 OTel SDK ParentBased + AlwaysOn / TraceIdRatioBased 조합 + 커스텀 sampler 구현 확인 needs-confirmation
B3 → W3C edge translation 의 정확한 구현 (multi-propagator 패턴) B3-C5/B3-C6 Usage Boundary: receiver precedence 만 규정 — edge converter 구현 별도 OTel SDK propagators=tracecontext,b3 설정 + 외부 시스템 fixture test planned
tracestate name/value vs key/value 표현 차이 (W3C-TC-C4) 의 정확한 spec 표현 2026-05-27 fetch 와 2026-05-25 캡처 표현 차이 — needs-confirmation W3C TR 페이지 단어 단위 재 fetch needs-confirmation
Observation.error(throwable) → OTel span recordException + setStatus(ERROR) 체인 공식 docs 산문 부재 — OtelSpan.error() 소스코드로만 확인(MICR-OBS-C1 + OTEL-TAPI-C4 간접) Micrometer Tracing reference(docs.micrometer.io/tracing) fetch 또는 OtelTracingObservationHandler 테스트로 span status 확인 needs-confirmation

테스트 계약

  • inbound 요청의 traceId가 response meta, log, outbound call에 연결되지 않으면 실패.
  • async/job/message boundary에서 correlationId가 사라지면 실패.
  • baggage에 금지 정보가 기록되면 실패.
  • tracing disabled local profile에서도 requestId/correlationId log field는 유지되어야 함.
  • tracing disabled 상태에서 meta.traceId가 누락되면 실패.

Audit & Findings

/branch-spec(2026-06-14) ground-truth 대조에서 발견한 drift·정합 권고·구현 상태. 사용자 작성 결정 영역은 자동 rewrite 하지 않고 정합 권고만 남긴다.

  • CARRIER_DRIFT (§구현 가이드 §2): Async/Messaging Carrier Keys 표의 @Async TaskDecorator | thread-local copy via Micrometer Observation 는 ca-tmpl 코드와 drift. 실제 src/app-bootstrap/.../async/AsyncContextTaskDecorator.javaMDC.getCopyOfContextMap() 기반 plain MDC 문자열 copy 이며 worker thread 에 Micrometer Observation scope 를 재establish 하지 않는다(javadoc 명시: io.micrometer:context-propagation + ContextPropagatingTaskDecorator 는 future enhancement). 표를 실측으로 정정함. carrier 전파 구현의 owner 는 background-job-async / runtime-context-propagation branch.
  • RESTATED_FOREIGN_DECISION (D9): D9 의 layer-notation mapping(snake/camel/kebab)은 foundation D19 + mdc-keys.yaml/headers.yaml(owner_branch = foundation)이 SSOT. consistency-contract(Single-Owner/Reference-Only)상 D9 는 재진술 이 아니라 foundation D19 의 consume pointer 여야 한다. D9 row 에 owner pointer 를 명시함. 추가 권고: ## 결정 사항 의 2026-05-22 identifier 표기 항목의 "본 branch의 Propagation Defaults 표가 보장" 문구는 "foundation D19 + mdc-keys.yaml/headers.yaml 이 SSOT, 본 branch 는 propagation 경계만 소유" 로 약화하는 것이 정확(사용자 결정 영역 → 권고만).
  • IMPL_STATUS (D1/D12): env/metric/header registry row 는 등록됐으나(owner_branch = 본 branch 확인), Micrometer Tracing config·OTLP exporter·Observation error handler·커스텀 sampler 클래스는 src/미존재. D1/D6 코드는 planned/documented-only. D12 부분 구현: SpanErrorRecorder 인터페이스 + NOOP constant 는 actually-implemented (Slice 1, 2026-06-14); tracer-backed 구현체는 planned. governing doc wiki/projects/ca-tmpl/observability-log-metric-trace-runbook 의 "과장 금지" 절과 정합 — 면접/포트폴리오에 "OTel 로 구현/운영했다" 금지. Slice 1 신규 타입: TraceParent (D5/D7), BaggageAllowlist (D2/D8), SpanErrorRecorder NOOP seam (D12) — 3개 모두 actually-implemented, 58 tests locally-verified, 2026-06-14.
  • GROUND_TRUTH 확인: ca-tmpl 경로 존재. registry owner_branch = feature-distributed-tracing-contract 를 env-keys/metrics/headers/secrets-classification 에서 확인. NO_GROUND_TRUTH 아님.

Seam composition 위험 / fork 가 실 tracer 배선 시 밟는 지뢰 (2026-06-15)

메타 위험: Scope C 구현은 ./gradlew check 1091 green 이나, 이 테스트는 실 OTel SDK 없이 mechanism 만 검증한다. seam 은 실 tracer 와 단 한 번도 composition-test 된 적 없다. "1091 green = seam 이 SDK 와 검증됨" 은 거짓 확신 — 아래 두 정합 위험은 green 이 구조적으로 못 잡는다. 둘 다 spec §Claims To Verify 의 planned/needs-confirmation 항목(trace-flags sampled bit / Micrometer 통합)과 직접 연결된다.

  • LANDMINE-1 — outbound sampled=00 하드코딩이 downstream trace 를 능동적으로 억제 (TraceContextPropagationInterceptor): mdc-keys.yaml(foundation SSOT)에 sampled/trace-flags carrier key 가 없으므로, outbound traceparenttrace_id+span_id 로만 재구성되고 flags 는 00(not-sampled)으로 강제된다. downstream ParentBased sampler 는 00 을 "parent not sampled" 로 읽어 child span 을 drop → 상류가 sample 한 trace 도 이 경계에서 끊긴다. 게다가 이 interceptor 는 OutboundHttpClient 에서 첫 번째로 등록되어 traceparent 를 먼저 stamp 하고, idempotency guard 가 이후 OTel instrumentation 을 skip 시킨다 — 00 은 fallback 이 아니라 실 결정을 덮어쓴다. seam 이 중립이 아니라 능동적으로 sampling 을 끄는 상태.
    • fork 조치: (a) 이 interceptor 를 비활성/제거하고 OTel RestClient instrumentation 이 traceparent 를 소유하게 하거나, (b) TraceParent.of(.., false) 를 실 Span.getSpanContext().isSampled() 로 교체 + foundation 에 trace_flags MDC carrier 신설(= cross-branch, foundation D11/D19 소유). sampled 비트 보존은 본 branch 단독으로 불가 — mdc-keys.yaml 소유권이 foundation 이기 때문.
  • LANDMINE-2 — filter-생성 meta.traceId vs 실 SDK trace-id 발산 (RequestLoggingFilter): no-tracer skeleton 에서는 filter 가 inbound 부재 시 trace_id민팅하고 ResponseMetaFactorymeta.traceId 로 투영한다. fork 가 Micrometer Tracing 을 켜면 OTel SDK 도 같은 요청에 trace-id 를 민팅하고 SLF4J-Micrometer bridge 가 자기 id 를 MDC trace_id 에 쓴다. filter 가 이기면 응답의 meta.traceId ≠ 실제 export 된 span 의 trace-id → "응답에 박힌 id 로 백엔드에서 trace 추적"(D4 핵심 목적)이 조용히 깨진다.
    • fork 조치: 실 tracer 가 MDC trace_id단독 owner 가 되도록 filter 를 tracing observation 이후로 ordering 하거나, filter 가 Span.current() 를 adopt 하도록 교체. ordering/scope 의존 → 반드시 통합 테스트로 meta.traceId == exported trace-id 확인.
  • 권고(차기 작업): 이 두 지뢰의 진짜 해소는 (1) foundation 에 trace_flags MDC carrier 추가(cross-branch) + (2) 실 OTel SDK 와의 composition 통합 테스트(Testcontainers OTLP collector / Jaeger 로 meta.traceId↔exported span 일치 + sampled 보존 검증)를 요구한다. 둘 다 Scope C(본 branch 단독) 밖 — planned 로 명시. 코드에는 TraceContextPropagationInterceptor/RequestLoggingFilter javadoc 에 ⚠ FORK LANDMINE 블록으로 박아둠.

완료 후 wiki 추출 대상

  • wiki/projects/ca-tmpl/observability-log-metric-trace-runbook.md 의 observability/tracing canonical section (governing doc).

관심사 커버리지 (coverage-auditor 생성 — 2026-06-14)

governing doc wiki/projects/ca-tmpl/observability-log-metric-trace-runbook (§Trace) 이 요구하는 관심사를 본 branch 가 빠짐없이 덮는지의 결과. 기준: rules/coverage-gate.md. 판정: Covered (missing 0).

관심사 상태 owner 심각도 근거
전파 형식: W3C traceparent 채택, B3 forbidden covered-here D5 (W3C-TC-C1/C2/C3), D7 (B3-C6); headers.yaml traceparent/tracestate owner
트레이싱 라이브러리/exporter: Micrometer Tracing + OTel bridge covered-here D1 (SB-TRAC-C1~C4); env OTEL_EXPORTER_OTLP_ENDPOINT owner
샘플링 전략: prod 1% / staging 10% / dev·local 100% + force-sample covered-here D6 (OTEL-SAMP-C1/C3/C4); env APP_TRACING_SAMPLE_RATE + metric tracing.sampling.rate owner
대안 검토: tail-based / Datadog·X-Ray / B3 거부 covered-here D11 / D10 / D7; §외부 근거·대안 조사
tracing 활성화 toggle + disabled 시 meta.traceId 유지 covered-here D4 (OTEL-TAPI-C1/C2/C3); env APP_TRACING_ENABLED owner
baggage: PII/token 금지 + allowlist (tenant_id/request_id) covered-here D2 (W3C-BAG-C1 + OTEL-BAG-C3), D8 (W3C-BAG-C2/C3 + OTEL-BAG-C4)
span error 기록: Observation.error() + error.code + sampled-only stack trace covered-here D12 (MICR-OBS-C1/C3 + OTEL-TAPI-C4). SpanErrorRecorder 인터페이스 + NOOP actually-implemented; tracer-backed impl 은 planned
log/trace 샘플링 분리 정합 (trace 1% vs log 10%) delegated raw/branch-notes/feature-log-management-contract OK D6 Open Risk + §엣지·실패·의존 포인터
ID 의미 SSOT (traceId/spanId/correlationId/requestId 의미) delegated raw/branch-notes/feature-operational-error-observability-foundation OK D3 + D9 consume-pointer (foundation D11/D19, mdc-keys.yaml owner)
async/messaging carrier 실 전파 구현 (TaskDecorator/context propagation) delegated raw/branch-notes/feature-background-job-async-contract, raw/branch-notes/feature-runtime-context-propagation-contract OK §구현 가이드 §2 (carrier key 정의만 본 branch) + §Audit CARRIER_DRIFT

마주친 문제

  • SpanErrorRecorder 생성자 의존이 @WebMvcTest 슬라이스 컨텍스트를 깨뜨림 (2026-06-14, 해결됨): Slice 2 에서 GlobalExceptionHandlerSpanErrorRecorder 생성자 파라미터를 추가하자, app-bootstrap@Bean(@ConditionalOnMissingBean)만으로는 부족 — sample-portfolio@WebMvcTest + @Import({Controller, GlobalExceptionHandler.class, ...}) 슬라이스 테스트 34개가 NoSuchBeanDefinitionException: SpanErrorRecorder 로 컨텍스트 로드 실패. @WebMvcTest 는 임의 @Configuration 을 component-scan 하지 않으므로 bootstrap 의 NOOP bean 이 슬라이스에 보이지 않았다. 해결: GlobalExceptionHandler@Autowired ObjectProvider<SpanErrorRecorder> 생성자를 추가해 getIfAvailable(() -> NOOP) 로 self-default — 모든 컨텍스트(풀 앱/슬라이스/유닛)가 bean 없이 wiring, fork 가 bean 기여 시 override. bootstrap 의 redundant bean + 테스트의 보조 @Import 는 제거. → raw/errors/spanerrorrecorder-constructor-breaks-webmvctest-slice-2026-06-14

묶음

본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

근거 자료

오류 기록 (본 feature 작업 중 발생)

  • (없음 — Slice 1 구현 무오류 완료)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • W3C traceparent 의 4개 필드와 각 필드의 유효성 검증 규칙(all-zero 거부, lowercase 강제 이유)을 설명하라.
  • 왜 OTel의 recordException() 만으로는 span status 가 ERROR 로 설정되지 않는가 — setStatus(ERROR) 를 별도로 호출해야 하는 이유.
  • Java stdlib-only 모듈(shared-contract)에 tracing 타입을 두는 이유와 trade-off.
  • BaggageAllowlist 의 D2/D8 결정 근거 — W3C Baggage spec 은 allowlist 를 정의하지 않는데 왜 여기서 allowlist 를 강제하는가.
  • @WebMvcTest 슬라이스에서 base 핸들러의 선택적 협력자를 어떻게 wiring 하는가 — @ConditionalOnMissingBean(composition-root bean) vs ObjectProvider<T> self-default 의 차이와, 왜 후자가 컨텍스트 견고성이 높은가. (→ raw/errors/spanerrorrecorder-constructor-breaks-webmvctest-slice-2026-06-14)
  • "tracer 를 fork-activated seam 으로 둔다"는 결정의 의미 — 계약 메커니즘(전파/baggage/disabled fallback/span-error seam/sampling gauge)만 구현하고 OTel SDK 런타임은 미배선으로 두는 trade-off, 그리고 면접에서 "구현했다/운영했다"를 어디까지 말할 수 있는가(과장 금지 경계).
  • 분산 추적 비활성(disabled) 상태에서도 meta.traceId 를 유지하는 방법 — OTel SDK 를 noop 으로 두면 traceId=all-zeros 인데, app-generated W3C id(request filter)로 fallback 하는 이유.

job-posting tie-ins (이 작업에서 파생된 글감)

  • "Spring Boot 에 OTel 없이 W3C traceparent 계약 타입만 구현하는 이유 — fork-activated seam 패턴"

관련 일일 노트

  • (없음 — Phase C2 실 구현 단계에 누적)

완료 후 정리

머지/종료 시점에 채움.

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: (1) shared.tracing.TraceParent/BaggageAllowlist/SpanErrorRecorder(+NOOP) pure 계약 타입; (2) RequestLoggingFilter W3C traceparent accept/생성 + meta.traceId disabled-fallback(D4); (3) GlobalExceptionHandler SpanErrorRecorder seam 호출 + ObjectProvider self-default; (4) TraceContextPropagationInterceptor outbound traceparent/X-Request-Id/X-Correlation-Id/allowlisted-baggage 전파; (5) TracingProperties(시작시 검증) + TracingSampleRateResolver(per-profile) + tracing.sampling.rate gauge; (6) .env/application.yml 3키 배선; (7) 6개 required_test + §테스트계약 5종.
    • locally-verified 항목: cd src && ./gradlew check = BUILD SUCCESSFUL, 1091/1091 tests (verifyCleanArchitectureDependencies + verifyEnvKeys + ArchUnit 포함), 2026-06-14. 리뷰 체인(architect/spec/quality) 통과.
    • prod-verified 항목: (없음 — 운영 환경 미검증)
  • 추출하지 않을 항목 (planned / documented-only / abandoned): 실 OTel SDK/Micrometer Tracing/OTLP exporter 런타임, 실 head/force-sample sampler, async Observation scope 재establish, B3 edge translation, tracestate 한계 모니터링 — 전부 planned(fork-activated seam). "OTel 로 추적을 구현/운영했다"는 추출 금지(과장 금지).