Merge pull request #514 from NapNeko/refactor-log4js--winston

refactor: log4js to winston
This commit is contained in:
手瓜一十雪 2024-11-13 12:29:35 +08:00 committed by GitHub
commit 2260fe32a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 75 additions and 63 deletions

View File

@ -45,12 +45,12 @@
"typescript-eslint": "^8.13.0", "typescript-eslint": "^8.13.0",
"vite": "^5.2.6", "vite": "^5.2.6",
"vite-plugin-cp": "^4.0.8", "vite-plugin-cp": "^4.0.8",
"vite-tsconfig-paths": "^5.1.0" "vite-tsconfig-paths": "^5.1.0",
"winston": "^3.17.0"
}, },
"dependencies": { "dependencies": {
"express": "^5.0.0", "express": "^5.0.0",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"ws": "^8.18.0" "ws": "^8.18.0"

View File

@ -1,4 +1,4 @@
import log4js, { Configuration } from 'log4js'; import winston, { format, transports } from 'winston';
import { truncateString } from '@/common/helper'; import { truncateString } from '@/common/helper';
import path from 'node:path'; import path from 'node:path';
import chalk from 'chalk'; import chalk from 'chalk';
@ -27,76 +27,78 @@ function getFormattedTimestamp() {
export class LogWrapper { export class LogWrapper {
fileLogEnabled = true; fileLogEnabled = true;
consoleLogEnabled = true; consoleLogEnabled = true;
logConfig: Configuration; logger: winston.Logger;
loggerConsole: log4js.Logger;
loggerFile: log4js.Logger;
loggerDefault: log4js.Logger;
// eslint-disable-next-line no-control-regex
colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
constructor(logDir: string) { constructor(logDir: string) {
const filename = `${getFormattedTimestamp()}.log`; const filename = `${getFormattedTimestamp()}.log`;
const logPath = path.join(logDir, filename); const logPath = path.join(logDir, filename);
this.logConfig = {
appenders: { this.logger = winston.createLogger({
FileAppender: { // 输出到文件的appender level: 'debug',
type: 'file', format: format.combine(
filename: logPath, // 指定日志文件的位置和文件名 format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
maxLogSize: 10485760, // 日志文件的最大大小单位字节这里设置为10MB format.printf(({ timestamp, level, message, ...meta }) => {
layout: { const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
type: 'pattern', return `${timestamp} [${level}] ${userInfo}${message}`;
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m', })
}, ),
}, transports: [
ConsoleAppender: { // 输出到控制台的appender new transports.File({ filename: logPath, level: 'debug' }),
type: 'console', new transports.Console({
layout: { format: format.combine(
type: 'pattern', format.colorize(),
pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`, format.printf(({ timestamp, level, message, ...meta }) => {
}, const userInfo = meta.userInfo ? `${chalk.magenta(meta.userInfo)} | ` : '';
}, return `${timestamp} [${level}] ${userInfo}${message}`;
}, })
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.setLogSelfInfo({ nick: '', uin: '', uid: '' }); this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
} }
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
this.logConfig.categories.file.level = fileLogLevel; this.logger.transports.forEach((transport) => {
this.logConfig.categories.console.level = consoleLogLevel; if (transport instanceof transports.File) {
log4js.configure(this.logConfig); transport.level = fileLogLevel;
} else if (transport instanceof transports.Console) {
transport.level = consoleLogLevel;
}
});
} }
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) { setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
this.loggerConsole.addContext('userInfo', userInfo); this.logger.defaultMeta = { userInfo };
this.loggerFile.addContext('userInfo', userInfo);
this.loggerDefault.addContext('userInfo', userInfo);
} }
setFileLogEnabled(isEnabled: boolean) { setFileLogEnabled(isEnabled: boolean) {
this.fileLogEnabled = isEnabled; this.fileLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.File) {
transport.silent = !isEnabled;
}
});
} }
setConsoleLogEnabled(isEnabled: boolean) { setConsoleLogEnabled(isEnabled: boolean) {
this.consoleLogEnabled = isEnabled; this.consoleLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.Console) {
transport.silent = !isEnabled;
}
});
} }
formatMsg(msg: any[]) { formatMsg(msg: any[]) {
let logMsg = ''; let logMsg = '';
for (const msgItem of msg) { for (const msgItem of msg) {
if (msgItem instanceof Error) { // 判断是否是错误 if (msgItem instanceof Error) {
logMsg += msgItem.stack + ' '; logMsg += msgItem.stack + ' ';
continue; continue;
} else if (typeof msgItem === 'object') { // 判断是否是对象 } else if (typeof msgItem === 'object') {
const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
logMsg += JSON.stringify(truncateString(obj)) + ' '; logMsg += JSON.stringify(truncateString(obj)) + ' ';
continue; continue;
@ -106,18 +108,18 @@ export class LogWrapper {
return logMsg; return logMsg;
} }
_log(level: LogLevel, ...args: any[]) { _log(level: LogLevel, ...args: any[]) {
if (this.consoleLogEnabled) { const message = this.formatMsg(args);
this.loggerConsole[level](this.formatMsg(args)); if (this.consoleLogEnabled && this.fileLogEnabled) {
} this.logger.log(level, message);
if (this.fileLogEnabled) { } else if (this.consoleLogEnabled) {
this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, '')); this.logger.log(level, message);
} else if (this.fileLogEnabled) {
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
} }
} }
log(...args: any[]) { log(...args: any[]) {
// info 等级
this._log(LogLevel.INFO, ...args); this._log(LogLevel.INFO, ...args);
} }
@ -140,7 +142,6 @@ export class LogWrapper {
logMessage(msg: RawMessage, selfInfo: SelfInfo) { logMessage(msg: RawMessage, selfInfo: SelfInfo) {
const isSelfSent = msg.senderUin === selfInfo.uin; const isSelfSent = msg.senderUin === selfInfo.uin;
// Intercept grey tip
if (msg.elements[0]?.elementType === ElementType.GreyTip) { if (msg.elements[0]?.elementType === ElementType.GreyTip) {
return; return;
} }
@ -167,12 +168,10 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
} }
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) { } else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
tokens.push('移动设备'); tokens.push('移动设备');
} else /* temp */ { } else {
tokens.push(`临时消息 (${msg.peerUin})`); tokens.push(`临时消息 (${msg.peerUin})`);
} }
// message content
function msgElementToText(element: MessageElement) { function msgElementToText(element: MessageElement) {
if (element.textElement) { if (element.textElement) {
if (element.textElement.atType === AtType.notAt) { if (element.textElement.atType === AtType.notAt) {
@ -190,7 +189,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
record => element.replyElement!.sourceMsgIdInRecords === record.msgId, record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
); );
return `[回复消息 ${recordMsgOrNull && return `[回复消息 ${recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到 recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
? ?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})` `未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
@ -245,4 +244,4 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
} }
return tokens.join(' '); return tokens.join(' ');
} }

View File

@ -23,6 +23,14 @@ export async function NCoreInitFramework(
) { ) {
//在进入本层前是否登录未进行判断 //在进入本层前是否登录未进行判断
console.log('NapCat Framework App Loading...'); 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 pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath); const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const basicInfoWrapper = new QQBasicInfoWrapper({ logger });

View File

@ -36,7 +36,12 @@ const cmdOptions = program.opts();
// NapCat Shell App ES 入口文件 // NapCat Shell App ES 入口文件
export async function NCoreInitShell() { export async function NCoreInitShell() {
console.log('NapCat Shell App Loading...'); 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 pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath); const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
@ -227,7 +232,7 @@ export async function NCoreInitShell() {
logger.log(`可用于快速登录的 QQ\n${historyLoginList logger.log(`可用于快速登录的 QQ\n${historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`) .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n') .join('\n')
}`); }`);
} }
loginService.getQRCodePicture(); loginService.getQRCodePicture();
} }

View File

@ -4,7 +4,7 @@ import { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve'; import nodeResolve from '@rollup/plugin-node-resolve';
import { builtinModules } from 'module'; 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(); const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat();
function genCpModule(module: string) { function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false }; return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };