mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-20 05:30:06 +08:00
parent
040b5535f3
commit
d13db5e8eb
@ -3,7 +3,7 @@ import { truncateString } from '@/common/helper';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
|
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
|
||||||
|
import EventEmitter from 'node:events';
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
DEBUG = 'debug',
|
DEBUG = 'debug',
|
||||||
INFO = 'info',
|
INFO = 'info',
|
||||||
@ -24,6 +24,36 @@ function getFormattedTimestamp() {
|
|||||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logEmitter = new EventEmitter();
|
||||||
|
export type LogListener = (msg: string) => void;
|
||||||
|
class Subscription {
|
||||||
|
public static MAX_HISTORY = 100;
|
||||||
|
public static history: string[] = [];
|
||||||
|
|
||||||
|
subscribe(listener: LogListener) {
|
||||||
|
for (const history of Subscription.history) {
|
||||||
|
try {
|
||||||
|
listener(history);
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logEmitter.on('log', listener);
|
||||||
|
}
|
||||||
|
unsubscribe(listener: LogListener) {
|
||||||
|
logEmitter.off('log', listener);
|
||||||
|
}
|
||||||
|
notify(msg: string) {
|
||||||
|
logEmitter.emit('log', msg);
|
||||||
|
if (Subscription.history.length >= Subscription.MAX_HISTORY) {
|
||||||
|
Subscription.history.shift();
|
||||||
|
}
|
||||||
|
Subscription.history.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logSubscription = new Subscription();
|
||||||
|
|
||||||
export class LogWrapper {
|
export class LogWrapper {
|
||||||
fileLogEnabled = true;
|
fileLogEnabled = true;
|
||||||
consoleLogEnabled = true;
|
consoleLogEnabled = true;
|
||||||
@ -47,7 +77,7 @@ export class LogWrapper {
|
|||||||
filename: logPath,
|
filename: logPath,
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
maxsize: 5 * 1024 * 1024, // 5MB
|
maxsize: 5 * 1024 * 1024, // 5MB
|
||||||
maxFiles: 5
|
maxFiles: 5,
|
||||||
}),
|
}),
|
||||||
new transports.Console({
|
new transports.Console({
|
||||||
format: format.combine(
|
format: format.combine(
|
||||||
@ -56,9 +86,9 @@ export class LogWrapper {
|
|||||||
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
|
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
|
||||||
return `${timestamp} [${level}] ${userInfo}${message}`;
|
return `${timestamp} [${level}] ${userInfo}${message}`;
|
||||||
})
|
})
|
||||||
)
|
),
|
||||||
})
|
}),
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setLogSelfInfo({ nick: '', uid: '' });
|
this.setLogSelfInfo({ nick: '', uid: '' });
|
||||||
@ -72,7 +102,7 @@ export class LogWrapper {
|
|||||||
this.logger.error('Failed to read log directory', err);
|
this.logger.error('Failed to read log directory', err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
files.forEach(file => {
|
files.forEach((file) => {
|
||||||
const filePath = path.join(logDir, file);
|
const filePath = path.join(logDir, file);
|
||||||
this.deleteOldLogFile(filePath, oneWeekAgo);
|
this.deleteOldLogFile(filePath, oneWeekAgo);
|
||||||
});
|
});
|
||||||
@ -86,7 +116,7 @@ export class LogWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stats.mtime.getTime() < oneWeekAgo) {
|
if (stats.mtime.getTime() < oneWeekAgo) {
|
||||||
fs.unlink(filePath, err => {
|
fs.unlink(filePath, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
this.logger.warn(`File already deleted: ${filePath}`);
|
this.logger.warn(`File already deleted: ${filePath}`);
|
||||||
@ -111,7 +141,7 @@ export class LogWrapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogSelfInfo(selfInfo: { nick: string, uid: string }) {
|
setLogSelfInfo(selfInfo: { nick: string; uid: string }) {
|
||||||
const userInfo = `${selfInfo.nick}`;
|
const userInfo = `${selfInfo.nick}`;
|
||||||
this.logger.defaultMeta = { userInfo };
|
this.logger.defaultMeta = { userInfo };
|
||||||
}
|
}
|
||||||
@ -135,14 +165,16 @@ export class LogWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatMsg(msg: any[]) {
|
formatMsg(msg: any[]) {
|
||||||
return msg.map(msgItem => {
|
return msg
|
||||||
|
.map((msgItem) => {
|
||||||
if (msgItem instanceof Error) {
|
if (msgItem instanceof Error) {
|
||||||
return msgItem.stack;
|
return msgItem.stack;
|
||||||
} else if (typeof msgItem === 'object') {
|
} else if (typeof msgItem === 'object') {
|
||||||
return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2))));
|
return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2))));
|
||||||
}
|
}
|
||||||
return msgItem;
|
return msgItem;
|
||||||
}).join(' ');
|
})
|
||||||
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
_log(level: LogLevel, ...args: any[]) {
|
_log(level: LogLevel, ...args: any[]) {
|
||||||
@ -155,6 +187,7 @@ export class LogWrapper {
|
|||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
|
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
|
||||||
}
|
}
|
||||||
|
logSubscription.notify(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
log(...args: any[]) {
|
log(...args: any[]) {
|
||||||
@ -282,13 +315,10 @@ function textElementToText(textElement: any): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
|
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
|
||||||
const recordMsgOrNull = msg.records.find(
|
const recordMsgOrNull = msg.records.find((record) => replyElement.sourceMsgIdInRecords === record.msgId);
|
||||||
record => replyElement.sourceMsgIdInRecords === record.msgId,
|
return `[回复消息 ${
|
||||||
);
|
recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
||||||
return `[回复消息 ${recordMsgOrNull &&
|
? rawMessageToText(recordMsgOrNull, recursiveLevel + 1)
|
||||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
: `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
|
||||||
?
|
|
||||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
|
||||||
`未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
|
|
||||||
}]`;
|
}]`;
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import type { RequestHandler } from 'express';
|
import type { RequestHandler } from 'express';
|
||||||
import { sendError, sendSuccess } from '../utils/response';
|
import { sendError, sendSuccess } from '../utils/response';
|
||||||
import { WebUiConfigWrapper } from '../helper/config';
|
import { WebUiConfigWrapper } from '../helper/config';
|
||||||
|
import { logSubscription } from '@/common/log';
|
||||||
|
|
||||||
// 日志记录
|
// 日志记录
|
||||||
export const LogHandler: RequestHandler = async (req, res) => {
|
export const LogHandler: RequestHandler = async (req, res) => {
|
||||||
@ -17,3 +18,17 @@ export const LogListHandler: RequestHandler = async (_, res) => {
|
|||||||
const logList = WebUiConfigWrapper.GetLogsList();
|
const logList = WebUiConfigWrapper.GetLogsList();
|
||||||
return sendSuccess(res, logList);
|
return sendSuccess(res, logList);
|
||||||
};
|
};
|
||||||
|
// 实时日志(SSE)
|
||||||
|
export const LogRealTimeHandler: RequestHandler = async (req, res) => {
|
||||||
|
const listener = (log: string) => {
|
||||||
|
try {
|
||||||
|
res.write(log + '\n');
|
||||||
|
} catch (error) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
logSubscription.subscribe(listener);
|
||||||
|
req.on('close', () => {
|
||||||
|
logSubscription.unsubscribe(listener);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { LogHandler, LogListHandler } from '../api/Log';
|
import { LogHandler, LogListHandler, LogRealTimeHandler } from '../api/Log';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
// router:读取日志内容
|
// router:读取日志内容
|
||||||
router.get('/GetLog', LogHandler);
|
router.get('/GetLog', LogHandler);
|
||||||
// router:读取日志列表
|
// router:读取日志列表
|
||||||
router.get('/GetLogList', LogListHandler);
|
router.get('/GetLogList', LogListHandler);
|
||||||
|
|
||||||
|
// router:实时日志
|
||||||
|
router.get('/GetLogRealTime', LogRealTimeHandler);
|
||||||
|
|
||||||
export { router as LogRouter };
|
export { router as LogRouter };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user