Traces 심화
Span의 구조
Span은 분산 추적의 기본 단위이다. 하나의 작업을 표현하며, 다음 정보를 담는다:
- 이름: 작업을 식별하는 문자열 (예:
process_order) - SpanContext: Trace ID + Span ID + Trace Flags
- 시작/종료 시간: 작업의 소요 시간 측정
- Attributes: 키-값 쌍의 메타데이터 (예:
order.id=12345) - Events: Span 내에서 발생한 이벤트 (예:
order_saved) - Status: OK, ERROR, UNSET
- Links: 다른 Trace와의 연결 (비동기 작업 등)
Python 예시
from opentelemetry import trace
tracer = trace.get_tracer("order-service")
with tracer.start_as_current_span("process_order") as span:
span.set_attribute("order.id", "12345")
span.set_attribute("order.amount", 99.99)
with tracer.start_as_current_span("validate_payment"):
# 결제 검증 로직
pass
with tracer.start_as_current_span("save_to_db"):
# DB 저장 로직
span.add_event("order_saved", {"db.table": "orders"})
with 블록이 Span의 시작과 종료를 자동으로 처리한다. 중첩된 with는 부모-자식 관계를 자동으로 형성한다.
SpanKind
| Kind | 설명 | 예시 |
|---|---|---|
SERVER | 서버가 수신한 요청 | HTTP 핸들러 |
CLIENT | 외부 서비스 호출 | HTTP/gRPC 클라이언트 |
PRODUCER | 메시지 생산 | Kafka Producer |
CONSUMER | 메시지 소비 | Kafka Consumer |
INTERNAL | 내부 작업 | 비즈니스 로직 |
SpanKind를 올바르게 설정하면 관측성 백엔드가 서비스 간 호출 관계를 자동으로 그릴 수 있다.
Metrics 심화
Instrument 종류
| Instrument | 설명 | 예시 | 집계 방식 |
|---|---|---|---|
| Counter | 단조 증가 | 총 요청 수, 에러 수 | Sum |
| UpDownCounter | 증감 가능 | 활성 연결 수, 큐 크기 | Sum |
| Histogram | 분포 측정 | 응답 시간, 요청 크기 | Bucket 분포 |
| Gauge | 특정 시점 값 | CPU 사용률, 메모리 | Last Value |
Python 예시
from opentelemetry import metrics
import time
meter = metrics.get_meter("order-service")
# Counter
order_counter = meter.create_counter(
name="orders.total",
description="Total number of orders",
unit="1"
)
# Histogram
latency_histogram = meter.create_histogram(
name="orders.duration",
description="Order processing duration",
unit="ms"
)
# UpDownCounter
active_orders = meter.create_up_down_counter(
name="orders.active",
description="Currently processing orders"
)
def process_order(order):
active_orders.add(1)
order_counter.add(1, {"status": "processing", "tier": order.tier})
start = time.time()
try:
# ... 처리 로직
order_counter.add(1, {"status": "completed", "tier": order.tier})
except Exception:
order_counter.add(1, {"status": "failed", "tier": order.tier})
raise
finally:
duration = (time.time() - start) * 1000
latency_histogram.record(duration, {"endpoint": "/orders"})
active_orders.add(-1)
Attributes(태그)를 붙이면 같은 메트릭을 다양한 차원으로 분석할 수 있다. status, tier, endpoint 등으로 필터링/그룹핑이 가능하다. 단, 카디널리티가 높은 속성(user_id 등)은 피해야 한다 — 메트릭 저장 비용이 폭증한다.
Logs
OTel은 새로운 로깅 API를 강제하지 않는다. 기존 로거(Python logging, Log4j 등)를 그대로 사용하면서 트레이스와 상관관계만 추가한다.
import logging
logger = logging.getLogger(__name__)
def process_order(order_id):
logger.info(f"Processing order {order_id}")
# OTel 자동 계측 활성화 시 출력:
# Processing order 12345 [trace_id=abc123 span_id=def456]
자동 계측이 활성화되면 로그에 trace_id와 span_id가 자동 삽입된다. 이를 통해 "이 에러 로그가 어떤 요청에서 발생했는지" 바로 추적할 수 있다.
Exemplar — Metrics와 Traces의 다리
Exemplar는 Metric 데이터 포인트에 Trace ID를 첨부하는 기능이다. "이 p99 레이턴시 급등의 원인이 된 구체적인 트레이스"를 바로 찾을 수 있다.
Grafana에서 메트릭 차트를 보다가 이상한 스파이크를 발견하면, Exemplar 링크를 클릭해 해당 트레이스로 바로 점프할 수 있다. 세 신호가 연결되어야 하는 이유이다.
계측(Instrumentation)
자동 계측 (Zero-Code)
코드를 수정하지 않고, 에이전트가 런타임에 라이브러리를 모니키패칭(monkey-patching)하여 텔레메트리를 수집한다.
# 1. 패키지 설치
pip install opentelemetry-distro opentelemetry-exporter-otlp
# 2. 사용 중인 라이브러리에 맞는 계측 패키지 자동 설치
opentelemetry-bootstrap -a install
# Flask → opentelemetry-instrumentation-flask 자동 설치
# requests → opentelemetry-instrumentation-requests 자동 설치
# 3. 에이전트로 애플리케이션 실행
OTEL_SERVICE_NAME=order-service \
OTEL_TRACES_EXPORTER=otlp \
OTEL_METRICS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
opentelemetry-instrument python app.py
이것만으로 Flask HTTP 요청, DB 쿼리, HTTP 클라이언트 호출 등에 자동으로 Span이 생성된다.
커버하는 것: Flask, Django, FastAPI, SQLAlchemy, psycopg2, pymongo, redis, requests, httpx, kafka-python, celery, gRPC 등
한계: 비즈니스 로직 수준의 세밀한 추적 불가, 커스텀 속성 추가 불가
수동 계측 (Manual)
OTel API를 사용하여 직접 Span을 생성하고, 비즈니스 컨텍스트를 추가한다.
from opentelemetry import trace
tracer = trace.get_tracer("order-service")
def process_order(order):
with tracer.start_as_current_span("process_order") as span:
span.set_attribute("order.id", order.id)
span.set_attribute("order.amount", order.total)
span.set_attribute("customer.tier", order.customer.tier)
try:
validate_order(order)
charge_payment(order)
with tracer.start_as_current_span("save_order") as child:
child.add_event("saving_to_db", {"table": "orders"})
db.save(order)
except PaymentError as e:
span.set_status(trace.StatusCode.ERROR, str(e))
span.record_exception(e)
raise
프로그래매틱 계측 (Programmatic)
코드에서 SDK를 직접 초기화한다. 자동 계측의 환경변수 설정을 코드로 옮긴 것.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
resource = Resource.create({
"service.name": "order-service",
"service.version": "1.2.0",
"deployment.environment": "production",
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://collector:4317"))
)
trace.set_tracer_provider(provider)
선택 가이드
| 자동 | 수동 | 프로그래매틱 | |
|---|---|---|---|
| 코드 변경 | 없음 | 많음 | SDK 초기화만 |
| 세밀함 | 라이브러리 수준 | 함수 수준 | SDK 설정 수준 |
| 시작 비용 | 매우 낮음 | 높음 | 중간 |
| 권장 | 빠른 시작, PoC | 비즈니스 로직 추적 | 세밀한 SDK 제어 |
실전 권장: 자동 계측으로 시작하고, 필요한 부분만 수동 계측을 추가한다. 두 방식은 함께 사용할 수 있다.