Skip to content

OpenTelemetry

트레이스, 메트릭, 로그를 자체 컬렉터/백엔드로 내보내는 옵트인 OTLP 익스포트.

Updated View as Markdown

shunt는 트레이스, 메트릭, 로그를 OTLP/HTTP로 자체 OpenTelemetry Collector(또는 OTLP 호환 백엔드)에 내보낼 수 있습니다. 옵트인이며 기본적으로 꺼져 있습니다 — [otel] 섹션이 없으면 아무것도 머신 밖으로 나가지 않습니다 — 그리고 Sentry와 독립적으로 동작하므로 둘 중 하나 또는 둘 다 켤 수 있습니다.

이 페이지는 밖으로 나가는 shunt 자체의 텔레메트리를 다룹니다. 게이트웨이의 별도 텔레메트리 ingest는 반대 방향 — 관리형 Claude Code 클라이언트가 shunt로 보내는 OTLP 페이로드 — 을 담당하며, [server.gateway.telemetry]의 목적지로 verbatim relay합니다. 둘은 독립적으로 구성되며 서로 다른 컬렉터를 가리킬 수 있습니다.

활성화

키 하나로 켜집니다 — 컬렉터의 OTLP/HTTP 리시버를 가리키세요:

[otel]
endpoint = "http://localhost:4318"   # OTLP/HTTP base URL; shunt가 /v1/{traces,metrics,logs}를 덧붙임

나머지는 모두 합리적인 기본값을 가집니다:

[otel]
endpoint = "http://localhost:4318"
service_name = "shunt"     # (기본값) service.name 리소스 속성
environment = "prod"       # 선택: deployment.environment.name
sample_ratio = 1.0         # (기본값) head-based 트레이스 샘플링, 0.0–1.0
traces = true              # (기본값) 요청 스팬 내보내기
metrics = true             # (기본값) 사용량 메트릭 내보내기
logs = true                # (기본값) 로그 이벤트 내보내기 (stderr 로그는 영향 없음)
include_session_id = false # (기본값) 클라이언트 세션 id를 스팬에서 제외

[otel.headers]             # 선택: 요청마다 붙는 헤더, 예: 호스팅 컬렉터 토큰
authorization = "Bearer <token>"

endpoint = ""(예: SHUNT_OTEL__ENDPOINT="")로 설정하면 섹션을 지우지 않고도 익스포트를 다시 끕니다. 유효하지 않은 엔드포인트, http(s)가 아닌 URL, 범위를 벗어난 sample_ratio는 시작 오류이므로, 오타 때문에 모든 익스포트가 조용히 사라지지 않습니다.

세 가지 시그널

시그널 내보내는 내용 참고
트레이스 요청별 proxy_request 스팬 sample_ratio 기반 head 샘플링. 저(低)카디널리티이며 요청/응답 본문 없음.
메트릭 아래에 나열된 저카디널리티 계열 [sentry] metrics = true일 때 shunt가 Sentry로 보내는 것과 동일한 계열.
로그 shunt의 tracing 로그 이벤트를 OTLP로 브리지 stderr 로그는 영향받지 않음.

각 시그널은 traces / metrics / logs로 개별 토글합니다.

메트릭 계열

계열 유형 속성 의미
shunt.requests 카운터 provider, model, http.response.status_code, caller, client_kind, managed 프록시된 추론 요청. caller는 인바운드 트래픽이면 client, [models.router] 항목이 호출자를 대신해 만든 판정 호출이면 router입니다.
shunt.latency 히스토그램(ms) provider, model, http.response.status_code, caller, client_kind, managed 스트림은 헤더 지연 시간, 그 외에는 전체 지연 시간. 커밋형 스트리밍 응답은 헤더가 업스트림 응답보다 먼저 커밋되므로 디스패치부터 시도 분류까지를 측정합니다.
shunt.ttft 히스토그램(ms) provider, model, client_kind, managed 요청 시작부터 첫 번째 완전한 비-킵얼라이브 SSE 프레임까지의 시간.
shunt.stream_outcome 카운터 provider, model, outcome, client_kind, managed SSE 최종 결과 하나: completed, error_event, upstream_cut, client_disconnect.
shunt.tokens 카운터 provider, model, kind, client_kind, managed 마지막으로 보고된 스트리밍 토큰 사용량(input, output, cache_read, cache_creation). 비스트리밍 사용량은 기록하지 않음.
shunt.codex_continuation 카운터 provider, outcome Codex WebSocket continuation hit 또는 fallback.
shunt.codex_ws_overflow 카운터 provider, outcome 동시 turn이 전용 overflow 연결을 열면 opened, 연결 상한에서 거부되면 refused (issue #248).
shunt.codex_client_events 카운터 event 정제된 이벤트 이름별 Codex CLI 분석 이벤트. payload와 속성은 폐기됨.
shunt.gateway_telemetry_ingest 카운터 signal, outcome 인바운드 게이트웨이 OTLP 페이로드(metrics/logs/traces)를 ingest 결과별로 집계: relayed, 어떤 목적지도 opt-in하지 않았으면 discarded, 동시 relay 상한에서 포화됐으면 shed, 잘못된 bearer나 상한 초과 바디는 rejected.
shunt.upstream_retries 카운터 provider, reason 제한된 일시적 업스트림 재시도.
shunt.failover 카운터 provider, state 순서 있는 업스트림 페일오버 전환: attempted, advanced, exhausted.
shunt.router.decisions 카운터 model, algorithm, target, source 모든 [models.router] type의 라우팅 결정과 [models.subagents] 오버레이의 결정(algorithm = "subagents"). 결정을 내린 [[models]] 항목의 id(매칭된 형태라 [1m] 힌트는 이미 제거됨), 설정된 type, 결정이 해석된 model id, 그렇게 고른 이유별로 집계합니다. count_tokens 프로브는 제외됩니다.
shunt.stage_router.decisions 카운터 model, tier, source 스테이지 라우터의 티어 결정. 라우터의 model id(매칭된 형태라 [1m] 힌트는 이미 제거됨), 선택된 티어(capable/efficient), 그렇게 고른 이유별로 집계합니다. count_tokens 프로브는 제외됩니다.
shunt.router.judge_calls 카운터 model, algorithm, outcome 판정 모델을 설정한 [models.router] 항목이 만든 판정 호출. 라우터의 model id, 설정된 type, 호출이 어떻게 끝났는지(decided, 또는 timeout, oversized, upstream_error, invalid_reply, translation_failed, budget_exhausted 중 하나)별로 집계합니다. budget_exhausted는 세션이 max_judge_calls를 다 써서 호출이 거부됐다는 뜻입니다. 업스트림이 중간에 끊은 응답은 invalid_reply가 아니라 upstream_error로 집계됩니다. 스테이지 라우터에서는 이 결과들 모두 그 턴을 picker 기본값으로 라우팅하고, llm_classifier나 composite 항목, classifier 형태의 [models.subagents] 오버레이에서는 알고리즘 자신의 대체 경로가 턴을 닫습니다. 호출 없이 보존된 배정으로 처리된 턴은 retained로 기록됩니다. escalation이나 advisor 항목에서는 완성된 턴 뒤의 실패가 붙잡아 둔 그 턴을 대신 내보내고(fail_open = false인 advisor는 요청을 실패시키며, 실패한 리뷰는 여전히 upstream_error나 timeout 같은 그 결과로 집계되고, max_judge_calls로 거부된 리뷰는 budget_exhausted로 집계됩니다), advisor의 리뷰는 algorithm = "advisor"로 집계됩니다. 붙잡아 둔 턴 자체는 판정 호출이 아닙니다.
shunt.stage_router.flips 카운터 model, from, to 세션을 고정된 티어에서 옮긴 결정. 올라가기는 쉽고 내려오기는 어렵게 설계했으므로 두 방향을 분리해 셉니다.
shunt.requests_shed 카운터 — max_concurrent_requests 상한에서 거부된 인바운드 요청. 거부 자체는 debug!로만 로깅되므로, 게이트웨이 포화 상태를 드러내는 것은 이 카운터입니다.
shunt.pool.quota_utilization 게이지 provider, window 5h, 7d, 7d_oi별 활성 상태이고 관측됐으며 만료되지 않은 quota 값 중 최소 사용률.
shunt.pool.rotations 카운터 provider, reason 계정에서 이동한 횟수와 pool이 소진된 요청 수.
shunt.pool.reprobes 카운터 provider 오래된 근접 쿼터 Codex/ChatGPT 계정의 첫 HTTP 전송 시 커밋된 재탐침. WebSocket을 켠 프로바이더는 inbound HTTP 탐침만 셉니다.
shunt.store.write_failures 카운터 subsystem 선택적 [store]에 대한 쓰기 실패를 subsystem별로 집계: pool(write-behind flush), spend, gateway(token 엔드포인트). 실패한 풀 flush는 다음 15초 주기에 재시도하지만, 이 프로세스가 store hold를 잃으면 풀 flush가 중단됩니다. 실패한 지출 쓰기는 관리자 호출자에게, 실패한 token 엔드포인트 쓰기는 클라이언트에게 보고하며, 백그라운드 게이트웨이 flush 실패는 호출자가 없어 로그로만 남습니다.
shunt.upstream.status 게이지 provider 관측된 [server.status] Statuspage severity: 0 none, 1 minor, 2 major, 3 critical. 현재 unknown(신호 없음) 상태인 provider는 0으로 보고되는 대신 이 시리즈에서 완전히 제외됩니다.

요청 단위 계열 다섯 개에는 속성 두 개가 추가로 붙습니다. client_kind는 요청 자체의 클라이언트 헤더(x-app, originator, user-agent)에서 읽어 claude-code, codex, other 중 하나로 정하며, 원본 헤더 값은 절대 내보내지 않습니다. managed는 게이트웨이가 발급한 JWT로 인증된 요청일 때만 true입니다. 그 크리덴셜이 곧 클라이언트가 푸시된 OTEL_* 환경을 받을 자격을 주는 크리덴셜입니다. 이 둘을 합치면 shunt가 서버에서 파생한 계열과 게이트웨이 모드 클라이언트가 스스로 내보내는 claude_code.*를 구분할 수 있으므로, 대시보드는 claude_code.*가 있는 곳에서는 그것을, 나머지에서는 shunt.*{managed="false"}를 골라 중복 집계 없이 합산할 수 있습니다. 이 필터는 백엔드가 관리 대상 클라이언트의 claude_code.* 지표를 실제로 받는다는 전제에서만 맞습니다. metrics를 켠 [server.gateway.telemetry].forward_to 목적지를 거쳐 릴레이되든 클라이언트가 직접 내보내든 상관없습니다. 받지 못한다면 managed 값과 관계없이 shunt.* 전체를 읽어야 합니다. 판정 호출(caller="router")은 원래 턴의 client_kind를 그대로 갖지만 managed는 항상 "false"입니다. shunt 자체의 트래픽이라 어떤 클라이언트 익스포터도 보고하지 않기 때문입니다. 인바운드 Codex 엔드포인트는 [server.auth] 크리덴셜만 받으므로 그 요청도 항상 managed="false"입니다. 같은 값 두 개가 요청 스팬에도 shunt.client_kind, shunt.managed로 기록됩니다. 인바운드 Codex WebSocket 턴에는 요청 스팬이 없으므로 그 귀속 정보는 지표에만 나타납니다.

프라이버시

shunt는 메트릭과 트레이스에서 요청/응답 본문, 헤더, 자격 증명을 절대 내보내지 않습니다.

  • 메트릭과 트레이스는 저카디널리티이며 본문이 없습니다. OTLP 트레이스 익스포트에서 요청 스팬의 클라이언트 세션 id는 include_session_id = true(기본 꺼짐)일 때만, 그리고 트레이스 익스포트가 활성화된 동안에만 컬렉터로 전송됩니다. 같은 규칙이 Sentry 트레이스 익스포트([sentry] traces_sample_rate / include_session_id)에도 적용됩니다. 어떤 스팬 익스포트도 활성화되지 않은 경우 id는 예전처럼 로컬 요청 스팬에만 남습니다.
  • 로그는 shunt 자체 진단 이벤트를 있는 그대로 반영하므로, stderr 로그처럼 요청에서 파생된 필드(업스트림 오류 본문, 인증된 클라이언트 id)를 포함할 수 있습니다. 엄격하게 본문 없는 익스포트를 원하면 logs = false로 두고 메트릭/트레이스만 유지하세요.

내보내는 리소스는 service.*, telemetry.sdk.*, 그리고 environment가 설정된 경우 deployment.environment.name을 광고합니다 — host나 process detector가 실행되지 않아 머신 호스트네임은 붙지 않습니다 — 여기에 표준 OTEL_RESOURCE_ATTRIBUTES로 설정한 값이 더해집니다.

표준 OTEL_ 환경 변수

  • endpoint와 service_name은 이 구성에서 오며 OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_SERVICE_NAME보다 우선합니다.
  • 표준 OTEL_EXPORTER_OTLP_HEADERS와 OTEL_RESOURCE_ATTRIBUTES는 [otel.headers] 및 내장 리소스 속성 위에 병합됩니다.

모든 키는 [otel] 구성 레퍼런스를 참고하세요.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close