export enum LogLevel { DEBUG = 'debug', INFO = 'info', WARN = 'warn', ERROR = 'error' } export interface LogEntry { timestamp: string; level: LogLevel; message: string; data?: any; userId?: string; sessionId?: string; userAgent?: string; url?: string; } export interface UserAction { action: string; component: string; data?: any; timestamp: string; sessionId: string; } class Logger { private logs: LogEntry[] = []; private userActions: UserAction[] = []; private maxLogs = 1000; private sessionId: string; constructor() { this.sessionId = this.generateSessionId(); this.setupGlobalErrorHandler(); } private generateSessionId(): string { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private setupGlobalErrorHandler() { window.addEventListener('error', (event) => { this.error('Global error', { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error?.stack }); }); window.addEventListener('unhandledrejection', (event) => { this.error('Unhandled promise rejection', { reason: event.reason, promise: event.promise }); }); } private addLog(level: LogLevel, message: string, data?: any) { const logEntry: LogEntry = { timestamp: new Date().toISOString(), level, message, data, sessionId: this.sessionId, userAgent: navigator.userAgent, url: window.location.href }; this.logs.push(logEntry); // 로그 개수 제한 if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(-this.maxLogs); } // 콘솔에 출력 console[level](`[${level.toUpperCase()}] ${message}`, data || ''); // 에러 로그는 서버로 전송 if (level === LogLevel.ERROR) { this.sendErrorToServer(logEntry); } } private async sendErrorToServer(logEntry: LogEntry) { try { await fetch('/api/logs/error', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(logEntry) }); } catch (error) { console.error('Failed to send error log to server:', error); } } debug(message: string, data?: any) { this.addLog(LogLevel.DEBUG, message, data); } info(message: string, data?: any) { this.addLog(LogLevel.INFO, message, data); } warn(message: string, data?: any) { this.addLog(LogLevel.WARN, message, data); } error(message: string, data?: any) { this.addLog(LogLevel.ERROR, message, data); } // 사용자 행동 추적 trackUserAction(action: string, component: string, data?: any) { const userAction: UserAction = { action, component, data, timestamp: new Date().toISOString(), sessionId: this.sessionId }; this.userActions.push(userAction); // 사용자 행동 개수 제한 if (this.userActions.length > this.maxLogs) { this.userActions = this.userActions.slice(-this.maxLogs); } this.debug(`User action: ${action}`, { component, data }); } // 로그 내보내기 exportLogs(): LogEntry[] { return [...this.logs]; } // 사용자 행동 내보내기 exportUserActions(): UserAction[] { return [...this.userActions]; } // 로그 초기화 clearLogs() { this.logs = []; this.userActions = []; } // 세션 ID 가져오기 getSessionId(): string { return this.sessionId; } // 로그 통계 getLogStats() { const stats = { total: this.logs.length, byLevel: { debug: 0, info: 0, warn: 0, error: 0 }, userActions: this.userActions.length }; this.logs.forEach(log => { stats.byLevel[log.level]++; }); return stats; } } export const logger = new Logger();