--- id: observability-opentelemetry title: OpenTelemetry — 통합 추적/메트릭/로그 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [observability, opentelemetry, tracing, vibe-coding] tech_stack: { language: "TypeScript / OpenTelemetry SDK", applicable_to: ["Backend"] } applied_in: [] aliases: [otel, distributed tracing, span, instrumentation] --- # OpenTelemetry > Vendor 중립 표준. 한 SDK 로 trace + metrics + logs 수집 → OTLP → Jaeger / Tempo / Datadog / Honeycomb 어디든. **auto-instrumentation 으로 80% 무료**, 비즈니스 span 만 직접. ## 📖 핵심 개념 - **Trace**: 요청 1건이 만든 span 트리. - **Span**: 한 작업 (HTTP, DB query, function call). - **Context propagation**: trace-id / span-id 가 외부 호출 / 큐로 이어짐. - **Resource**: service name / version / instance. - **Exporter**: OTLP gRPC/HTTP 가 표준. ## 💻 코드 패턴 ### Node 부팅 시 셋업 ```ts // telemetry.ts (앱 import 보다 먼저) import { NodeSDK } from '@opentelemetry/sdk-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto'; import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { Resource } from '@opentelemetry/resources'; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'; const sdk = new NodeSDK({ resource: new Resource({ [ATTR_SERVICE_NAME]: 'user-service', [ATTR_SERVICE_VERSION]: process.env.GIT_SHA, }), traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }), metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }), exportIntervalMillis: 30_000, }), instrumentations: [getNodeAutoInstrumentations()], }); sdk.start(); process.on('SIGTERM', () => sdk.shutdown()); ``` `node --require ./telemetry.ts app.ts` 로 실행. 자동으로 잡히는 것: - HTTP server / client - Express / Fastify / Koa - pg / mysql / redis - aws-sdk - gRPC ### 비즈니스 span 직접 ```ts import { trace } from '@opentelemetry/api'; const tracer = trace.getTracer('user-service'); async function processOrder(orderId: string) { return tracer.startActiveSpan('order.process', async (span) => { span.setAttribute('order.id', orderId); try { const o = await fetchOrder(orderId); // 자동 child span await chargePayment(o); // 자동 child span span.setAttribute('order.amount', o.total); return o; } catch (e) { span.recordException(e as Error); span.setStatus({ code: 2 /* ERROR */ }); throw e; } finally { span.end(); } }); } ``` ### Span 안의 baggage (메타데이터 전파) ```ts import { propagation, context } from '@opentelemetry/api'; const baggage = propagation.createBaggage({ user_id: { value: userId }, feature_flag_x: { value: 'on' }, }); context.with(propagation.setBaggage(context.active(), baggage), () => { // 이 안의 모든 외부 호출이 user_id 전파 }); ``` ## 🤔 의사결정 기준 | 도입 단계 | 권장 | |---|---| | 모놀리스 단일 서비스 | auto-instrumentation 만 | | 마이크로서비스 (>3) | auto + 비즈니스 span | | 매우 큰 트래픽 (비용) | head sampling (1%) + tail sampling (errors) | | Lambda / Edge | OpenTelemetry Lambda Layer | | 자체 인프라 | OpenTelemetry Collector + Tempo/Jaeger | | SaaS (Datadog/Honeycomb) | OTLP 호환 | ## ❌ 안티패턴 - **모든 함수에 span**: 폭증. 큰 단위 (request, transaction, queue job). - **span attribute 에 PII**: 같은 redact 정책. 민감 X. - **수동 trace-id 전파**: SDK 가 자동. 직접 설정 시 충돌. - **sampling 100% prod**: 비용 폭증. 1-10% + tail sampling for errors. - **resource attribute 없음**: 어떤 서비스 / 버전인지 모름. - **shutdown 누락**: deploy 시 마지막 span 손실. SIGTERM handler. - **로그와 trace 별개**: trace_id 를 로그에 같이 출력 → exemplar 로 그래프 점프. ## 🤖 LLM 활용 힌트 - "auto-instrumentation 먼저, 비즈니스 span 만 직접" 명시. - service.name / version / env 리소스 필수. - shutdown handler. ## 🔗 관련 문서 - [[Observability_Correlation_IDs]] - [[Observability_RED_USE_Metrics]]