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:
@@ -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 '프로필 생성 실패: 예상치 못한 오류';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user