阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 日志与审计功能实现

日志与审计功能实现

作者:陈川 阅读数:12246人阅读 分类: MongoDB

日志与审计功能实现

日志与审计功能是后端系统的重要组成部分,尤其在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())
  });
});

性能优化策略

大量日志记录需要考虑性能影响:

  1. 批量写入优化:
const logQueue = [];
setInterval(() => {
  if(logQueue.length > 0) {
    AuditLog.insertMany(logQueue.splice(0, 100));
  }
}, 5000);
  1. 索引优化:
auditSchema.index({ docId: 1 });
auditSchema.index({ timestamp: -1 });
auditSchema.index({ model: 1, operation: 1 });
  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);
});

安全保护措施

审计日志本身需要加强保护:

  1. 加密存储敏感字段
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();
});
  1. 设置严格的访问控制
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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌