llm-chat/backend/middleware/logger.js

225 lines
5.1 KiB
JavaScript
Raw Normal View History

const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
// 创建日志目录
const logDir = path.join(__dirname, '../logs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// 自定义日志格式
const customFormat = ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :response-time ms';
// 创建访问日志流
const accessLogStream = fs.createWriteStream(
path.join(logDir, 'access.log'),
{ flags: 'a' }
);
// 创建错误日志流
const errorLogStream = fs.createWriteStream(
path.join(logDir, 'error.log'),
{ flags: 'a' }
);
/**
* 请求日志中间件
*/
const requestLogger = morgan(customFormat, {
stream: accessLogStream,
skip: (req, res) => {
// 跳过健康检查请求的日志记录
return req.url === '/api/health';
}
});
/**
* 控制台日志中间件开发环境
*/
const consoleLogger = morgan('dev', {
skip: (req, res) => {
// 生产环境跳过控制台日志
return process.env.NODE_ENV === 'production';
}
});
/**
* 错误日志中间件
*/
const errorLogger = (err, req, res, next) => {
const timestamp = new Date().toISOString();
const errorLog = {
timestamp,
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.get('User-Agent'),
error: {
name: err.name,
message: err.message,
stack: err.stack
},
body: req.body,
params: req.params,
query: req.query
};
// 写入错误日志文件
errorLogStream.write(JSON.stringify(errorLog) + '\n');
// 控制台输出(开发环境)
if (process.env.NODE_ENV !== 'production') {
console.error('错误日志:', errorLog);
}
next(err);
};
/**
* 自定义日志记录器
*/
class Logger {
static info(message, data = {}) {
const logEntry = {
level: 'INFO',
timestamp: new Date().toISOString(),
message,
data
};
console.log('[INFO]', message, data);
// 可选:写入日志文件
if (process.env.NODE_ENV === 'production') {
const infoLogStream = fs.createWriteStream(
path.join(logDir, 'app.log'),
{ flags: 'a' }
);
infoLogStream.write(JSON.stringify(logEntry) + '\n');
}
}
static warn(message, data = {}) {
const logEntry = {
level: 'WARN',
timestamp: new Date().toISOString(),
message,
data
};
console.warn('[WARN]', message, data);
if (process.env.NODE_ENV === 'production') {
const warnLogStream = fs.createWriteStream(
path.join(logDir, 'app.log'),
{ flags: 'a' }
);
warnLogStream.write(JSON.stringify(logEntry) + '\n');
}
}
static error(message, error = {}, data = {}) {
const logEntry = {
level: 'ERROR',
timestamp: new Date().toISOString(),
message,
error: {
name: error.name,
message: error.message,
stack: error.stack
},
data
};
console.error('[ERROR]', message, error, data);
errorLogStream.write(JSON.stringify(logEntry) + '\n');
}
static debug(message, data = {}) {
if (process.env.NODE_ENV === 'development') {
console.debug('[DEBUG]', message, data);
}
}
}
/**
* API调用统计中间件
*/
const apiStats = (() => {
const stats = {
totalRequests: 0,
endpoints: {},
methods: {},
statusCodes: {},
averageResponseTime: 0,
startTime: Date.now()
};
return (req, res, next) => {
const startTime = Date.now();
// 记录请求开始
stats.totalRequests++;
const endpoint = req.route?.path || req.path;
const method = req.method;
stats.endpoints[endpoint] = (stats.endpoints[endpoint] || 0) + 1;
stats.methods[method] = (stats.methods[method] || 0) + 1;
// 监听响应结束
res.on('finish', () => {
const responseTime = Date.now() - startTime;
const statusCode = res.statusCode;
stats.statusCodes[statusCode] = (stats.statusCodes[statusCode] || 0) + 1;
// 更新平均响应时间
if (stats.averageResponseTime === 0) {
stats.averageResponseTime = responseTime;
} else {
stats.averageResponseTime = (stats.averageResponseTime + responseTime) / 2;
}
});
next();
};
})();
/**
* 获取API统计信息
*/
const getApiStats = () => {
const uptime = Date.now() - apiStats.startTime;
return {
...apiStats,
uptime,
uptimeHuman: formatUptime(uptime)
};
};
/**
* 格式化运行时间
*/
function formatUptime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}${hours % 24}小时${minutes % 60}分钟`;
if (hours > 0) return `${hours}小时${minutes % 60}分钟`;
if (minutes > 0) return `${minutes}分钟${seconds % 60}`;
return `${seconds}`;
}
module.exports = {
requestLogger,
consoleLogger,
errorLogger,
apiStats,
getApiStats,
Logger
};