日志与审计功能实现
日志与审计功能实现
日志与审计功能是后端系统的重要组成部分,尤其在Mongoose这类ODM库中,合理的日志设计能有效追踪数据操作。通过记录关键操作和异常情况,开发者可以快速定位问题,同时满足合规性要求。
日志级别与分类
在Mongoose中实现日志首先需要明确日志级别:
const LogLevel = {
DEBUG: 0, // 开发调试信息
INFO: 1, // 常规操作记录
WARN: 2, // 潜在问题警告
ERROR: 3, // 错误事件
AUDIT: 4 // 审计专用级别
};
典型日志分类包括:
- 操作日志:记录CRUD操作
- 系统日志:记录服务启停等事件
- 安全日志:记录登录认证等敏感操作
- 审计日志:满足合规要求的特殊记录
Mongoose插件实现
通过Mongoose插件可统一添加日志功能:
function auditLogPlugin(schema) {
schema.post('init', doc => {
doc._originalData = doc.toObject();
});
schema.pre('save', function(next) {
this._modifiedPaths = this.modifiedPaths();
next();
});
schema.post('save', function(doc) {
if (this._modifiedPaths.length > 0) {
const changes = this._modifiedPaths.map(path => ({
field: path,
from: this._originalData[path],
to: doc[path]
}));
AuditLog.create({
model: doc.constructor.modelName,
docId: doc._id,
operation: 'update',
changes,
timestamp: new Date()
});
}
});
}
审计追踪实现
深度审计需要记录数据变更前后的完整状态:
const auditSchema = new mongoose.Schema({
operation: { type: String, enum: ['create', 'update', 'delete'] },
model: String,
docId: mongoose.Types.ObjectId,
before: mongoose.Schema.Types.Mixed,
after: mongoose.Schema.Types.Mixed,
modifiedBy: { type: mongoose.Types.ObjectId, ref: 'User' },
timestamp: { type: Date, default: Date.now }
}, { capped: { size: 1024*1024*10, max: 10000 } });
// 使用变更流实现实时审计
const changeStream = Model.watch();
changeStream.on('change', change => {
AuditLog.create({
operation: change.operationType,
model: change.ns.coll,
docId: change.documentKey._id,
before: change.updateDescription?.updatedFields,
after: change.fullDocument
});
});
敏感数据脱敏处理
审计日志中的敏感字段需要特殊处理:
function maskSensitiveData(data) {
const sensitiveFields = ['password', 'creditCard', 'ssn'];
return JSON.parse(JSON.stringify(data, (key, value) => {
return sensitiveFields.includes(key)
? '***MASKED***'
: value;
}));
}
userSchema.post('save', function(doc) {
AuditLog.create({
operation: 'update',
model: 'User',
docId: doc._id,
before: maskSensitiveData(this._originalData),
after: maskSensitiveData(doc.toObject())
});
});
性能优化策略
大量日志记录需要考虑性能影响:
- 批量写入优化:
const logQueue = [];
setInterval(() => {
if(logQueue.length > 0) {
AuditLog.insertMany(logQueue.splice(0, 100));
}
}, 5000);
- 索引优化:
auditSchema.index({ docId: 1 });
auditSchema.index({ timestamp: -1 });
auditSchema.index({ model: 1, operation: 1 });
- 日志分级存储:
const logSchema = new mongoose.Schema({
// ...字段定义
}, {
timeseries: {
timeField: 'timestamp',
metaField: 'metadata',
granularity: 'hours'
}
});
查询与分析接口
提供强大的日志查询能力:
router.get('/audit', async (req, res) => {
const { model, docId, startDate, endDate, operation } = req.query;
const query = {};
if(model) query.model = model;
if(docId) query.docId = docId;
if(operation) query.operation = operation;
if(startDate || endDate) {
query.timestamp = {};
if(startDate) query.timestamp.$gte = new Date(startDate);
if(endDate) query.timestamp.$lte = new Date(endDate);
}
const logs = await AuditLog.find(query)
.sort('-timestamp')
.limit(100)
.lean();
res.json(logs);
});
安全保护措施
审计日志本身需要加强保护:
- 加密存储敏感字段
const encryptField = (value) => {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
return cipher.update(value, 'utf8', 'hex') + cipher.final('hex');
};
auditSchema.pre('save', function(next) {
if(this.after?.password) {
this.after.password = encryptField(this.after.password);
}
next();
});
- 设置严格的访问控制
router.get('/audit/:id', authMiddleware, async (req, res) => {
if(!req.user.roles.includes('auditor')) {
return res.status(403).send('Forbidden');
}
const log = await AuditLog.findById(req.params.id);
res.json(log);
});
可视化展示
结合图表库展示日志分析结果:
router.get('/stats', async (req, res) => {
const stats = await AuditLog.aggregate([
{
$group: {
_id: {
model: "$model",
operation: "$operation"
},
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } }
]);
res.json(stats);
});
日志轮转与归档
处理历史日志的存储策略:
const archiveOldLogs = async () => {
const cutoff = new Date();
cutoff.setMonth(cutoff.getMonth() - 3);
const logs = await AuditLog.find({
timestamp: { $lt: cutoff }
}).lean();
if(logs.length > 0) {
await ArchiveLog.insertMany(logs);
await AuditLog.deleteMany({
_id: { $in: logs.map(l => l._id) }
});
}
};
// 每月执行一次归档
cron.schedule('0 0 1 * *', archiveOldLogs);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn