UNPKG

@racla-dev/node-iris

Version:

TypeScript port of Python irispy-client module for KakaoTalk bot development

242 lines 10.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.winstonLogger = exports.defaultLogger = exports.Logger = void 0; const lossless_json_1 = require("lossless-json"); const path_1 = __importDefault(require("path")); const winston_1 = __importDefault(require("winston")); // 순환 참조를 처리하는 stringify replacer 함수 function getCircularReplacer() { const seen = new WeakSet(); return (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular]'; } seen.add(value); } // 함수는 직렬화하지 않음 if (typeof value === 'function') { return '[Function]'; } // BigInt는 문자열로 변환 if (typeof value === 'bigint') { return value.toString(); } // undefined는 null로 변환 if (value === undefined) { return null; } return value; }; } // 로그 포맷 설정 const logFormat = winston_1.default.format.combine(winston_1.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss', }), winston_1.default.format.errors({ stack: true }), winston_1.default.format.printf(({ timestamp, level, message, stack, ...meta }) => { let log = `[${timestamp}] [${level.toUpperCase()}] ${message}`; // 메타데이터가 있으면 추가 (안전한 stringify 사용) if (Object.keys(meta).length > 0) { try { log += ' ' + (0, lossless_json_1.stringify)(meta, getCircularReplacer()); } catch (error) { log += ' [Medata serialization error]'; } } // 스택 트레이스가 있으면 추가 if (stack) { log += '\n' + stack; } return log; })); // 기본 로거 생성 const logger = winston_1.default.createLogger({ level: process.env.LOG_LEVEL || 'info', format: logFormat, transports: [ // 콘솔 출력 - 컬러 포맷 분리 new winston_1.default.transports.Console({ format: winston_1.default.format.combine(winston_1.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss', }), winston_1.default.format.colorize(), winston_1.default.format.printf(({ timestamp, level, message, stack, ...meta }) => { let log = `[${timestamp}] [${level}] ${message}`; // 메타데이터가 있으면 추가 (안전한 stringify 사용) if (Object.keys(meta).length > 0) { try { log += ' ' + (0, lossless_json_1.stringify)(meta, getCircularReplacer()); } catch (error) { log += ' [메타데이터 직렬화 오류]'; } } // 스택 트레이스가 있으면 추가 if (stack) { log += '\n' + stack; } return log; })), }), // 파일 출력 - 모든 로그 (채팅 로그 제외) new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'app.log'), maxsize: 5242880, // 5MB maxFiles: 5, format: winston_1.default.format.combine(winston_1.default.format((info) => { // 채팅 로그는 파일에 저장하지 않음 if (typeof info.message === 'string' && info.message.includes('[MSG]')) { return false; } return info; })(), logFormat), }), // 파일 출력 - 에러만 new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'error.log'), level: 'error', maxsize: 5242880, // 5MB maxFiles: 5, }), ], // 처리되지 않은 예외 캐치 exceptionHandlers: [ new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'exceptions.log'), }), ], // 처리되지 않은 Promise rejection 캐치 rejectionHandlers: [ new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'rejections.log'), }), ], }); exports.winstonLogger = logger; // 로거 래퍼 클래스 class Logger { constructor(context = 'App', options = {}) { this.context = context; this.saveChatLogs = options.saveChatLogs || false; this.logLevel = options.logLevel || 'info'; // 개별 winston 인스턴스 생성 (logLevel 적용) this.winstonLogger = winston_1.default.createLogger({ level: this.logLevel, format: logFormat, transports: [ // 콘솔 출력 - 컬러 포맷 분리 new winston_1.default.transports.Console({ format: winston_1.default.format.combine(winston_1.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss', }), winston_1.default.format.colorize(), winston_1.default.format.printf(({ timestamp, level, message, stack, ...meta }) => { let log = `[${timestamp}] [${level}] ${message}`; // 메타데이터가 있으면 추가 (안전한 stringify 사용) if (Object.keys(meta).length > 0) { try { log += ' ' + (0, lossless_json_1.stringify)(meta, getCircularReplacer()); } catch (error) { log += ' [메타데이터 직렬화 오류]'; } } // 스택 트레이스가 있으면 추가 if (stack) { log += '\n' + stack; } return log; })), }), // 파일 출력 - 모든 로그 (채팅 로그 제외) new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'app.log'), maxsize: 5242880, // 5MB maxFiles: 5, format: winston_1.default.format.combine(winston_1.default.format((info) => { // 채팅 로그는 파일에 저장하지 않음 if (typeof info.message === 'string' && info.message.includes('[MSG]')) { return false; } return info; })(), logFormat), }), // 파일 출력 - 에러만 new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'error.log'), level: 'error', maxsize: 5242880, // 5MB maxFiles: 5, }), ], // 개별 로거는 예외 핸들러를 등록하지 않음 (메모리 누수 방지) silent: false, }); // 채팅 로그 저장이 활성화된 경우 별도 로거 생성 if (this.saveChatLogs) { this.chatLogger = winston_1.default.createLogger({ level: this.logLevel, format: logFormat, transports: [ new winston_1.default.transports.File({ filename: path_1.default.join(process.cwd(), 'logs', 'chat.log'), maxsize: 5242880, // 5MB maxFiles: 5, }), ], }); } } formatMessage(message) { return `[${this.context}] ${message}`; } error(message, error, meta) { if (error instanceof Error) { this.winstonLogger.error(this.formatMessage(message), { error: error.message, stack: error.stack, ...meta, }); } else if (error) { this.winstonLogger.error(this.formatMessage(message), { error, ...meta }); } else { this.winstonLogger.error(this.formatMessage(message), meta); } } warn(message, meta) { this.winstonLogger.warn(this.formatMessage(message), meta); } info(message, meta) { this.winstonLogger.info(this.formatMessage(message), meta); } debug(message, meta) { this.winstonLogger.debug(this.formatMessage(message), meta); } // 채팅 관련 특별한 로그 메서드들 chat(type, roomName, senderName, message) { const logMessage = this.formatMessage(`[${type.replace(' | ', '] [')}] [${roomName}] ${senderName}: ${message}`); // 콘솔에는 항상 출력 this.winstonLogger.info(logMessage); // 파일 저장은 옵션에 따라 if (this.saveChatLogs && this.chatLogger) { this.chatLogger.info(logMessage); } } command(roomName, senderName, command) { this.winstonLogger.info(this.formatMessage(`[CMD] [${roomName}] ${senderName} executed command: ${command}`)); } newMember(roomName, memberName) { this.winstonLogger.info(this.formatMessage(`[NEWMEM] [${roomName}]: ${memberName}`)); } delMember(roomName, memberName) { this.winstonLogger.info(this.formatMessage(`[DELMEM] [${roomName}]: ${memberName}`)); } } exports.Logger = Logger; // 기본 로거 인스턴스 생성 exports.defaultLogger = new Logger('IrisBot'); exports.default = Logger; //# sourceMappingURL=logger.js.map