import path from 'path'; import _util from 'util'; import 'colors'; import _ from 'lodash'; import fs from 'fs-extra'; import { format as dateFormat } from 'date-fns'; import config from './config.ts'; import util from './util.ts'; const isVercelEnv = process.env.VERCEL; class LogWriter { #buffers = []; constructor() { !isVercelEnv && fs.ensureDirSync(config.system.logDirPath); !isVercelEnv && this.work(); } push(content) { const buffer = Buffer.from(content); this.#buffers.push(buffer); } writeSync(buffer) { !isVercelEnv && fs.appendFileSync(path.join(config.system.logDirPath, `/${util.getDateString()}.log`), buffer); } async write(buffer) { !isVercelEnv && await fs.appendFile(path.join(config.system.logDirPath, `/${util.getDateString()}.log`), buffer); } flush() { if(!this.#buffers.length) return; !isVercelEnv && fs.appendFileSync(path.join(config.system.logDirPath, `/${util.getDateString()}.log`), Buffer.concat(this.#buffers)); } work() { if (!this.#buffers.length) return setTimeout(this.work.bind(this), config.system.logWriteInterval); const buffer = Buffer.concat(this.#buffers); this.#buffers = []; this.write(buffer) .finally(() => setTimeout(this.work.bind(this), config.system.logWriteInterval)) .catch(err => console.error("Log write error:", err)); } } class LogText { /** @type {string} 日志级别 */ level; /** @type {string} 日志文本 */ text; /** @type {string} 日志来源 */ source; /** @type {Date} 日志发生时间 */ time = new Date(); constructor(level, ...params) { this.level = level; this.text = _util.format.apply(null, params); this.source = this.#getStackTopCodeInfo(); } #getStackTopCodeInfo() { const unknownInfo = { name: "unknown", codeLine: 0, codeColumn: 0 }; const stackArray = new Error().stack.split("\n"); const text = stackArray[4]; if (!text) return unknownInfo; const match = text.match(/at (.+) \((.+)\)/) || text.match(/at (.+)/); if (!match || !_.isString(match[2] || match[1])) return unknownInfo; const temp = match[2] || match[1]; const _match = temp.match(/([a-zA-Z0-9_\-\.]+)\:(\d+)\:(\d+)$/); if (!_match) return unknownInfo; const [, scriptPath, codeLine, codeColumn] = _match as any; return { name: scriptPath ? scriptPath.replace(/.js$/, "") : "unknown", path: scriptPath || null, codeLine: parseInt(codeLine || 0), codeColumn: parseInt(codeColumn || 0) }; } toString() { return `[${dateFormat(this.time, "yyyy-MM-dd HH:mm:ss.SSS")}][${this.level}][${this.source.name}<${this.source.codeLine},${this.source.codeColumn}>] ${this.text}`; } } class Logger { /** @type {Object} 系统配置 */ config = {}; /** @type {Object} 日志级别映射 */ static Level = { Info: "info", Debug: "debug", Warning: "warning", Error: "error", }; /** @type {Object} 日志级别文本颜色樱色 */ static LevelColor = { [Logger.Level.Success]: "green", [Logger.Level.Info]: "brightCyan", [Logger.Level.Debug]: "white", [Logger.Level.Warning]: "brightYellow", [Logger.Level.Error]: "brightRed", [Logger.Level.Fatal]: "red" }; static LevelPriority = { [Logger.Level.Fatal]: 1, [Logger.Level.Error]: 2, [Logger.Level.Warning]: 3, [Logger.Level.Success]: 4, [Logger.Level.Info]: 5, [Logger.Level.Log]: 6, [Logger.Level.Debug]: 7, } #writer; constructor() { this.#writer = new LogWriter(); } header() { this.#writer.writeSync(Buffer.from(`\n\n===================== LOG START ${dateFormat(new Date(), "yyyy-MM-dd HH:mm:ss.SSS")} =====================\n\n`)); } footer() { this.#writer.flush(); //将未写入文件的日志缓存写入 this.#writer.writeSync(Buffer.from(`\n\n===================== LOG END ${dateFormat(new Date(), "yyyy-MM-dd HH:mm:ss.SSS")} =====================\n\n`)); } #checkLevel(level) { const currentLevelPriority = Logger.LevelPriority[config.system.log_level] || 99; const levelPriority = Logger.LevelPriority[level]; return levelPriority <= currentLevelPriority; } success(...params) { if (!this.#checkLevel(Logger.Level.Success)) return; const content = new LogText(Logger.Level.Success, ...params).toString(); console.info(content[Logger.LevelColor[Logger.Level.Success]]); this.#writer.push(content + "\n"); } info(...params) { if (!this.#checkLevel(Logger.Level.Info)) return; const content = new LogText(Logger.Level.Info, ...params).toString(); console.info(content[Logger.LevelColor[Logger.Level.Info]]); this.#writer.push(content + "\n"); } debug(...params) { if(!config.system.debug) return; //非调试模式忽略debug if (!this.#checkLevel(Logger.Level.Debug)) return; const content = new LogText(Logger.Level.Debug, ...params).toString(); console.debug(content[Logger.LevelColor[Logger.Level.Debug]]); this.#writer.push(content + "\n"); } warn(...params) { if (!this.#checkLevel(Logger.Level.Warning)) return; const content = new LogText(Logger.Level.Warning, ...params).toString(); console.warn(content[Logger.LevelColor[Logger.Level.Warning]]); this.#writer.push(content + "\n"); } error(...params) { if (!this.#checkLevel(Logger.Level.Error)) return; const content = new LogText(Logger.Level.Error, ...params).toString(); console.error(content[Logger.LevelColor[Logger.Level.Error]]); this.#writer.push(content); } destory() { this.#writer.destory(); } } export default new Logger();