feat(log): 增强日志系统功能与追踪能力
- 新增模块级别日志控制和采样配置 - 为操作日志添加requestId字段支持全链路追踪 - 优化请求日志中间件,增强结构化记录能力 - 实现日志降级机制,数据库不可用时写入文件 - 添加相关数据库迁移脚本和模型索引
This commit is contained in:
@@ -1,9 +1,27 @@
|
||||
/**
|
||||
* 操作日志记录器
|
||||
* 支持:
|
||||
* - 关联请求追踪ID(requestId)
|
||||
* - 数据库写入失败时降级到文件日志
|
||||
* - 统一错误处理(使用logger替代console.error)
|
||||
*/
|
||||
|
||||
const OperationLog = require('../models/OperationLog');
|
||||
const logger = require('./logger');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const logModule = logger.module('OperationLogger');
|
||||
|
||||
const generateRecordId = () => {
|
||||
return `OPLOG_${Date.now().toString(36)}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取操作人信息
|
||||
* @param {Object} req - Express请求对象
|
||||
* @returns {Object} 操作人信息
|
||||
*/
|
||||
const getOperatorInfo = req => {
|
||||
if (!req || !req.user) {
|
||||
return {
|
||||
@@ -19,9 +37,14 @@ const getOperatorInfo = req => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取客户端信息
|
||||
* @param {Object} req - Express请求对象
|
||||
* @returns {Object} 客户端信息(含requestId)
|
||||
*/
|
||||
const getClientInfo = req => {
|
||||
if (!req) {
|
||||
return { ipAddress: null, userAgent: null };
|
||||
return { ipAddress: null, userAgent: null, requestId: null };
|
||||
}
|
||||
const ipAddress =
|
||||
req.headers['x-forwarded-for'] ||
|
||||
@@ -29,8 +52,11 @@ const getClientInfo = req => {
|
||||
req.connection?.remoteAddress ||
|
||||
req.ip ||
|
||||
null;
|
||||
const userAgent = req.headers['user-agent'] || null;
|
||||
return { ipAddress, userAgent };
|
||||
return {
|
||||
ipAddress,
|
||||
userAgent: req.headers['user-agent'] || null,
|
||||
requestId: req.requestId || null,
|
||||
};
|
||||
};
|
||||
|
||||
const DEVICE_TYPE_MAP = {
|
||||
@@ -43,6 +69,13 @@ const DEVICE_TYPE_MAP = {
|
||||
other: '其他设备',
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成设备操作描述
|
||||
* @param {string} operation - 操作类型描述
|
||||
* @param {Object} device - 设备信息
|
||||
* @param {Object} options - 选项
|
||||
* @returns {string} 操作描述
|
||||
*/
|
||||
const generateDeviceDescription = (operation, device, options = {}) => {
|
||||
const {
|
||||
includeRack = true,
|
||||
@@ -82,6 +115,12 @@ const generateDeviceDescription = (operation, device, options = {}) => {
|
||||
return `${operation}${parts.join(',')}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建设备元数据
|
||||
* @param {Object} device - 设备信息
|
||||
* @param {Object} extra - 额外字段
|
||||
* @returns {Object} 元数据
|
||||
*/
|
||||
const buildDeviceMetadata = (device, extra = {}) => {
|
||||
return {
|
||||
deviceId: device.deviceId || null,
|
||||
@@ -99,6 +138,46 @@ const buildDeviceMetadata = (device, extra = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 降级写入文件日志
|
||||
* 当数据库不可用时,将操作日志写入文件作为降级方案
|
||||
* @param {Object} logData - 日志数据
|
||||
*/
|
||||
const fallbackToFile = (logData) => {
|
||||
try {
|
||||
const fallbackDir = path.join(process.env.LOG_DIR || './logs', 'fallback');
|
||||
if (!fs.existsSync(fallbackDir)) {
|
||||
fs.mkdirSync(fallbackDir, { recursive: true });
|
||||
}
|
||||
const fallbackPath = path.join(fallbackDir, `operation-fallback-${new Date().toISOString().slice(0, 10)}.log`);
|
||||
const fallbackEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: 'warn',
|
||||
module: 'OperationLogger',
|
||||
message: '操作日志数据库写入失败(降级记录)',
|
||||
data: logData,
|
||||
};
|
||||
fs.appendFileSync(fallbackPath, JSON.stringify(fallbackEntry) + '\n');
|
||||
} catch {
|
||||
// 文件写入也失败时静默忽略,避免级联错误
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 记录操作日志(核心方法)
|
||||
* @param {Object} params - 参数对象
|
||||
* @param {string} params.module - 模块名称
|
||||
* @param {string} params.operationType - 操作类型
|
||||
* @param {string} params.operationDescription - 操作描述
|
||||
* @param {string} params.targetId - 目标ID
|
||||
* @param {string} params.targetName - 目标名称
|
||||
* @param {Object} params.beforeState - 操作前状态
|
||||
* @param {Object} params.afterState - 操作后状态
|
||||
* @param {string} params.result - 操作结果
|
||||
* @param {Object} params.req - 请求对象
|
||||
* @param {Object} params.metadata - 附加元数据
|
||||
* @returns {Promise<Object|null>} 创建的日志记录
|
||||
*/
|
||||
async function logOperation({
|
||||
module,
|
||||
operationType,
|
||||
@@ -111,32 +190,64 @@ async function logOperation({
|
||||
req,
|
||||
metadata = {},
|
||||
}) {
|
||||
try {
|
||||
const operatorInfo = getOperatorInfo(req);
|
||||
const clientInfo = getClientInfo(req);
|
||||
const operatorInfo = getOperatorInfo(req);
|
||||
const clientInfo = getClientInfo(req);
|
||||
|
||||
await OperationLog.create({
|
||||
const logData = {
|
||||
module,
|
||||
operationType,
|
||||
operationDescription,
|
||||
targetId: targetId || null,
|
||||
targetName: targetName || null,
|
||||
operatorId: operatorInfo.operatorId,
|
||||
operatorName: operatorInfo.operatorName,
|
||||
operatorRole: operatorInfo.operatorRole,
|
||||
beforeState: beforeState || null,
|
||||
afterState: afterState || null,
|
||||
result,
|
||||
ipAddress: clientInfo.ipAddress,
|
||||
userAgent: clientInfo.userAgent,
|
||||
requestId: clientInfo.requestId,
|
||||
metadata,
|
||||
};
|
||||
|
||||
try {
|
||||
const operationLog = await OperationLog.create({
|
||||
recordId: generateRecordId(),
|
||||
...logData,
|
||||
});
|
||||
|
||||
logModule.debug('操作日志记录成功', {
|
||||
recordId: operationLog.recordId,
|
||||
module,
|
||||
operationType,
|
||||
operationDescription,
|
||||
targetId: targetId || null,
|
||||
targetName: targetName || null,
|
||||
operatorId: operatorInfo.operatorId,
|
||||
operatorName: operatorInfo.operatorName,
|
||||
operatorRole: operatorInfo.operatorRole,
|
||||
beforeState: beforeState || null,
|
||||
afterState: afterState || null,
|
||||
result,
|
||||
ipAddress: clientInfo.ipAddress,
|
||||
userAgent: clientInfo.userAgent,
|
||||
metadata,
|
||||
targetId,
|
||||
requestId: clientInfo.requestId,
|
||||
});
|
||||
|
||||
return operationLog;
|
||||
} catch (error) {
|
||||
console.error('记录操作日志失败:', error);
|
||||
logModule.error('记录操作日志失败', {
|
||||
error: error.message,
|
||||
module,
|
||||
operationType,
|
||||
targetId,
|
||||
requestId: clientInfo.requestId,
|
||||
});
|
||||
|
||||
fallbackToFile(logData);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录设备操作日志
|
||||
* @param {string} operationType - 操作类型
|
||||
* @param {string} operationDescription - 操作描述
|
||||
* @param {Object} params - 参数
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
async function logDeviceOperation(
|
||||
operationType,
|
||||
operationDescription,
|
||||
@@ -156,6 +267,13 @@ async function logDeviceOperation(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录用户操作日志
|
||||
* @param {string} operationType - 操作类型
|
||||
* @param {string} operationDescription - 操作描述
|
||||
* @param {Object} params - 参数
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
async function logUserOperation(
|
||||
operationType,
|
||||
operationDescription,
|
||||
@@ -175,6 +293,13 @@ async function logUserOperation(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录角色操作日志
|
||||
* @param {string} operationType - 操作类型
|
||||
* @param {string} operationDescription - 操作描述
|
||||
* @param {Object} params - 参数
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
async function logRoleOperation(
|
||||
operationType,
|
||||
operationDescription,
|
||||
|
||||
Reference in New Issue
Block a user