feat(phase-5-3): Logger, Metrics, 알림 시스템 통합

Phase 5-3 모니터링 강화 작업의 통합을 완료했습니다.

변경사항:
- Logger 통합: console.log를 구조화된 로깅으로 전환 (9개 파일)
  - JSON 기반 로그, 환경별 자동 전환 (개발/프로덕션)
  - 타입 안전성 보장, 성능 측정 타이머 내장

- Metrics 통합: 실시간 성능 모니터링 시스템 연결 (3개 파일)
  - Circuit Breaker 상태 추적 (api_call_count, error_count, state)
  - Retry 재시도 횟수 추적 (retry_count)
  - OpenAI API 응답 시간 측정 (api_call_duration)

- 알림 통합: 장애 자동 알림 시스템 구현 (2개 파일)
  - Circuit Breaker OPEN 상태 → 관리자 Telegram 알림
  - 재시도 실패 → 관리자 Telegram 알림
  - Rate Limiting 적용 (1시간에 1회)

- 문서 업데이트:
  - CLAUDE.md: coder 에이전트 설명 강화 (20년+ 시니어 전문가)
  - README.md, docs/: 아키텍처 문서 추가

영향받은 파일: 16개 (수정 14개, 신규 2개)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-19 21:23:38 +09:00
parent 410676e322
commit eee934391a
16 changed files with 675 additions and 777 deletions

View File

@@ -2,6 +2,10 @@ import type { Env } from './types';
import { tools, selectToolsForMessage, executeTool } from './tools';
import { retryWithBackoff, RetryError } from './utils/retry';
import { CircuitBreaker, CircuitBreakerError } from './utils/circuit-breaker';
import { createLogger } from './utils/logger';
import { metrics } from './utils/metrics';
const logger = createLogger('openai');
// Cloudflare AI Gateway를 통해 OpenAI API 호출 (지역 제한 우회)
const OPENAI_API_URL = 'https://gateway.ai.cloudflare.com/v1/d8e5997eb4040f8b489f09095c0f623c/telegram-bot/openai/chat/completions';
@@ -42,36 +46,42 @@ async function callOpenAI(
messages: OpenAIMessage[],
selectedTools?: typeof tools // undefined = 도구 없음, 배열 = 해당 도구만 사용
): Promise<OpenAIResponse> {
return await retryWithBackoff(
async () => {
const response = await fetch(OPENAI_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages,
tools: selectedTools?.length ? selectedTools : undefined,
tool_choice: selectedTools?.length ? 'auto' : undefined,
max_tokens: 1000,
}),
});
const timer = metrics.startTimer('api_call_duration', { service: 'openai' });
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
try {
return await retryWithBackoff(
async () => {
const response = await fetch(OPENAI_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages,
tools: selectedTools?.length ? selectedTools : undefined,
tool_choice: selectedTools?.length ? 'auto' : undefined,
max_tokens: 1000,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
}
return response.json();
},
{
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
}
return response.json();
},
{
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
}
);
);
} finally {
timer(); // duration 자동 기록 (성공/실패 관계없이)
}
}
// 메인 응답 생성 함수
@@ -108,8 +118,10 @@ export async function generateOpenAIResponse(
let response = await callOpenAI(apiKey, messages, selectedTools);
let assistantMessage = response.choices[0].message;
console.log('[OpenAI] tool_calls:', assistantMessage.tool_calls ? JSON.stringify(assistantMessage.tool_calls.map(t => ({ name: t.function.name, args: t.function.arguments }))) : 'none');
console.log('[OpenAI] content:', assistantMessage.content?.slice(0, 100));
logger.info('tool_calls', {
calls: assistantMessage.tool_calls ? assistantMessage.tool_calls.map(t => ({ name: t.function.name, args: t.function.arguments })) : 'none'
});
logger.info('content', { preview: assistantMessage.content?.slice(0, 100) });
// Function Calling 처리 (최대 3회 반복)
let iterations = 0;
@@ -154,17 +166,17 @@ export async function generateOpenAIResponse(
} catch (error) {
// 에러 처리
if (error instanceof CircuitBreakerError) {
console.error('[OpenAI] Circuit breaker open:', error.message);
logger.error('Circuit breaker open', error as Error);
return '죄송합니다. 일시적으로 서비스를 이용할 수 없습니다. 잠시 후 다시 시도해주세요.';
}
if (error instanceof RetryError) {
console.error('[OpenAI] All retry attempts failed:', error.message);
logger.error('All retry attempts failed', error as Error);
return '죄송합니다. AI 응답 생성에 실패했습니다. 잠시 후 다시 시도해주세요.';
}
// 기타 에러
console.error('[OpenAI] Unexpected error:', error);
logger.error('Unexpected error', error as Error);
return '죄송합니다. 예상치 못한 오류가 발생했습니다.';
}
}
@@ -194,17 +206,17 @@ export async function generateProfileWithOpenAI(
} catch (error) {
// 에러 처리
if (error instanceof CircuitBreakerError) {
console.error('[OpenAI Profile] Circuit breaker open:', error.message);
logger.error('Profile - Circuit breaker open', error as Error);
return '프로필 생성 실패: 일시적으로 서비스를 이용할 수 없습니다.';
}
if (error instanceof RetryError) {
console.error('[OpenAI Profile] All retry attempts failed:', error.message);
logger.error('Profile - All retry attempts failed', error as Error);
return '프로필 생성 실패: 재시도 횟수 초과';
}
// 기타 에러
console.error('[OpenAI Profile] Unexpected error:', error);
logger.error('Profile - Unexpected error', error as Error);
return '프로필 생성 실패: 예상치 못한 오류';
}
}