# 메트릭 시스템 사용 예시 ## 개요 `src/utils/metrics.ts`는 API 호출 성능, Circuit Breaker 상태, 에러율 등을 추적하는 메트릭 수집 시스템입니다. ## 주요 특징 - **메모리 기반**: 최근 1000개 메트릭만 FIFO 방식으로 유지 - **Thread-safe**: 동시 요청 안전 - **저비용**: 메모리 연산만 사용하여 성능 오버헤드 최소화 - **Worker 재시작 시 초기화**: 장기 저장이 필요하면 향후 KV 확장 가능 ## 기본 사용법 ### 1. Import ```typescript import { metrics } from './utils/metrics'; ``` ### 2. 카운터 증가 API 호출 횟수, 에러 횟수 등을 카운트합니다. ```typescript // API 호출 시작 metrics.increment('api_call_count', { service: 'openai', endpoint: '/chat' }); // 에러 발생 metrics.increment('api_error_count', { service: 'openai', error: 'timeout' }); // 재시도 metrics.increment('retry_count', { service: 'openai' }); ``` ### 3. 값 기록 Circuit Breaker 상태, 캐시 히트율 등 특정 값을 기록합니다. ```typescript // Circuit Breaker 상태 (0=CLOSED, 1=OPEN, 2=HALF_OPEN) metrics.record('circuit_breaker_state', 1, { service: 'openai' }); // 캐시 히트율 (0.0 ~ 1.0) metrics.record('cache_hit_rate', 0.85, { cache: 'tld_prices' }); // API 응답 시간 (ms) metrics.record('api_call_duration', 245, { service: 'openai' }); ``` ### 4. 타이머 사용 API 호출 시간을 자동으로 측정합니다. ```typescript const timer = metrics.startTimer('api_call_duration', { service: 'openai' }); try { const response = await openaiClient.chat.completions.create({...}); return response; } finally { timer(); // 자동으로 duration 기록 } ``` ### 5. 통계 조회 ```typescript // 전체 API 호출 시간 통계 const stats = metrics.getStats('api_call_duration'); console.log(`평균: ${stats.avg}ms, 최대: ${stats.max}ms`); // 특정 서비스만 필터링 const openaiStats = metrics.getStats('api_call_duration', { service: 'openai' }); console.log(`OpenAI 평균: ${openaiStats.avg}ms`); ``` ## Circuit Breaker와 통합 `src/utils/circuit-breaker.ts`에서 상태 변경 시 메트릭을 기록합니다. ```typescript // circuit-breaker.ts import { metrics } from './metrics'; class CircuitBreaker { private setState(newState: CircuitBreakerState): void { this.state = newState; // 메트릭 기록 (0=CLOSED, 1=OPEN, 2=HALF_OPEN) const stateValue = newState === 'CLOSED' ? 0 : newState === 'OPEN' ? 1 : 2; metrics.record('circuit_breaker_state', stateValue, { service: this.name }); console.log(`[CircuitBreaker:${this.name}] State: ${newState}`); } async execute(fn: () => Promise): Promise { const timer = metrics.startTimer('api_call_duration', { service: this.name }); try { metrics.increment('api_call_count', { service: this.name }); const result = await fn(); return result; } catch (error) { metrics.increment('api_error_count', { service: this.name }); // 재시도 로직... throw error; } finally { timer(); } } } ``` ## Retry 메커니즘과 통합 `src/utils/retry.ts`에서 재시도 횟수를 기록합니다. ```typescript // retry.ts import { metrics } from './metrics'; async function retryWithExponentialBackoff( fn: () => Promise, options: RetryOptions ): Promise { let attempt = 0; while (attempt < options.maxRetries) { try { return await fn(); } catch (error) { attempt++; metrics.increment('retry_count', { attempt: attempt.toString() }); if (attempt >= options.maxRetries) { throw error; } await delay(options.initialDelay * Math.pow(2, attempt - 1)); } } throw new Error('Max retries exceeded'); } ``` ## Cache와 통합 `src/utils/cache.ts`에서 캐시 히트율을 기록합니다. ```typescript // cache.ts import { metrics } from './metrics'; class Cache { async get(key: string): Promise { const value = await this.kv.get(key, 'json'); if (value !== null) { metrics.record('cache_hit_rate', 1, { cache: this.name }); } else { metrics.record('cache_hit_rate', 0, { cache: this.name }); } return value; } } ``` ## 모니터링 대시보드 예시 Worker에서 메트릭 엔드포인트를 추가하여 실시간 모니터링: ```typescript // index.ts if (url.pathname === '/metrics') { const stats = { api_calls: { openai: metrics.getStats('api_call_count', { service: 'openai' }), context7: metrics.getStats('api_call_count', { service: 'context7' }), }, response_times: { openai: metrics.getStats('api_call_duration', { service: 'openai' }), context7: metrics.getStats('api_call_duration', { service: 'context7' }), }, errors: { total: metrics.getStats('api_error_count'), }, circuit_breaker: { openai: metrics.getStats('circuit_breaker_state', { service: 'openai' }), }, cache: { tld_prices: metrics.getStats('cache_hit_rate', { cache: 'tld_prices' }), }, }; return new Response(JSON.stringify(stats, null, 2), { headers: { 'Content-Type': 'application/json' }, }); } ``` ## 메트릭 타입 | 타입 | 설명 | 값 범위 | |------|------|---------| | `api_call_duration` | API 호출 시간 | ms (0+) | | `api_call_count` | API 호출 횟수 | 카운터 (1) | | `api_error_count` | API 에러 횟수 | 카운터 (1) | | `circuit_breaker_state` | Circuit Breaker 상태 | 0=CLOSED, 1=OPEN, 2=HALF_OPEN | | `retry_count` | 재시도 횟수 | 카운터 (1) | | `cache_hit_rate` | 캐시 히트율 | 0 (miss) ~ 1 (hit) | ## 메모리 관리 - 최대 1000개 메트릭 유지 (FIFO) - 각 메트릭: ~100 bytes (name + value + timestamp + tags) - 총 메모리: ~100KB (매우 저렴) ## 향후 확장 ### KV 저장 (장기 보관) ```typescript // 주기적으로 KV에 저장 (cron) const stats = metrics.getStats('api_call_duration'); await env.METRICS_KV.put( `stats:${Date.now()}`, JSON.stringify(stats), { expirationTtl: 86400 * 7 } // 7일 보관 ); ``` ### 외부 모니터링 연동 ```typescript // Cloudflare Analytics Engine import { Analytics } from '@cloudflare/workers-types'; const timer = metrics.startTimer('api_call_duration', { service: 'openai' }); const response = await callOpenAI(); timer(); // Analytics Engine에 전송 await env.ANALYTICS.writeDataPoint({ blobs: ['openai'], doubles: [response.duration], indexes: ['api_call'], }); ``` ## 테스트 ```typescript import { MetricsCollector } from './utils/metrics'; describe('MetricsCollector', () => { const metrics = new MetricsCollector(); beforeEach(() => { metrics.reset(); }); it('should increment counter', () => { metrics.increment('api_call_count', { service: 'test' }); const stats = metrics.getStats('api_call_count', { service: 'test' }); expect(stats.sum).toBe(1); }); it('should calculate stats correctly', () => { metrics.record('api_call_duration', 100); metrics.record('api_call_duration', 200); metrics.record('api_call_duration', 300); const stats = metrics.getStats('api_call_duration'); expect(stats.count).toBe(3); expect(stats.avg).toBe(200); expect(stats.min).toBe(100); expect(stats.max).toBe(300); }); }); ```