import { tsMd5 } from '../utils/md5' import { tsTime } from '../time/time' import { tsDate } from '../utils/date' import { tsString } from '../utils/string' /** * 日志模块 */ export module tsLog { /** * 日志级别 */ export enum Level { None, Error, Warn, Info, Debug, Trace } /** * 日志记录器设置 */ export type InitOptions = { /** * 输出级别 */ level: Level /** * 上报级别 */ reportLevel: Level /** * 关键字 */ tag: string /** * 补充关键字 */ addTag: string /** * 日期关键字,默认: false */ dateTag: boolean /** * 重写日志输出方法,默认:console.log * @param message * @returns */ substitute: (message: string) => void /** * 设置上报方法,默认: null * @param message * @returns */ reportCaller: (message: string) => void /** * 是否显示耗时调试信息(默认false) */ isShowDebugTimeUse?: boolean } export enum ExecTimeLevel { Warn = 20, Error = 50 } /** * 日志记录器 */ export class Logger { private static levelTags = ['NONE', 'ERRO', 'WARN', 'INFO', 'DEBU', 'TRAC'] private static options: InitOptions = { level: Level.Trace, reportLevel: Level.Error, tag: '', addTag: '', dateTag: false, substitute: null, reportCaller: null, isShowDebugTimeUse: false } /** * 设置日志记录器 * @param options InitOptions */ static setOptions(options: InitOptions): void { this.options = options if (!tsString.isNullOrEmpty(this.options.tag)) { for (let index = 0; index < this.levelTags.length; index++) { this.levelTags[index] = this.levelTags[index] + ' ' + this.options.tag } } } /** * 追加关键字内容 * @param content */ static appendTag(append: string): void { if (!tsString.isNullOrEmpty(append)) { for (let index = 0; index < this.levelTags.length; index++) { this.levelTags[index] = this.levelTags[index] + append } } } /** * 设置补充关键字 * @param content */ static setAddTag(content: string): void { this.options.addTag = content } private static checkLevel(level: Level): boolean { return this.options.level >= level } private static toString(...optionalParams): string { if (!optionalParams || optionalParams.length == 0) { return '' } let str = '' for (let index = 0; index < optionalParams.length; index++) { const element = optionalParams[index] str = str + ' ' + tsString.toString(element) } return str } /** * 优化日志上传过滤器 */ private static reportFilter = new Map() private static report(level: Level, content: string, contentNoDate: string): void { if (this.options.reportLevel < level || !this.options.reportCaller) { return } const hash = tsMd5.hash(contentNoDate) if (this.reportFilter.get(hash)) { return } this.reportFilter.set(hash, true) try { this.options.reportCaller(content) } catch (error) { console.log(content) } } private static log(level: Level, message: any, ...optionalParams: any): void { if (!this.checkLevel(level)) { return } let content = this.levelTags[level] + this.options.addTag + ' ' + tsString.toString(message) if (this.options.dateTag) { content = '[' + tsDate.toLocaleStringMS() + '] ' + content } if (this.options.substitute || this.options.reportCaller) { let contentNew = content if (optionalParams.length > 0) { contentNew = content + ' ' + this.toString(...optionalParams) } this.report(level, contentNew, content) if (this.options.substitute) { this.options.substitute(contentNew) return } } console.log(content, ...optionalParams) } private static logf(level: Level, message: string, ...optionalParams: any) { if (!this.checkLevel(level)) { return } let content = this.levelTags[level] + this.options.addTag + ' ' + tsString.format(message, ...optionalParams) let contentNew = content if (this.options.dateTag) { contentNew = '[' + tsDate.toLocaleStringMS() + '] ' + content } if (this.options.substitute || this.options.reportCaller) { this.report(level, contentNew, content) if (this.options.substitute) { this.options.substitute(contentNew) return } } console.log(contentNew) } /** * Level.Trace级别输出,与console.log同样用法 * @param message * @param optionalParams */ static trace(message: any, ...optionalParams: any[]): void { this.log(Level.Trace, message, ...optionalParams) } /** * Level.Debug级别输出,与console.log同样用法 * @param message * @param optionalParams */ static debug(message: any, ...optionalParams: any[]): void { this.log(Level.Debug, message, ...optionalParams) } /** * Level.Info级别输出,与console.log同样用法 * @param message * @param optionalParams */ static info(message: any, ...optionalParams: any[]): void { this.log(Level.Info, message, ...optionalParams) } /** * Level.Warn级别输出,与console.log同样用法 * @param message * @param optionalParams */ static warn(message: any, ...optionalParams: any[]): void { this.log(Level.Warn, message, ...optionalParams) } /** * Level.Error级别输出,与console.log同样用法 * @param message * @param optionalParams */ static error(message: any, ...optionalParams: any[]): void { this.log(Level.Error, message, ...optionalParams) } /** * Level.Trace级别格式化输出 ("example: hello, world 2023") * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static tracef(message: string, ...optionalParams: any[]): void { this.logf(Level.Trace, message, ...optionalParams) } /** * Level.Debug级别格式化输出 ("example: hello, world 2023") * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static debugf(message: string, ...optionalParams: any[]): void { this.logf(Level.Debug, message, ...optionalParams) } /** * Level.Info级别格式化输出 ("example: hello, world 2023") * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static infof(message: string, ...optionalParams: any[]): void { this.logf(Level.Info, message, ...optionalParams) } /** * Level.Warn级别格式化输出 ("example: hello, world 2023") * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static warnf(message: string, ...optionalParams: any[]): void { this.logf(Level.Warn, message, ...optionalParams) } /** * Level.Error级别格式化输出 ("example: hello, world 2023") * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static errorf(message: string, ...optionalParams: any[]): void { this.logf(Level.Error, message, ...optionalParams) } /** * Level.Trace级别带堆栈输出,与console.log同样用法 * @param message * @param optionalParams */ static traceStack(message: any, ...optionalParams: any[]): void { this.trace(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.trace(name, location) }) } /** * Level.Debug级别带堆栈输出,与console.log同样用法 * @param message * @param optionalParams */ static debugStack(message: any, ...optionalParams: any[]): void { this.debug(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.debug(name, location) }) } /** * Level.Info级别带堆栈输出,与console.log同样用法 * @param message * @param optionalParams */ static infoStack(message: any, ...optionalParams: any[]): void { this.info(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.info(name, location) }) } /** * Level.Warn级别带堆栈输出,与console.log同样用法 * @param message * @param optionalParams */ static warnStack(message: any, ...optionalParams: any[]): void { this.warn(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.warn(name, location) }) } /** * Level.Error级别带堆栈输出,与console.log同样用法 * @param message * @param optionalParams */ static errorStack(message: any, ...optionalParams: any[]): void { this.error(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.error(name, location) }) } /** * Level.Trace级别还堆栈格式化输出 ("example: hello, world 2023" + "\n" + ${stack info}) * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static traceStackf(message: string, ...optionalParams: any[]): void { this.tracef(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.trace(name, location) }) } /** * Level.Debug级别还堆栈格式化输出 ("example: hello, world 2023" + "\n" + ${stack info}) * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static debugStackf(message: string, ...optionalParams: any[]): void { this.debugf(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.debug(name, location) }) } /** * Level.Info级别还堆栈格式化输出 ("example: hello, world 2023" + "\n" + ${stack info}) * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static infoStackf(message: string, ...optionalParams: any[]): void { this.infof(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.info(name, location) }) } /** * Level.Warn级别还堆栈格式化输出 ("example: hello, world 2023" + "\n" + ${stack info}) * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static warnStackf(message: string, ...optionalParams: any[]): void { this.warnf(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.warn(name, location) }) } /** * Level.Error级别还堆栈格式化输出 ("example: hello, world 2023" + "\n" + ${stack info}) * @param message 带格式的字符串("example: {0}, {1} {2}") * @param optionalParams 任意类型参数(["hello", "world", 2023]) */ static errorStackf(message: string, ...optionalParams: any[]): void { this.errorf(message, ...optionalParams) const callstack = new Error().stack.split('\n') callstack.forEach(s => { let matchArray = s.match(/at (.+) ((.+))/) if (!matchArray) return let name = matchArray[1] let location = matchArray[2] this.error(name, location) }) } private static timeUseMap: Map = new Map() /** * debug耗时 * @param from 来源关键字 * @param optionalParams 可变参数 */ static debugTimeUse(from: string, ...optionalParams: any[]): void { if (!this.options.isShowDebugTimeUse) return let beginMs = this.timeUseMap.get(from) let content = '' let content_time = '' let curMs = tsTime.unixMsec() if (!beginMs) { this.timeUseMap.set(from, curMs) content = tsString.format('debugTimeUse {0}', from) content_time = tsString.format('BEGIN:{0}', curMs) } else { this.timeUseMap.delete(from) const diff = curMs - beginMs content = tsString.format('debugTimeUse {0}', from) content_time = tsString.format('ENDED:{0} USE:{1} ms', curMs, diff) } content += ' ' + content_time if (optionalParams.length > 0) { content += ' ' + this.toString(...optionalParams) } switch (this.options.level) { case Level.Trace: this.trace(content) break case Level.Debug: this.debug(content) break case Level.Info: this.info(content) break case Level.Warn: this.warn(content) break default: this.timeUseMap.delete(from) break } } /** * debug耗时 * @param format 格式化字符串 * @param optionalParams 格式化参数 */ static debugTimeUsef(format: string, ...optionalParams: any[]): void { if (!this.options.isShowDebugTimeUse) return const content = tsString.format(format, ...optionalParams) this.debugTimeUse(content) } } }