From 3e1f566699114eb0e4335bcc2ca85111e9f595ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Wed, 13 Nov 2024 12:26:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=B8=8Elog4js=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/log.ts | 106 ++++++++++++++++++++-------------------- src/framework/napcat.ts | 8 +++ src/shell/napcat.ts | 9 +++- vite.config.ts | 2 +- 4 files changed, 68 insertions(+), 57 deletions(-) diff --git a/src/common/log.ts b/src/common/log.ts index e836dfe7..69ae9afb 100644 --- a/src/common/log.ts +++ b/src/common/log.ts @@ -1,4 +1,4 @@ -import log4js, { Configuration } from 'log4js'; +import winston, { format, transports } from 'winston'; import { truncateString } from '@/common/helper'; import path from 'node:path'; import chalk from 'chalk'; @@ -27,76 +27,78 @@ function getFormattedTimestamp() { export class LogWrapper { fileLogEnabled = true; consoleLogEnabled = true; - logConfig: Configuration; - loggerConsole: log4js.Logger; - loggerFile: log4js.Logger; - loggerDefault: log4js.Logger; - // eslint-disable-next-line no-control-regex - colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g; + logger: winston.Logger; constructor(logDir: string) { const filename = `${getFormattedTimestamp()}.log`; const logPath = path.join(logDir, filename); - this.logConfig = { - appenders: { - FileAppender: { // 输出到文件的appender - type: 'file', - filename: logPath, // 指定日志文件的位置和文件名 - maxLogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB - layout: { - type: 'pattern', - pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m', - }, - }, - ConsoleAppender: { // 输出到控制台的appender - type: 'console', - layout: { - type: 'pattern', - pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`, - }, - }, - }, - categories: { - default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台 - file: { appenders: ['FileAppender'], level: 'debug' }, - console: { appenders: ['ConsoleAppender'], level: 'debug' }, - }, - }; - log4js.configure(this.logConfig); - this.loggerConsole = log4js.getLogger('console'); - this.loggerFile = log4js.getLogger('file'); - this.loggerDefault = log4js.getLogger('default'); + + this.logger = winston.createLogger({ + level: 'debug', + format: format.combine( + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf(({ timestamp, level, message, ...meta }) => { + const userInfo = meta.userInfo ? `${meta.userInfo} | ` : ''; + return `${timestamp} [${level}] ${userInfo}${message}`; + }) + ), + transports: [ + new transports.File({ filename: logPath, level: 'debug' }), + new transports.Console({ + format: format.combine( + format.colorize(), + format.printf(({ timestamp, level, message, ...meta }) => { + const userInfo = meta.userInfo ? `${chalk.magenta(meta.userInfo)} | ` : ''; + return `${timestamp} [${level}] ${userInfo}${message}`; + }) + ) + }) + ] + }); + this.setLogSelfInfo({ nick: '', uin: '', uid: '' }); } setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { - this.logConfig.categories.file.level = fileLogLevel; - this.logConfig.categories.console.level = consoleLogLevel; - log4js.configure(this.logConfig); + this.logger.transports.forEach((transport) => { + if (transport instanceof transports.File) { + transport.level = fileLogLevel; + } else if (transport instanceof transports.Console) { + transport.level = consoleLogLevel; + } + }); } setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) { const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; - this.loggerConsole.addContext('userInfo', userInfo); - this.loggerFile.addContext('userInfo', userInfo); - this.loggerDefault.addContext('userInfo', userInfo); + this.logger.defaultMeta = { userInfo }; } setFileLogEnabled(isEnabled: boolean) { this.fileLogEnabled = isEnabled; + this.logger.transports.forEach((transport) => { + if (transport instanceof transports.File) { + transport.silent = !isEnabled; + } + }); } setConsoleLogEnabled(isEnabled: boolean) { this.consoleLogEnabled = isEnabled; + this.logger.transports.forEach((transport) => { + if (transport instanceof transports.Console) { + transport.silent = !isEnabled; + } + }); } formatMsg(msg: any[]) { let logMsg = ''; for (const msgItem of msg) { - if (msgItem instanceof Error) { // 判断是否是错误 + if (msgItem instanceof Error) { logMsg += msgItem.stack + ' '; continue; - } else if (typeof msgItem === 'object') { // 判断是否是对象 + } else if (typeof msgItem === 'object') { const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); logMsg += JSON.stringify(truncateString(obj)) + ' '; continue; @@ -106,18 +108,17 @@ export class LogWrapper { return logMsg; } - _log(level: LogLevel, ...args: any[]) { + const message = this.formatMsg(args); if (this.consoleLogEnabled) { - this.loggerConsole[level](this.formatMsg(args)); + this.logger.log(level, message); } if (this.fileLogEnabled) { - this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, '')); + this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, '')); } } log(...args: any[]) { - // info 等级 this._log(LogLevel.INFO, ...args); } @@ -140,7 +141,6 @@ export class LogWrapper { logMessage(msg: RawMessage, selfInfo: SelfInfo) { const isSelfSent = msg.senderUin === selfInfo.uin; - // Intercept grey tip if (msg.elements[0]?.elementType === ElementType.GreyTip) { return; } @@ -167,12 +167,10 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { } } else if (msg.chatType == ChatType.KCHATTYPEDATALINE) { tokens.push('移动设备'); - } else /* temp */ { + } else { tokens.push(`临时消息 (${msg.peerUin})`); } - // message content - function msgElementToText(element: MessageElement) { if (element.textElement) { if (element.textElement.atType === AtType.notAt) { @@ -190,7 +188,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { record => element.replyElement!.sourceMsgIdInRecords === record.msgId, ); return `[回复消息 ${recordMsgOrNull && - recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到 + recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020' ? rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : `未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})` @@ -245,4 +243,4 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { } return tokens.join(' '); -} +} \ No newline at end of file diff --git a/src/framework/napcat.ts b/src/framework/napcat.ts index 60607d0f..9c947594 100644 --- a/src/framework/napcat.ts +++ b/src/framework/napcat.ts @@ -23,6 +23,14 @@ export async function NCoreInitFramework( ) { //在进入本层前是否登录未进行判断 console.log('NapCat Framework App Loading...'); + + process.on('uncaughtException', (err) => { + console.log('[NapCat] [Error] Unhandled Exception:', err.message); + }); + process.on('unhandledRejection', (reason, promise) => { + console.log('[NapCat] [Error] unhandledRejection:', reason); + }); + const pathWrapper = new NapCatPathWrapper(); const logger = new LogWrapper(pathWrapper.logsPath); const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); diff --git a/src/shell/napcat.ts b/src/shell/napcat.ts index cc8890de..f0d80eac 100644 --- a/src/shell/napcat.ts +++ b/src/shell/napcat.ts @@ -36,7 +36,12 @@ const cmdOptions = program.opts(); // NapCat Shell App ES 入口文件 export async function NCoreInitShell() { console.log('NapCat Shell App Loading...'); - + process.on('uncaughtException', (err) => { + console.log('[NapCat] [Error] Unhandled Exception:', err.message); + }); + process.on('unhandledRejection', (reason, promise) => { + console.log('[NapCat] [Error] unhandledRejection:', reason); + }); const pathWrapper = new NapCatPathWrapper(); const logger = new LogWrapper(pathWrapper.logsPath); const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); @@ -227,7 +232,7 @@ export async function NCoreInitShell() { logger.log(`可用于快速登录的 QQ:\n${historyLoginList .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`) .join('\n') - }`); + }`); } loginService.getQRCodePicture(); } diff --git a/vite.config.ts b/vite.config.ts index b74c214c..aa4c0df8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,7 +4,7 @@ import { resolve } from 'path'; import nodeResolve from '@rollup/plugin-node-resolve'; import { builtinModules } from 'module'; //依赖排除 -const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'log4js', 'qrcode-terminal']; +const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'qrcode-terminal']; const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat(); function genCpModule(module: string) { return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };