학습 목표
trace_id · span_id를 구조화 로그에 넣는 방법·필드명을 설명할 수 있다.
correlation_id와 trace_id 역할 차이·병행 사용을 설명할 수 있다.
exemplar로 histogram 한 점에서 특정 trace로 점프하는 이유를 설명할 수 있다.
Grafana Tempo ↔ Loki derived field로 trace→log·log→trace 탐색을 구성할 수 있다.
alert → metric → exemplar/log → trace 조사 흐름을 설명할 수 있다.
문제 상황
- p99 알람 — Grafana에서 집계만 보고 어느 요청인지 모름
histogram_quantile(0.99, …)는 숫자 — 개별 slow request 없음
- 로그에
trace_id는 있는데 Tempo에서 붙여넣기 검색 — 클릭 한 번이 아님 - 트레이스 waterfall에서 Payment span이 빨간데 stack trace는 로그에만 있음
- span attribute에 전체 stack 넣으면 비용·PII 폭발
trace_id를 Prometheus 라벨로 넣었다가 OOM (앞서 cardinality)- exemplar가 있는데 라벨로 우회
correlation_id와trace_id둘 다 있는데 팀마다 다른 필드로 검색- on-call마다 쿼리 레시피 재발명
Part 3 마지막 — OTel·Tempo는 깔았다. 신호 세 개를 한 요청으로 묶는 운영 패턴.
1. 한 요청, 세 신호

| 신호 | 질문 | 연결 키 |
|---|---|---|
| 메트릭 | 얼마나 나쁜가 | exemplar → trace_id |
| 로그 | 무슨 이벤트·stack | trace_id, span_id 필드 |
| 트레이스 | 어디서·얼마나 | span → log derived field |
조사 (양방향):
Alert (5xx rate)
→ exemplar on slow histogram point → Tempo trace
→ span "Payment" → Logs for this span (Loki)
→ ERROR line + stack
또는:
Log ERROR (trace_id=7a3f…)
→ Explore Tempo by ID
→ waterfall → upstream span
→ metric panel same time range (검증)
- 앞서 three signals — 이번 편은 도구로 연결하는 법
- 샘플 안 된 trace도 로그는 남음 — ID로 상호 이동
2. 로그에 trace_id · span_id
OTel SDK·auto-instrumentation은 active span context를 제공한다. 모든 로그 라인에 주입.
{
"timestamp": "2026-06-28T06:01:05.512Z",
"level": "ERROR",
"service": "payment-service",
"message": "upstream timeout",
"trace_id": "7a3f2c8b1d4e5f60718293a4b5c6d7e8",
"span_id": "00f067aa0ba902b7",
"correlation_id": "req-9f2a-8841",
"duration_ms": 5012,
"upstream": "stripe-api"
}
| 필드 | 형식 | 용도 |
|---|---|---|
| trace_id | 32 hex (W3C) | Tempo/Jaeger 전 구간 |
| span_id | 16 hex | 이 서비스 구간 좁히기 |
| correlation_id | UUID/string | 레거시·비-OTel hop · 알람 메시지 |
- 필드명 통일 —
trace_idnottraceId(팀 convention 하나) - 32자 hex — Tempo query·derived field regex와 맞춤
- 앞서 구조화 로그 —
trace_id는 권장 필드에 추가
주입 방법
| 스택 | 방법 |
|---|---|
| Java Logback | OpenTelemetry Logback appender / MDC trace_id |
| Python structlog | contextvars + OTel get_current_span() |
| Go zap | otelzap 또는 span context hook |
| Collector | transform processor — 가능하지만 앱 주입이 일관 |
- 로그에 ID 없으면 — propagation 누락·비-OTel 서비스 의심
- ERROR 로그 alert payload에
trace_id포함 — on-call 첫 클릭
전체 예시: code/log-with-trace.json
3. correlation_id vs trace_id
| correlation_id | trace_id | |
|---|---|---|
| 범위 | 팀·API 관례 | W3C 표준 |
| 생성 | gateway 첫 서비스 | OTel root span |
| 전파 | X-Request-ID 등 |
traceparent |
| 로그 검색 | ✅ | ✅ |
| Tempo | ❌ (직접 X) | ✅ |
| exemplar | ❌ | ✅ |
- 둘 다 로그에 넣어도 됨 — gateway는 둘 다 생성·전파
- 레거시는 correlation_id만 → OTel 도입 시 같은 요청에 trace_id 추가
- 알람·Slack에는 trace_id 우선 (Grafana 링크용)
4. Exemplar — 메트릭에서 trace로
Exemplar — histogram (또는 counter) 샘플 하나에 trace_id(및 timestamp)를 붙임 — 라벨이 아님 → cardinality 안전.

http_request_duration_seconds_bucket{le="2.5"} ← 집계
└── exemplar {trace_id="7a3f…", timestamp=…} ← 이 버킷에 기여한 실제 요청 하나
| trace_id 라벨 | exemplar | |
|---|---|---|
| series 수 | 요청마다 폭발 | 변하지 않음 |
| Grafana | 불가 | histogram 점 클릭 → Explore trace |
| 전제 | — | OTel SDK metrics exemplar + Prometheus exemplar storage |
Prometheus 2.x — --enable-feature=exemplar-storage (버전별 확인).
OTel → Prometheus: exemplar 지원 remote write / scrape.
# exemplar가 붙은 histogram — Grafana Explore에서 "Query with exemplars"
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
- p99 숫자만으로 부족할 때 — exemplar 점에서 대표 slow trace 열기
- 앞서 cardinality — exemplar는 정석 우회
쿼리·설정: code/exemplar-query.promql
5. Grafana — trace ↔ log
Tempo datasource + Loki derived fields (또는 trace to logs 설정).

| 방향 | 설정 | UX |
|---|---|---|
| Trace → Log | Tempo: Loki datasource, trace_id → LogQL |
span 상세 "Logs for this span" |
| Log → Trace | Loki: derived field regex on trace_id → Tempo |
로그 줄 링크 클릭 |
# excerpt — code/grafana-tempo-loki.yaml
tracesToLogs:
datasourceUid: loki
filterByTraceID: true
mapTagNamesEnabled: false
logsToTraces:
datasourceUid: tempo
tags: [{ key: trace_id }]
LogQL 예:
{service="payment-service"} | json | trace_id="7a3f2c8b1d4e5f60718293a4b5c6d7e8"
- service 라벨 + trace_id 필드 — Loki 인덱스 설계 (앞서 label-first)
- span_id까지 필터하면 해당 hop 로그만
6. 조사 플레이북
| 단계 | 행동 | 신호 |
|---|---|---|
| 1 | 알람 확인·시간창 | metric |
| 2 | RED 패널·SLO | metric |
| 3 | p99 histogram exemplar 클릭 | metric → trace |
| 4 | slow/error span 선택 | trace |
| 5 | Logs for span | trace → log |
| 6 | root ERROR·stack | log |
| 7 | 같은 trace_id 다른 서비스 로그 | log |
| 8 | 배포·트래픽 같은 구간 metric | 검증 |
역방향: 로그 ERROR → trace_id → Tempo → 어느 hop slow.
- 앞서 로그 장애 좁히기 — trace_id 링크가 4편 playbook 업그레이드
- postmortem — trace_id 하나로 증거 체인 첨부
7. 실무 체크리스트
| 항목 | 권장 |
|---|---|
| 로그 | trace_id·span_id ERROR+WARN 최소, ideally 모든 레벨 |
| 필드명 | trace_id snake_case 팀 표준 |
| exemplar | histogram RED duration + OTel metrics |
| Grafana | Tempo↔Loki 한 대시보드에서 링크 테스트 |
| alert | notification에 trace_id·Explore URL |
| cardinality | trace_id 라벨 금지 — exemplar·로그 |
| legacy | correlation_id 유지 + trace_id 병행 |
| 안티패턴 | |
|---|---|
| trace_id metric label | OOM (8편) |
| stack in span attribute | 크기·PII — 로그로 |
| derived field regex 불일치 | 32 hex vs UUID 혼용 |
| exemplar 없이 p99만 | 어느 요청인지 모름 |
8. 정리
- 로그 —
trace_id·span_id로 텍스트 증거·stack - exemplar — 집계 metric 한 점 → 개별 trace (라벨 아님)
- Grafana — Tempo↔Loki 양방향 — 클릭 조사
- correlation_id — 레거시·사람용; trace_id — 도구 표준 키
- Part 3 트레이스 마무리 — 다음 SLO·에러 버짓
다음에 다룰 것
- SLO와 에러 버짓
- SLI, Architecture 가용성
해당 내용은 Observability Engineering (Charity Majors, Liz Fong-Jones, George Miranda), Prometheus exemplars, Grafana Tempo traces-to-logs 문서를 기반으로 합니다.
'관측성' 카테고리의 다른 글
| 온콜과 인시던트 (0) | 2026.06.30 |
|---|---|
| SLO와 에러 버짓 (0) | 2026.06.29 |
| OpenTelemetry (0) | 2026.06.27 |
| 분산 트레이싱 개요 (0) | 2026.06.26 |
| 메트릭 설계 안티패턴 (0) | 2026.06.25 |