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

402 lines
46 KiB
Markdown

---
title: branch / feature-distributed-tracing-contract
source_type: branch-note
status: raw
branch: feature-distributed-tracing-contract
parent_branch:
governing_docs: [wiki/projects/ca-tmpl/observability-log-metric-trace-runbook]
related_projects: [ca-skeleton]
tags: [branch, ca-skeleton, tracing, observability]
created: 2026-05-22
target_merge:
status_label: in-progress
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-027
kind: project-work-item
project: ca-skeleton-operational-contract
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-027
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-ERROR-ENVELOPE-001@1]
refines: []
overrides: []
depends_on: []
contract_packet: 1
contract_packet_sha256: 7b6718a5912304437453bc70ffbbaba27bced681a98e7ed246687a83b38f67fa
---
# branch: feature-distributed-tracing-contract
> Layer: `raw/branch-notes/` — HTTP, async, messaging, outbound 경계에서 trace context가 끊기지 않도록 distributed tracing 기준을 정의합니다.
<!-- section-id: branch-parent -->
## 부모 (필수)
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: request·trace correlation contract test가 통과한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| 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]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
structured log만으로는 운영 장애의 흐름을 끝까지 추적하기 어렵습니다. traceId/requestId/correlationId/spanId의 의미와 전파 경계를 고정해서 어떤 adapter를 붙여도 같은 방식으로 원인을 추적할 수 있게 합니다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- 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)
### 채택 결정 + 뒷받침
- 결정: **W3C traceparent + tracestate (B3 forbidden) + Micrometer Tracing + OpenTelemetry exporter + prod 1% head-based sampling + force-sample on error/slow/retry-exhausted**.
- 뒷받침 source:
- [[raw/official-docs/tracing-w3c-trace-context-spec.md]] — W3C Recommendation, OTel default propagator. 128-bit trace-id + `tracestate` vendor 확장 spec.
- [[raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md]] — head-based + force-sample boost가 SDK 기본 기능만으로 구현 가능. tail-based는 collector overhead.
### 검토 대안 + source
- 대안 1 — **B3 / Zipkin propagation**: [[raw/official-docs/tracing-b3-propagation-zipkin-spec.md]]. legacy, 64-bit mode는 W3C 비호환. ca-tmpl은 forbidden, edge translation만 허용.
- 대안 2 — **Tail-based / Adaptive sampling**: [[raw/official-docs/tracing-otel-sampling-tail-vs-head-spec.md]]. error/slow trace 100% 보존 가능하나 collector 메모리 + decision_wait window 추가 운영 비용.
- 대안 3 — **Datadog APM / AWS X-Ray native tracer**: [[raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry.md]]. auto-instrumentation 광범위하나 vendor lock-in. ca-tmpl out-of-scope 결정과 충돌.
### 비교 핵심 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_id``ResponseMetaFactory` `meta.traceId` 항상 non-null (**D4 disabled-fallback = request_id mirror 제거하고 실 W3C id 로 교체**). `GlobalExceptionHandler``SpanErrorRecorder.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<MeterRegistry> 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`/`C6``needs-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 없음. 코드 미구현(`planned``src/` 에 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 코드와 어긋남 — 실제 `AsyncContextTaskDecorator` 는 **plain 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 유출).
- **다른 계약 의존**:
- [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `D11`(MDC snake_case 표준) + `D19`(snake/camel/kebab layer mapping) + `D16`(operational error → span ERROR 기록) 에 의존 — 본 branch 는 `trace_id`/`span_id`/`correlation_id`*의미·명명* 을 consume(SSOT 는 foundation + `mdc-keys.yaml`). 그 계약이 바뀌면 D3/D9/D12 영향.
- ca-tmpl `docs/registries/mdc-keys.yaml` + `headers.yaml`(registry SSOT) — `trace_id ↔ traceparent ↔ meta.traceId` 매핑. 본 branch 는 `traceparent`/`tracestate` header row 의 owner, MDC key row 는 foundation owner.
- [[raw/branch-notes/feature-log-management-contract]] — log sampling(prod 10%) vs trace sampling(prod 1%) 의도된 분리(D6 정합). log sampling 정책이 바뀌면 D6 비교 근거 재검토.
- [[raw/branch-notes/feature-background-job-async-contract]] + [[raw/branch-notes/feature-runtime-context-propagation-contract]] — 실제 async context 전파 메커니즘(`AsyncContextTaskDecorator` / `DomainContextPropagator`)의 owner. 본 branch 는 carrier key 만 정의하고 전파 구현은 그 branch 소유(§Audit CARRIER_DRIFT).
## 검증해야 할 주장
| 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.java``MDC.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 `traceparent``trace_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`**민팅**하고 `ResponseMetaFactory` 가 `meta.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 에서 `GlobalExceptionHandler``SpanErrorRecorder` 생성자 파라미터를 추가하자, `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]]
## 묶음
<!-- GENERATED: sources:start -->
- [[raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry]]
- [[raw/official-docs/baggage-otel-baggage-api-spec]]
- [[raw/official-docs/baggage-w3c-baggage-spec]]
- [[raw/official-docs/tracing-b3-propagation-zipkin-spec]]
- [[raw/official-docs/tracing-micrometer-observation-introduction]]
- [[raw/official-docs/tracing-otel-sampling-tail-vs-head-spec]]
- [[raw/official-docs/tracing-otel-trace-api-spec]]
- [[raw/official-docs/tracing-spring-boot-3-actuator-tracing-reference]]
- [[raw/official-docs/tracing-w3c-trace-context-spec]]
<!-- GENERATED: sources:end -->
<!-- GENERATED: errors:start -->
- [[raw/errors/spanerrorrecorder-constructor-breaks-webmvctest-slice-2026-06-14]]
<!-- GENERATED: errors:end -->
<!-- GENERATED: blog-topics:start -->
- [[raw/blog-topics/w3c-traceparent-fork-activated-seam-2026-07-02]]
<!-- GENERATED: blog-topics:end -->
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
### 근거 자료
- [[raw/official-docs/tracing-w3c-trace-context-spec]]
- [[raw/official-docs/tracing-otel-sampling-tail-vs-head-spec]]
- [[raw/official-docs/tracing-b3-propagation-zipkin-spec]]
- [[raw/company-tech-blogs/tracing-datadog-apm-vs-opentelemetry]]
- [[raw/official-docs/tracing-micrometer-observation-introduction]]
- [[raw/official-docs/tracing-spring-boot-3-actuator-tracing-reference]]
- [[raw/official-docs/tracing-otel-trace-api-spec]]
- [[raw/official-docs/baggage-w3c-baggage-spec]]
- [[raw/official-docs/baggage-otel-baggage-api-spec]]
### 오류 기록 (본 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 로 추적을 구현/운영했다"는 추출 금지(과장 금지).