Spaces:
Running
Running
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 = { | |
Success: "success", | |
Info: "info", | |
Log: "log", | |
Debug: "debug", | |
Warning: "warning", | |
Error: "error", | |
Fatal: "fatal" | |
}; | |
/** @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" | |
}; | |
#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`)); | |
} | |
success(...params) { | |
const content = new LogText(Logger.Level.Success, ...params).toString(); | |
console.info(content[Logger.LevelColor[Logger.Level.Success]]); | |
this.#writer.push(content + "\n"); | |
} | |
info(...params) { | |
const content = new LogText(Logger.Level.Info, ...params).toString(); | |
console.info(content[Logger.LevelColor[Logger.Level.Info]]); | |
this.#writer.push(content + "\n"); | |
} | |
log(...params) { | |
const content = new LogText(Logger.Level.Log, ...params).toString(); | |
console.log(content[Logger.LevelColor[Logger.Level.Log]]); | |
this.#writer.push(content + "\n"); | |
} | |
debug(...params) { | |
if(!config.system.debug) return; //非调试模式忽略debug | |
const content = new LogText(Logger.Level.Debug, ...params).toString(); | |
console.debug(content[Logger.LevelColor[Logger.Level.Debug]]); | |
this.#writer.push(content + "\n"); | |
} | |
warn(...params) { | |
const content = new LogText(Logger.Level.Warning, ...params).toString(); | |
console.warn(content[Logger.LevelColor[Logger.Level.Warning]]); | |
this.#writer.push(content + "\n"); | |
} | |
error(...params) { | |
const content = new LogText(Logger.Level.Error, ...params).toString(); | |
console.error(content[Logger.LevelColor[Logger.Level.Error]]); | |
this.#writer.push(content); | |
} | |
fatal(...params) { | |
const content = new LogText(Logger.Level.Fatal, ...params).toString(); | |
console.error(content[Logger.LevelColor[Logger.Level.Fatal]]); | |
this.#writer.push(content); | |
} | |
destory() { | |
this.#writer.destory(); | |
} | |
} | |
export default new Logger(); |