# Logger 사용 가이드 구조화된 로깅 유틸리티 (`src/utils/logger.ts`) 사용 방법 안내 ## 목차 1. [개요](#개요) 2. [기본 사용법](#기본-사용법) 3. [로그 레벨](#로그-레벨) 4. [성능 측정](#성능-측정) 5. [사용자 컨텍스트](#사용자-컨텍스트) 6. [출력 형식](#출력-형식) 7. [마이그레이션 가이드](#마이그레이션-가이드) 8. [Best Practices](#best-practices) --- ## 개요 ### 주요 특징 - ✅ **JSON 기반 구조화 로그**: 프로덕션 환경에서 로그 수집 시스템 연동 - ✅ **환경별 자동 전환**: 개발(읽기 쉬운 포맷) ↔ 프로덕션(JSON) - ✅ **타입 안전성**: TypeScript strict mode 지원 - ✅ **성능 측정**: 내장 타이머로 실행 시간 자동 계산 - ✅ **에러 안전성**: 로깅 실패가 메인 로직 중단시키지 않음 - ✅ **사용자 컨텍스트**: 사용자 ID 자동 포함 ### 로그 레벨 | 레벨 | 우선순위 | 용도 | 예시 | |------|---------|------|------| | `DEBUG` | 0 | 디버깅 정보 (개발 전용) | 함수 호출, 파라미터 값 | | `INFO` | 1 | 일반 정보 | 요청 처리, 성공 응답 | | `WARN` | 2 | 경고 (주의 필요) | API 지연, 폴백 사용 | | `ERROR` | 3 | 에러 (복구 가능) | API 실패, 재시도 | | `FATAL` | 4 | 치명적 에러 | 시스템 장애, DB 연결 실패 | --- ## 기본 사용법 ### 1. 로거 생성 ```typescript import { createLogger } from './utils/logger'; // 서비스별 로거 생성 const logger = createLogger('openai'); ``` ### 2. 로그 출력 ```typescript // 일반 정보 logger.info('AI 응답 생성 시작'); // 컨텍스트 포함 logger.info('API 호출 완료', { model: 'gpt-4o-mini', tokensUsed: 1000, }); // 경고 logger.warn('API 응답 지연', { endpoint: '/chat/completions', responseTime: 3500, }); ``` ### 3. 에러 처리 ```typescript try { await callOpenAI(); } catch (error) { logger.error('OpenAI API 호출 실패', error as Error, { model: 'gpt-4o-mini', retryCount: 3, }); } ``` --- ## 로그 레벨 ### DEBUG (개발 전용) ```typescript import { createDebugLogger } from './utils/logger'; const logger = createDebugLogger('debug-service'); logger.debug('함수 호출', { functionName: 'processData', arguments: [1, 2, 3], }); ``` ### INFO (기본) ```typescript logger.info('메시지 수신', { userId: '123456789', messageType: 'text', length: 42, }); ``` ### WARN (주의 필요) ```typescript logger.warn('Circuit breaker 경고', { failureCount: 2, threshold: 3, }); ``` ### ERROR (복구 가능) ```typescript try { await apiCall(); } catch (error) { logger.error('API 호출 실패', error as Error, { endpoint: '/api/data', retryAttempt: 2, }); } ``` ### FATAL (치명적) ```typescript logger.fatal('데이터베이스 연결 실패', error as Error, { database: 'telegram-conversations', connectionString: 'D1', }); ``` --- ## 성능 측정 ### 기본 사용 ```typescript const endTimer = logger.startTimer('데이터 처리 완료', { recordCount: 10000, }); await processData(); endTimer(); // duration이 자동으로 로그에 포함됨 ``` ### OpenAI API 호출 예시 ```typescript const logger = createLogger('openai'); async function generateResponse() { const end = logger.startTimer('AI 응답 생성 완료'); try { const response = await callOpenAI(); end(); // 성공 시 duration 로그 return response; } catch (error) { logger.error('AI 응답 생성 실패', error as Error); throw error; } } ``` ### 출력 예시 ``` ℹ️ [2026-01-19T16:45:23.123Z] INFO [openai] AI 응답 생성 완료 {"model":"gpt-4o-mini"} (1234ms) ``` --- ## 사용자 컨텍스트 ### 사용자 ID 자동 포함 ```typescript const logger = createLogger('telegram'); // 사용자 로거 생성 (userId 자동 포함) const userLogger = logger.withUser('821596605'); userLogger.info('메시지 수신', { messageType: 'text', length: 42, }); userLogger.info('AI 응답 전송', { responseLength: 150, }); ``` ### 출력 예시 ```json { "timestamp": "2026-01-19T16:45:23.123Z", "level": "INFO", "service": "telegram", "message": "메시지 수신", "userId": "821596605", "context": { "messageType": "text", "length": 42 } } ``` --- ## 출력 형식 ### 개발 환경 (human-readable) ``` ℹ️ [2026-01-19T16:45:23.123Z] INFO [openai] AI 응답 생성 시작 {"userId":"123","model":"gpt-4"} ⚠️ [2026-01-19T16:45:25.456Z] WARN [openai] API 응답 지연 {"responseTime":3500} ❌ [2026-01-19T16:45:27.789Z] ERROR [openai] API 호출 실패 {"retryCount":3} Error: Error: Connection timeout Stack: Error: Connection timeout at callOpenAI (openai-service.ts:45:11) ``` ### 프로덕션 (JSON) ```json { "timestamp": "2026-01-19T16:45:23.123Z", "level": "INFO", "service": "openai", "message": "AI 응답 생성 시작", "context": { "userId": "123", "model": "gpt-4" } } ``` ```json { "timestamp": "2026-01-19T16:45:27.789Z", "level": "ERROR", "service": "openai", "message": "API 호출 실패", "context": { "retryCount": 3 }, "error": { "name": "Error", "message": "Connection timeout", "stack": "Error: Connection timeout\n at callOpenAI (openai-service.ts:45:11)" } } ``` --- ## 마이그레이션 가이드 ### 기존 코드 ```typescript // ❌ 기존: console.log console.log('[OpenAI] AI 응답 생성 시작'); console.log('[OpenAI] tool_calls:', JSON.stringify(toolCalls)); console.error('[OpenAI] API 호출 실패:', error); ``` ### 새 코드 ```typescript // ✅ 신규: createLogger const logger = createLogger('openai'); logger.info('AI 응답 생성 시작'); logger.debug('tool_calls 수신', { toolCalls }); logger.error('API 호출 실패', error as Error, { model: 'gpt-4o-mini' }); ``` ### 서비스별 변환 예시 #### openai-service.ts ```typescript // Before console.log('[OpenAI] tool_calls:', assistantMessage.tool_calls); console.error('[OpenAI] Circuit breaker open:', error.message); // After const logger = createLogger('openai'); logger.debug('tool_calls 수신', { toolCalls: assistantMessage.tool_calls }); logger.error('Circuit breaker 열림', error as Error); ``` #### deposit-agent.ts ```typescript // Before console.log(`[DepositAgent] Function call: ${funcName}`, funcArgs); console.error('[DepositAgent] Error:', error); // After const logger = createLogger('deposit'); logger.info('Function call 실행', { functionName: funcName, arguments: funcArgs }); logger.error('예치금 처리 실패', error as Error); ``` #### services/notification.ts ```typescript // Before console.log('[Notification] 관리자 ID가 설정되지 않아 알림을 건너뜁니다.'); console.error('[Notification] 알림 전송 중 오류 발생:', error); // After const logger = createLogger('notification'); logger.warn('관리자 ID 미설정', { notificationType: type }); logger.error('알림 전송 실패', error as Error, { type, service: details.service }); ``` --- ## Best Practices ### 1. 서비스별 로거 생성 ```typescript // ✅ Good: 서비스별 로거 const openaiLogger = createLogger('openai'); const telegramLogger = createLogger('telegram'); const depositLogger = createLogger('deposit'); // ❌ Bad: 로거 재사용 const logger = createLogger('app'); ``` ### 2. 컨텍스트 정보 포함 ```typescript // ✅ Good: 컨텍스트 포함 logger.info('메시지 수신', { userId: '123', messageType: 'text', length: 42, }); // ❌ Bad: 메시지에 직접 포함 logger.info(`메시지 수신: userId=123, type=text, length=42`); ``` ### 3. 에러 객체 전달 ```typescript // ✅ Good: 에러 객체 전달 try { await apiCall(); } catch (error) { logger.error('API 호출 실패', error as Error, { endpoint: '/api/data' }); } // ❌ Bad: 에러를 문자열로 변환 try { await apiCall(); } catch (error) { logger.error(`API 호출 실패: ${error}`); } ``` ### 4. 성능 측정 활용 ```typescript // ✅ Good: 타이머 사용 const end = logger.startTimer('데이터 처리 완료'); await processData(); end(); // ❌ Bad: 수동 계산 const start = Date.now(); await processData(); logger.info(`데이터 처리 완료 (${Date.now() - start}ms)`); ``` ### 5. 민감 정보 제외 ```typescript // ✅ Good: 민감 정보 제외 logger.info('API 호출', { endpoint: '/api/data', method: 'POST', }); // ❌ Bad: API 키, 비밀번호 등 포함 logger.info('API 호출', { endpoint: '/api/data', apiKey: env.OPENAI_API_KEY, // ❌ 절대 금지 password: userPassword, // ❌ 절대 금지 }); ``` ### 6. 로그 레벨 적절히 사용 ```typescript // ✅ Good: 레벨에 맞는 로그 logger.debug('디버그 정보', { details }); // 개발 전용 logger.info('일반 정보', { status: 'ok' }); // 정상 동작 logger.warn('경고', { delayMs: 3500 }); // 주의 필요 logger.error('에러', error, { context }); // 복구 가능 에러 logger.fatal('치명적 에러', error); // 시스템 장애 // ❌ Bad: 모든 로그를 info로 logger.info('디버그 정보'); // ❌ debug 사용 logger.info('경고'); // ❌ warn 사용 logger.info('에러 발생'); // ❌ error 사용 ``` --- ## 환경 설정 ### 환경 변수 `wrangler.toml` 또는 환경 변수로 설정: ```toml [vars] ENVIRONMENT = "production" # 또는 "development" ``` ### 로그 레벨 제어 ```typescript // 프로덕션: INFO 레벨 이상만 출력 const logger = createLogger('service', env); // 개발: DEBUG 레벨부터 모든 로그 출력 const logger = createDebugLogger('service'); ``` --- ## 실제 사용 예시 ### OpenAI 서비스 ```typescript const logger = createLogger('openai', env); export async function generateOpenAIResponse( env: Env, userMessage: string, systemPrompt: string ): Promise { const userLogger = logger.withUser(telegramUserId || 'unknown'); const end = logger.startTimer('AI 응답 생성 완료'); try { userLogger.info('AI 응답 생성 시작', { messageLength: userMessage.length, model: 'gpt-4o-mini', }); const response = await callOpenAI(apiKey, messages, selectedTools); userLogger.info('Function Calling 실행', { toolName: toolCall.function.name, arguments: toolCall.function.arguments, }); end(); return response; } catch (error) { if (error instanceof CircuitBreakerError) { userLogger.error('Circuit breaker 열림', error, { service: 'openai', }); } else if (error instanceof RetryError) { userLogger.error('재시도 실패', error, { maxRetries: 3, }); } else { userLogger.error('AI 응답 생성 실패', error as Error); } throw error; } } ``` ### 예치금 서비스 ```typescript const logger = createLogger('deposit', env); export async function matchPendingDeposit( db: D1Database, notification: BankNotification, env: Env ): Promise { logger.info('입금 매칭 시작', { depositorName: notification.depositorName, amount: notification.amount, bank: notification.bankName, }); const end = logger.startTimer('입금 매칭 완료'); try { const result = await db.prepare(/* ... */).first(); if (!result) { logger.info('매칭되는 pending 거래 없음'); return false; } logger.info('매칭 발견', { transactionId: pendingTx.id, userId: pendingTx.user_id, }); await db.batch([/* ... */]); logger.info('매칭 완료', { transactionId: pendingTx.id, newBalance: currentBalance + notification.amount, }); end(); return true; } catch (error) { logger.error('DB 업데이트 실패', error as Error, { transactionId: pendingTx?.id, }); throw error; } } ``` --- ## 문제 해결 ### Q: 로그가 출력되지 않습니다 **A:** 로그 레벨 확인: ```typescript // DEBUG 레벨은 개발 환경에서만 출력됨 const logger = createDebugLogger('service'); // DEBUG 포함 const logger = createLogger('service'); // INFO 이상만 ``` ### Q: 프로덕션에서 JSON이 아닌 텍스트 로그가 출력됩니다 **A:** 환경 변수 확인: ```typescript // wrangler.toml [vars] ENVIRONMENT = "production" ``` ### Q: 에러 스택이 출력되지 않습니다 **A:** 에러 객체를 전달하세요: ```typescript // ✅ Good logger.error('실패', error as Error); // ❌ Bad logger.error('실패', undefined, { error: error.message }); ``` --- ## 추가 자료 - **구현 파일**: `/src/utils/logger.ts` - **테스트 예시**: `/src/utils/__test__/logger.test.ts` - **CLAUDE.md**: 개발자 가이드 (Code Style & Conventions 섹션)