Back to Blog
OpenTelemetryTracesMetricsLogsInstrumentationSDK

0x02. OTel Signals & Instrumentation - 세 가지 신호와 계측 전략

Traces, Metrics, Logs 각 신호의 구조와 API, 그리고 자동/수동 계측 방식을 비교한다.

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_idspan_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 제어

실전 권장: 자동 계측으로 시작하고, 필요한 부분만 수동 계측을 추가한다. 두 방식은 함께 사용할 수 있다.