import errorCodes from '../lib/error'; import createDebuggerLogger from '../lib/debugLogger'; import { isPlainObject } from '../utils'; import { createLogger, format, Logger, LogLevel, combine, Format, LogInfo, LogType } from '../logger'; import { ConsoleTransport, SPEC_CONSOLE_ARGS } from '../internal-transport'; import { QuickLogger } from './QuickLogger'; const debugLogger = createDebuggerLogger('iot-logger-insight'); const { ERR_INVALID_ARG, } = errorCodes; // 占位符 const EVENT_PH = '\\*'; const EVENT_PH_REG = new RegExp(`${EVENT_PH}$`); export type UnknownRecord = Record; export interface InsightLoggerOptions { derivedLevel?: number; level?: LogType; getReportData?: () => UnknownRecord; useConsoleTransport?: boolean; } export abstract class InsightLogger extends Logger { readonly parent: InsightLogger; abstract updateGetReportData(getReportData: InsightLoggerOptions['getReportData']): void; abstract derive(getReportData: InsightLoggerOptions['getReportData']): InsightLogger; abstract destroy(): void; abstract disableDefaultConsoleTransport(): void; abstract enableDefaultConsoleTransport(): void; abstract event(event: string): QuickLogger; abstract setDefaultConsoleTransportFilter(fn: (logInfo: UnknownRecord) => boolean): void; } // 最大派生深度 const MAX_DERIVED_LEVEL = 5; // 报表里需要深度拷贝的属性 const DEEP_COPY_PROPS = { metric: true, context: true, }; const copyProps = ( originObj: UnknownRecord, targetObj: UnknownRecord, deep = 0, ): UnknownRecord => { if (!isPlainObject(originObj)) { return isPlainObject(targetObj) ? targetObj : {}; } if (!isPlainObject(targetObj)) { return originObj; } Object.keys(targetObj).forEach((key) => { const originVal = originObj[key]; const targetVal = targetObj[key]; if (DEEP_COPY_PROPS[key] && deep === 0) { originObj[key] = copyProps(originVal as UnknownRecord, targetVal as UnknownRecord, 1); } else if ( // 针对event字段做一个特殊逻辑,如果event以'*'结尾,可提供占位符功能,例如origin.event = 'CGI.*',target.event = 'tokenApi',合并后为CGI.tokenApi deep === 0 && key === 'event' && typeof originVal === 'string' && typeof targetVal === 'string' && originVal.match(EVENT_PH_REG)) { originObj[key] = originVal.replace(EVENT_PH_REG, targetVal); } else { originObj[key] = targetVal; } }); return originObj; }; /** * @description 格式化打印的log * */ const formatLog = (curLog: LogInfo): UnknownRecord => { const [extraArg1, extraArg2] = curLog.splat; const event = curLog.message; let curReportData: UnknownRecord; // e.g. insightLogger.info({ event: 'CGI', message: 'success' }) if (isPlainObject(event)) { curReportData = event; } else { // e.g. insightLogger.info('CGI', { data: requestData, message: 'success' }) if (isPlainObject(extraArg1)) { curReportData = extraArg1 as UnknownRecord; } else if (isPlainObject(extraArg2)) { // e.g. insightLogger.info('CGI', 'success', { data: requestData }) curReportData = extraArg2 as UnknownRecord; curReportData.message = extraArg1 ? String(extraArg1) : ''; } else { // e.g. insightLogger.info('CGI', 'success') curReportData = { message: extraArg1 ? String(extraArg1) : '', }; } curReportData.event = String(event); } if (typeof curReportData.at !== 'number') { curReportData.at = +new Date(); } curReportData.level = LogLevel[curLog.level]; // 如果当前的level是error,并且error字段的值是类Error的实例,需要将message以及stack提取出来,否则JSON.stringify后是一个空对象 if (curReportData.level === LogLevel.error && curReportData.error instanceof Error) { curReportData.error = { message: curReportData.error.message, stack: curReportData.error.stack, }; } return curReportData; }; export const createInsightLogger = ({ level, useConsoleTransport = true, ...opts }: InsightLoggerOptions = {}): InsightLogger => { const logger = createLogger({ level, }); let consoleTransport: ConsoleTransport; let isConsoleTransportDisabled = false; let consoleTransportFormat: Format; // 默认内置ConsoleTransport if (useConsoleTransport) { consoleTransportFormat = combine( // 用来控制是否使用ConsoleTransport打印 format((info) => { if (isConsoleTransportDisabled) { return false; } else { return info; } })(), format((info) => { const { level, } = info; const { event, message = '', data = '', error, } = info.message; info[SPEC_CONSOLE_ARGS] = [`[${event}]`, message, level === 'error' ? error : data, info.message]; return info; })(), ); consoleTransport = new ConsoleTransport({ level: 'debug', format: consoleTransportFormat, }); logger.add(consoleTransport); } // 中转类 - 让每一个InsightLogger实例上的原型都指向logger const Indirect = function () { /**/ }; Indirect.prototype = logger; // @ts-ignore class InsightLoggerFactory extends Indirect { parent: InsightLoggerFactory; protected derivedLevel: InsightLoggerOptions['derivedLevel']; protected getReportData: InsightLoggerOptions['getReportData']; constructor({ derivedLevel, getReportData, }: InsightLoggerOptions) { super(); this.initFormat(); if (typeof derivedLevel === 'number' && derivedLevel >= 0) { this.derivedLevel = derivedLevel; } else { this.derivedLevel = Infinity; } this.updateGetReportData(getReportData); } updateGetReportData(getReportData: InsightLoggerOptions['getReportData']) { if (typeof getReportData === 'function') { this.getReportData = getReportData; } } derive(getReportData: InsightLoggerOptions['getReportData']) { if (typeof getReportData !== 'function') { throw new ERR_INVALID_ARG('getReportData', 'function'); } // 限制一下派生的深度 if (this.derivedLevel + 1 >= MAX_DERIVED_LEVEL) { throw new Error(`derived level exceed, current: ${this.derivedLevel + 1}, max: ${MAX_DERIVED_LEVEL}`); } const derivedInstance = new InsightLoggerFactory({ derivedLevel: this.derivedLevel + 1, getReportData, }); derivedInstance.parent = this; return derivedInstance; } destroy() { this.parent = null; } disableDefaultConsoleTransport() { if (consoleTransport) { isConsoleTransportDisabled = true; } } enableDefaultConsoleTransport() { if (consoleTransport) { isConsoleTransportDisabled = false; } } setDefaultConsoleTransportFilter(fn: (logInfo: UnknownRecord) => boolean) { if (!consoleTransport || !consoleTransportFormat || typeof fn !== 'function') { return; } consoleTransport.format = combine( format((info) => { const { message: insightLogInfo } = info; if (fn(insightLogInfo)) { return info; } else { return false; } })(), consoleTransportFormat, ); } event(name) { if (typeof name !== 'string' || !name) { throw new ERR_INVALID_ARG('name', 'string'); } return new QuickLogger(this, { event: name }); } private initFormat() { const fn = format((info) => { let reportData = formatLog(info); reportData = this.mergeLogInfo(reportData); if (!reportData) { debugLogger.warn('initFormat() reportData is null'); return false; } info.message = reportData; return info; }); // updateFormat - Logger的方法 // @ts-ignore this.updateFormat(fn()); } /** * @description 沿着派生链一直合并上报数据 * */ private mergeLogInfo(curReportData: UnknownRecord): UnknownRecord { const reportDataChain: UnknownRecord[] = []; reportDataChain.push(curReportData); // 沿着派生链去获取公共的reportData // eslint-disable-next-line @typescript-eslint/no-this-alias let target: InsightLoggerFactory = this; while (target) { if (target.getReportData) { const res = target.getReportData(); if (isPlainObject(res)) { reportDataChain.push(res); } } target = target.parent; } // 合并数据 let newReportData = null; for (let i = reportDataChain.length - 1; i >= 0; i--) { newReportData = copyProps(newReportData, reportDataChain[i], 0); } return newReportData; } } // @ts-ignore return new InsightLoggerFactory({ ...opts, derivedLevel: 0, }) as InsightLogger; };