// API 호출 로깅 유틸리티 // 개발 중 API 요청/응답을 추적하고 디버깅하기 위한 로거 /** * 로그 레벨 */ export enum LogLevel { DEBUG = 'DEBUG', INFO = 'INFO', WARN = 'WARN', ERROR = 'ERROR', } /** * API 로그 항목 인터페이스 */ interface ApiLogEntry { timestamp: string; level: LogLevel; method: string; url: string; requestData?: any; responseData?: any; statusCode?: number; error?: Error; duration?: number; } /** * API Logger 클래스 */ class ApiLogger { private enabled: boolean; private logs: ApiLogEntry[] = []; private maxLogs: number = 100; constructor() { // 개발 환경에서만 로깅 활성화 this.enabled = process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_API_LOGGING === 'true'; } /** * 로깅 활성화 여부 설정 */ setEnabled(enabled: boolean) { this.enabled = enabled; } /** * 로그 최대 개수 설정 */ setMaxLogs(max: number) { this.maxLogs = max; } /** * API 요청 시작 로그 */ logRequest(method: string, url: string, data?: any): number { if (!this.enabled) return Date.now(); const startTime = Date.now(); const entry: ApiLogEntry = { timestamp: new Date().toISOString(), level: LogLevel.INFO, method, url, requestData: data, }; console.group(`🚀 API Request: ${method} ${url}`); console.log('⏰ Time:', entry.timestamp); if (data) { console.log('📤 Request Data:', data); } console.groupEnd(); this.addLog(entry); return startTime; } /** * API 응답 성공 로그 */ logResponse( method: string, url: string, statusCode: number, data: any, startTime: number ) { if (!this.enabled) return; const duration = Date.now() - startTime; const entry: ApiLogEntry = { timestamp: new Date().toISOString(), level: LogLevel.INFO, method, url, responseData: data, statusCode, duration, }; console.group(`✅ API Response: ${method} ${url}`); console.log('⏰ Time:', entry.timestamp); console.log('📊 Status:', statusCode); console.log('⏱️ Duration:', `${duration}ms`); console.log('📥 Response Data:', data); console.groupEnd(); this.addLog(entry); } /** * API 에러 로그 */ logError( method: string, url: string, error: Error, statusCode?: number, startTime?: number ) { if (!this.enabled) return; const duration = startTime ? Date.now() - startTime : undefined; const entry: ApiLogEntry = { timestamp: new Date().toISOString(), level: LogLevel.ERROR, method, url, error, statusCode, duration, }; console.group(`❌ API Error: ${method} ${url}`); console.log('⏰ Time:', entry.timestamp); if (statusCode) { console.log('📊 Status:', statusCode); } if (duration) { console.log('⏱️ Duration:', `${duration}ms`); } console.error('💥 Error:', error); console.groupEnd(); this.addLog(entry); } /** * 경고 로그 */ logWarning(message: string, data?: any) { if (!this.enabled) return; const entry: ApiLogEntry = { timestamp: new Date().toISOString(), level: LogLevel.WARN, method: 'WARN', url: message, requestData: data, }; console.warn(`⚠️ API Warning: ${message}`, data); this.addLog(entry); } /** * 디버그 로그 */ logDebug(message: string, data?: any) { if (!this.enabled) return; const entry: ApiLogEntry = { timestamp: new Date().toISOString(), level: LogLevel.DEBUG, method: 'DEBUG', url: message, requestData: data, }; console.debug(`🔍 API Debug: ${message}`, data); this.addLog(entry); } /** * 로그 추가 및 최대 개수 관리 */ private addLog(entry: ApiLogEntry) { this.logs.push(entry); if (this.logs.length > this.maxLogs) { this.logs.shift(); // 가장 오래된 로그 제거 } } /** * 모든 로그 조회 */ getLogs(): ApiLogEntry[] { return [...this.logs]; } /** * 특정 레벨의 로그만 조회 */ getLogsByLevel(level: LogLevel): ApiLogEntry[] { return this.logs.filter((log) => log.level === level); } /** * 에러 로그만 조회 */ getErrors(): ApiLogEntry[] { return this.getLogsByLevel(LogLevel.ERROR); } /** * 모든 로그 삭제 */ clearLogs() { this.logs = []; console.log('🗑️ API logs cleared'); } /** * 로그 통계 조회 */ getStats() { const stats = { total: this.logs.length, byLevel: { [LogLevel.DEBUG]: 0, [LogLevel.INFO]: 0, [LogLevel.WARN]: 0, [LogLevel.ERROR]: 0, }, averageDuration: 0, errorRate: 0, }; let totalDuration = 0; let countWithDuration = 0; this.logs.forEach((log) => { stats.byLevel[log.level]++; if (log.duration) { totalDuration += log.duration; countWithDuration++; } }); if (countWithDuration > 0) { stats.averageDuration = totalDuration / countWithDuration; } if (stats.total > 0) { stats.errorRate = (stats.byLevel[LogLevel.ERROR] / stats.total) * 100; } return stats; } /** * 로그 통계 출력 */ printStats() { const stats = this.getStats(); console.group('📊 API Logger Statistics'); console.log('Total Logs:', stats.total); console.log('By Level:', stats.byLevel); console.log( 'Average Duration:', `${stats.averageDuration.toFixed(2)}ms` ); console.log('Error Rate:', `${stats.errorRate.toFixed(2)}%`); console.groupEnd(); } /** * 로그를 JSON으로 내보내기 */ exportLogs(): string { return JSON.stringify(this.logs, null, 2); } /** * 로그를 콘솔에 테이블로 출력 */ printLogsAsTable() { if (this.logs.length === 0) { console.log('📭 No logs available'); return; } const tableData = this.logs.map((log) => ({ Timestamp: log.timestamp, Level: log.level, Method: log.method, URL: log.url, Status: log.statusCode || '-', Duration: log.duration ? `${log.duration}ms` : '-', Error: log.error?.message || '-', })); console.table(tableData); } } // 싱글톤 인스턴스 생성 export const apiLogger = new ApiLogger(); /** * API 호출 래퍼 함수 * 자동으로 요청/응답을 로깅합니다 */ export async function loggedFetch( method: string, url: string, options?: RequestInit ): Promise { const startTime = apiLogger.logRequest(method, url, options?.body); try { const response = await fetch(url, { ...options, method, }); const data = await response.json(); apiLogger.logResponse(method, url, response.status, data, startTime); if (!response.ok) { throw new Error(data.message || 'API request failed'); } return data; } catch (error) { apiLogger.logError(method, url, error as Error, undefined, startTime); throw error; } } // 개발 도구를 window에 노출 (브라우저 콘솔에서 사용 가능) if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') { (window as any).apiLogger = apiLogger; console.log( '💡 API Logger is available in console as "apiLogger"\n' + ' - apiLogger.getLogs() - View all logs\n' + ' - apiLogger.getErrors() - View errors only\n' + ' - apiLogger.printStats() - View statistics\n' + ' - apiLogger.printLogsAsTable() - View logs as table\n' + ' - apiLogger.clearLogs() - Clear all logs' ); }