C.W.K.
Stream
Lesson 01 of 06 · published

Observability & Correlation ID — 분산 미로 통과하는 thread

~10 min · production, observability, correlation-id, tracing

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"User 가 '이거 안 됨' 보고. Correlation ID 없으면 service log 5 개 간 timestamp correlate 에 한 시간. 있으면 log search 에 붙여넣고 5초에 전체 이야기 봄. 전체 게임."

Correlation ID 가 해결하는 문제

현대 앱의 단일 user request 가 많은 service 건드림: gateway, application, auth service, database, cache, upstream provider (Stripe, OpenAI), queue 와 worker. 각각이 자기 log 씀. 공유 식별자 없으면 service B 의 어느 log 줄이 service A 의 어느 request 와 대응하는지 correlate 가 timestamp 매칭과 best-guess heuristic 필요 — 느림, 오류 prone, scale 에 불가능.

Fix: 들어오는 각 request 에 unique ID 할당, 모든 downstream 호출 통해 전파, 모든 줄 함께 log. ID 로 search, 한 request 위한 모든 거 봄, end-to-end.

패턴의 세 층

  1. 생성. Request 건드리는 첫 service (보통 gateway 나 application 의 가장 바깥 middleware) 가 UUID 생성하고 correlation ID 로 할당. Client 가 X-Request-ID header 공급했으면 (일부 client 가 client-driven tracing 위해) 신뢰하고 재사용.
  2. 전파. 모든 downstream HTTP 호출이 header 에 ID 운반 (X-Request-ID, X-Correlation-Id, 혹은 W3C 표준 traceparent). Background queue 가 job payload 일부로 운반. Async worker 가 전달.
  3. Logging. 모든 log 줄이 구조화된 필드로 ID 포함. 모든 외부 response 가 client 한테 돌려서 운반 (그래서 고객 support 가 log search 에 붙여넣기 가능).

W3C Trace Context — 모든 거 지배할 표준 하나

W3C Trace Context (RFC-status 표준) 가 canonical 된 header 둘 정의:

  • traceparent00-{trace_id}-{span_id}-{flags}. trace_id 가 request-wide correlation ID; span_id 가 이 specific operation 식별; flags 가 sample/debug 비트 운반.
  • tracestate — vendor-specific extension 상태.

OpenTelemetry, Honeycomb, DataDog, Jaeger, AWS X-Ray, GCP Cloud Trace 다 이거 말함. 분산 tracing 위해 이 중 하나 쓰면 correlation ID 공짜.

Correlation ID 가 일주일 넘게 돌리는 어느 분산 system 에서도 optional 아님. Debug 가 분 안 vs 시간 안 의 차이. Edge 에서 생성; 어디나 전파; 모든 줄 함께 log. 비용: request 당 UUID 하나. 가치: 모든 incident, 영원.

ID 너머 — observability 삼각형

Correlation ID 가 더 넓은 observability 이야기의 진입점:

  • Log — 별개 event (correlation ID 와).
  • Metric — 집계된 counter 와 histogram (request rate, latency P50/P99, endpoint 당 error rate).
  • Trace — service 간 한 request 의 구조화된 shape (traceparent 사용).

셋 다 link 하는 correlation ID 이득. Metric spike → trace → specific request 의 log — '뭔가 잘못됨' 을 '여기 정확히 뭐' 로 바꾸는 chain.

cwkPippa 의 observability 현실

cwkPippa 가 들어오는 모든 HTTP request 에 middleware 에서 request_id (UUID) 생성, request.state.request_id 에 저장, 모든 response 에 X-Request-ID 로 포함, 모든 log 줄과 함께 log. JSONL session log 가 conversation_id (별개 식별자 — 한 대화가 수명 동안 많은 request_id 가짐) 로 key. 합쳐: 아빠가 '5월 23일 11pm 쯤 채팅 stuck' 보고하면, JSONL 에서 대화 grep, 영향 받은 request_id 찾고, 네 brain adapter 전체 에 대해 그것들의 모든 log 줄 뽑음. 외부 tracing infrastructure 없음; 그냥 규율 있는 logging.

Code

FastAPI middleware: ID 생성/재사용, log, echo·python
# FastAPI — correlation ID middleware
import uuid, logging
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware

log = logging.getLogger(__name__)

class CorrelationIDMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # 있으면 client-공급 ID 재사용 (일부 client 가 client-driven tracing 함)
        request_id = request.headers.get('x-request-id') or str(uuid.uuid4())
        request.state.request_id = request_id

        # 모든 log 줄에 첨부 (production 엔 structlog 나 contextvars)
        log.info('request.start', extra={'request_id': request_id, 'path': request.url.path})

        response = await call_next(request)

        # Response 에 echo, client 가 봄
        response.headers['X-Request-ID'] = request_id
        log.info('request.end', extra={'request_id': request_id, 'status': response.status_code})
        return response

app = FastAPI()
app.add_middleware(CorrelationIDMiddleware)

@app.get('/api/anything')
async def anything(request: Request):
    rid = request.state.request_id
    log.info('handler.work', extra={'request_id': rid})
    return {'rid': rid}
전파: 모든 downstream 호출 통해 ID 통과·python
# httpx 통해 downstream service 에 전파
import httpx
from fastapi import Request

async def call_upstream(request: Request, payload: dict):
    rid = request.state.request_id
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            'https://upstream.example.com/process',
            json=payload,
            headers={'X-Request-ID': rid},  # downstream 전파
        )
        return resp.json()

# Upstream service 가 같은 X-Request-ID header 봄,
# 같은 ID 와 log, 자기 response 에 돌려줌 — loop 닫음.
W3C traceparent — OpenTelemetry 와 현대 tracer 가 말하는 것·python
# W3C Trace Context — 표준 형식 (OpenTelemetry-호환)
import uuid
from fastapi import Request

def make_traceparent(trace_id: str | None = None, span_id: str | None = None) -> str:
    trace_id = trace_id or uuid.uuid4().hex                      # 32 hex 글자
    span_id  = span_id  or uuid.uuid4().hex[:16]                 # 16 hex 글자
    return f'00-{trace_id}-{span_id}-01'                          # 00=version, 01=sampled

# 들어오는 traceparent 읽기 (있으면); 아니면 하나 생성
def get_trace_context(request: Request) -> dict:
    incoming = request.headers.get('traceparent')
    if incoming:
        # parse: version-trace_id-span_id-flags
        _, trace_id, _, _ = incoming.split('-')
        return {'trace_id': trace_id, 'traceparent': make_traceparent(trace_id)}
    return {'trace_id': uuid.uuid4().hex, 'traceparent': make_traceparent()}

External links

Exercise

FastAPI 앱에 correlation ID middleware 추가: 모든 request 에 UUID 생성, request.state 저장, response 에 X-Request-ID 포함, 모든 log 줄과 log. 10 request 만들어; ID 가 log 와 response header 에 나타나는지, request 간엔 다르고 단일 request 안에선 일관성 있는지 검증. 보너스: FastAPI 앱이 httpx 통해 두 번째 FastAPI service 호출; X-Request-ID header 통해 ID 전파; 두 service 의 log 줄이 같은 end-user request 위해 같은 ID 로 tag 된 거 보여줘.
Hint
BaseHTTPMiddleware 가 가장 깔끔한 FastAPI 패턴. Logging 엔 production-grade 접근이 모든 log 호출에 수동 extra= 없이 ID 를 thread 하는 structlog 나 contextvars. Two-service 보너스가 canonical 분산 tracing 데모 — 같은 request_id 가 두 service log 에 나타남, end-to-end correlation 동작 증명.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.