import createDebuggerLogger from '../lib/debugLogger'; import { Format } from './logger-format'; import { LoggerTransport } from './LoggerTransport'; import { LEVEL } from '../constants'; const debugLogger = createDebuggerLogger('iot-logger'); export type LogType = 'emerg' | 'error' | 'warn' | 'info' | 'debug'; export const LogLevel: Record = { emerg: 0, error: 1, warn: 2, info: 3, debug: 4, }; export interface LogInfo { level: LogType; message: any; splat: unknown[]; [LEVEL]: LogType; [key: string]: unknown; } type LoggerFunc = (...args: unknown[]) => void; export interface LoggerOptions { format?: Format; level?: LogType; transports?: LoggerTransport | LoggerTransport[]; } export class Logger { emerg: LoggerFunc; error: LoggerFunc; warn: LoggerFunc; info: LoggerFunc; debug: LoggerFunc; readonly level: LogType; readonly levels = LogLevel; protected format: Format; protected readonly transports?: LoggerTransport[] = []; constructor({ format, level, transports, }: LoggerOptions = {}) { this.updateFormat(format); if (level && typeof this.levels[level] !== 'number') { const levelsStr = Object.keys(this.levels).toString(); debugLogger.warn(`level: ${level} not exist in ${levelsStr}`); this.level = 'info'; } else { this.level = level || 'info'; } this.add(transports); this.emerg = this.createLogFunc('emerg'); this.error = this.createLogFunc('error'); this.warn = this.createLogFunc('warn'); this.info = this.createLogFunc('info'); this.debug = this.createLogFunc('debug'); } add(transport, options = { force: false, }) { if ( (transport instanceof LoggerTransport || options.force) && !this.transports.includes(transport) ) { transport.load(this); this.transports.push(transport); } } remove(transport) { const index = this.transports.findIndex(item => item === transport); if (index !== -1) { const [transport] = this.transports.splice(index, 1); transport.close(); } } updateFormat(format: Format) { if (format instanceof Format) { this.format = format; } } private log(level: LogType, msg: unknown, splat: unknown[]) { const info = { level, message: msg, splat, // 预防level在执行format时被串改 [LEVEL]: level, }; this.write(info); } private createLogFunc(level: LogType): LoggerFunc { // 这里不能使用箭头函数,否则this上下文会绑定死当前的Logger实例,影响InsightLogger的继承 return function (msg, ...splat) { try { return this.log(level, msg, splat); } catch (err) { debugLogger.error('exec log fail', err); } }; } private write(info: LogInfo) { if (!this.transports.length) { return debugLogger.warn('no transport to consume log'); } let formatInfo: LogInfo | boolean = info; if (this.format) { try { formatInfo = this.format.transform(formatInfo); } catch (err) { debugLogger.error('exec format err', err); return; } } if (formatInfo) { this.transports.forEach((transport) => { const transportName = transport.name; const canWrite = transport.write(formatInfo); if (!canWrite) { debugLogger.warn(`The ${transportName}'s pending pool is full`); } }); } } }