토큰 지출 추적, 이중 분석 파이프라인, PII 제어, GrowthBook 게이팅.
Claude Code의 비용 분석 시스템은 모든 토큰 사용을 마이크로달러 단위로 추적하고, 이중 분석 파이프라인(1P OTel + Datadog)을 통해 관측성을 제공합니다. 동시에 PII(개인 식별 정보) 보호를 위한 엄격한 분리 정책이 적용됩니다.
analytics/cost-tracker.ts → analytics/sinks.ts → analytics/datadog.ts → analytics/otel-pipeline.ts → analytics/pii.ts
핵심 구성 요소:
_PROTO_ 패턴과 never 타입 마커를 통한 안전한 데이터 분류Claude Code는 분석 데이터를 두 개의 독립적인 파이프라인을 통해 전송합니다. 이 이중 구조는 각 파이프라인의 장점을 활용하면서도 단일 장애점을 방지합니다.
// 2파이프라인 아키텍처 개요
//
// 이벤트 발생
// │
// ├──→ 파이프라인 1: 1P OTel (OpenTelemetry)
// │ └─ OTel Logs SDK → OTLP 내보내기 → 디스크 JSONL 복원
// │
// └──→ 파이프라인 2: Datadog
// └─ 40 이벤트 허용목록 → SHA-256 해싱 → RUM
비용 추적기는 모든 API 호출의 토큰 사용량을 마이크로달러 단위로 변환하여 3개 목적지로 전송합니다.
// analytics/cost-tracker.ts — 마이크로달러 단위 비용 추적
interface CostEvent {
model: string
inputTokens: number
outputTokens: number
cacheReadTokens: number
cacheWriteTokens: number
costMicroUSD: number // 1 마이크로달러 = $0.000001
timestamp: number
}
function trackCost(event: CostEvent): void {
// 목적지 1: 로컬 세션 상태 (UI 표시용)
state.totalCostUSD += event.costMicroUSD / 1_000_000
// 목적지 2: 1P OTel 파이프라인
emitOtelCostLog(event)
// 목적지 3: Datadog RUM
emitDatadogCostEvent(event)
}
API 호출 한 건의 비용은 매우 작을 수 있습니다(예: 100 토큰 입력 = $0.00015). 부동소수점 연산의 정밀도 문제를 피하기 위해 정수 기반 마이크로달러($1 = 1,000,000 마이크로달러)를 사용합니다. 최종 표시 시에만 달러로 변환합니다.
캐시 읽기 토큰(cacheReadTokens)과 캐시 쓰기 토큰(cacheWriteTokens)은 서로 다른 요금이 적용됩니다. 캐시 읽기는 일반 입력 토큰의 10% 비용이지만, 캐시 쓰기는 125% 비용이 부과됩니다.
분석 싱크는 제로 의존성으로 구현되며, 큐-드레인 패턴을 사용하여 싱크가 초기화되기 전에 발생한 이벤트도 유실 없이 처리합니다.
// analytics/sinks.ts — 큐-드레인 패턴
let eventQueue: AnalyticsEvent[] = []
let sinksInitialized = false
function logEvent(name: string, data: Record<string, unknown>): void {
const event = { name, data, timestamp: Date.now() }
if (!sinksInitialized) {
// 싱크 초기화 전: 큐에 저장
eventQueue.push(event)
return
}
// 싱크 초기화 후: 직접 전송
dispatchToSinks(event)
}
function initSinks(): void {
// 싱크 연결
connectOtelSink()
connectDatadogSink()
sinksInitialized = true
// 대기열의 모든 이벤트 드레인
for (const event of eventQueue) {
dispatchToSinks(event)
}
eventQueue = []
}
분석 모듈은 외부 라이브러리에 의존하지 않습니다(OTel SDK 제외). HTTP 전송, 큐 관리, 재시도 로직이 모두 자체 구현되어 있습니다. 이는 분석 라이브러리의 버그나 호환성 문제가 Claude Code의 핵심 기능에 영향을 주는 것을 방지하기 위함입니다.
Datadog 파이프라인은 40개의 허용목록(allowlist) 이벤트만 전송하며, 모든 식별 가능 정보는 SHA-256 해싱 후 30개 버킷으로 매핑됩니다.
// analytics/datadog.ts — 이벤트 허용목록 & SHA-256 해싱
const ALLOWED_EVENTS = new Set([
'tengu_started',
'session_end',
'tool_use',
'api_call',
// ... 총 40개 이벤트
])
function hashToBucket(value: string): number {
const hash = createHash('sha256').update(value).digest()
// 첫 4바이트를 uint32로 읽고 30으로 모듈러 연산
return hash.readUInt32BE(0) % 30
}
function sanitizeForDatadog(event: AnalyticsEvent): DatadogEvent {
return {
name: event.name,
// 사용자 ID → 30개 버킷 중 하나로 해싱
userBucket: hashToBucket(event.data.userId),
// 원본 사용자 ID는 제거
timestamp: event.timestamp,
}
}
사용자 식별 정보를 30개 버킷으로 해싱하는 이유는 k-익명성을 보장하기 위함입니다. 각 버킷에는 충분히 많은 사용자가 포함되므로 개별 사용자를 식별할 수 없습니다. SHA-256은 역산이 불가능하므로 버킷 번호에서 원본 ID를 복원할 수 없습니다. 30이라는 버킷 수는 분석에 유용한 세분화를 제공하면서도 프라이버시를 보호하는 균형점입니다.
1P(First-Party) 파이프라인은 OpenTelemetry Logs SDK를 기반으로 풍부한 원격 측정 데이터를 수집합니다. 네트워크 장애 시 디스크 JSONL 파일로 백업하고 이차 백오프로 재전송합니다.
// analytics/otel-pipeline.ts — 디스크 JSONL 복원 & 이차 백오프
async function exportWithResilience(logs: OtelLog[]): Promise<void> {
try {
await otlpExport(logs)
} catch (err) {
// 네트워크 장애: 디스크에 JSONL로 저장
await appendToJSONL(RECOVERY_PATH, logs)
// 이차 백오프로 재시도 스케줄링
scheduleRetry({
backoff: 'quadratic', // 1s, 4s, 9s, 16s, 25s...
maxRetries: 10,
onRetry: () => drainJSONL(RECOVERY_PATH),
})
}
}
// 이차 백오프: delay = attempt^2 * 1000ms
function quadraticBackoff(attempt: number): number {
return attempt * attempt * 1000
}
이차 백오프(quadratic backoff)는 지수 백오프보다 초기 재시도 간격이 짧으면서도 급격히 증가합니다. 10번째 재시도는 100초(약 1.7분) 후에 발생합니다. 이는 짧은 네트워크 중단에는 빠르게 복구하면서도 장기 장애 시 과도한 재시도를 방지합니다.
PII(개인 식별 정보) 분리는 _PROTO_ 접두사 패턴과 TypeScript의 never 타입 마커를 결합하여 컴파일 타임에 PII 유출을 방지합니다.
// analytics/pii.ts — _PROTO_ 패턴 & never 타입 마커
// PII 데이터는 _PROTO_ 접두사가 붙은 필드에만 저장
interface UserEvent {
_PROTO_userId: string // PII: 1P 파이프라인에만 전송 가능
_PROTO_email: string // PII: 1P 파이프라인에만 전송 가능
eventName: string // 비PII: 모든 파이프라인에 전송 가능
timestamp: number // 비PII: 모든 파이프라인에 전송 가능
}
// Datadog 전송 타입: _PROTO_ 필드는 never로 마킹
type DatadogSafe<T> = {
[K in keyof T]: K extends `_PROTO_${string}` ? never : T[K]
}
// 컴파일 에러: _PROTO_ 필드를 Datadog에 전송하려 하면 타입 에러
function sendToDatadog(event: DatadogSafe<UserEvent>): void {
// event._PROTO_userId는 never 타입 → 접근 불가
}
런타임 검증(예: if (key.startsWith('_PROTO_')) throw)은 테스트에서 누락될 수 있습니다. TypeScript의 never 타입 마커를 사용하면 PII 필드에 접근하는 코드 자체가 컴파일되지 않으므로, 리뷰어의 주의력에 의존하지 않는 구조적 안전장치가 됩니다.
GrowthBook은 Claude Code에서 4가지 용도로 사용됩니다:
// GrowthBook 4가지 용도 예시
// 1. 기능 플래그
if (isFeatureEnabled('ultraplan_v2')) {
launchUltraplanV2(prompt)
}
// 2. A/B 테스트
const variant = getExperimentVariant('prompt_style')
// 3. 점진적 롤아웃
const rolloutPct = getFeatureValue('new_model_rollout', 0)
// 4. 원격 구성
const maxTokens = getFeatureValue('max_output_tokens', 16384)
엔터프라이즈 고객은 자체 OTel 수집기를 구성하여 Claude Code의 원격 측정 데이터를 자사 관측성 스택으로 전송할 수 있습니다. 이 기능은 완전한 옵트인 방식이며, 별도의 내보내기 경로를 사용합니다.
// 고객용 OTel 옵트인 구성
// settings.json에 엔드포인트 지정
{
"otel": {
"endpoint": "https://otel-collector.company.com:4318",
"headers": {
"Authorization": "Bearer ${OTEL_TOKEN}"
}
}
}
_PROTO_ 접두사와 TypeScript never 타입으로 컴파일 타임에 유출을 차단합니다Q1. cost-tracker.ts가 달러 대신 마이크로달러 단위를 사용하는 이유는?
Q2. 큐-드레인 패턴의 목적은?
tengu_started)는 분석 싱크가 아직 초기화되지 않은 시점에 발생합니다. 큐-드레인 패턴은 이 이벤트들을 임시 큐에 저장했다가 initSinks() 후에 일괄 전송합니다.Q3. Datadog 파이프라인에서 사용자 ID를 30개 버킷으로 SHA-256 해싱하는 이유는?
Q4. _PROTO_ 접두사와 TypeScript never 타입의 조합이 런타임 검증보다 우수한 이유는?
never 타입은 해당 필드에 접근하는 코드 자체를 컴파일 에러로 만들므로, 사람의 주의력에 의존하지 않는 강력한 안전장치입니다.Q5. 1P OTel 파이프라인이 이차 백오프(quadratic backoff)를 사용하는 이유는?