You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js中统计响应时间存MongoDB及解决mongoose.query报错

报错根因

触发TypeError: mongoose.query is not a function的核心原因是Mongoose没有提供顶层的query()方法,和你之前使用PostgreSQL时直接调用连接池pool.query的逻辑不同:Mongoose作为MongoDB的ODM工具,所有数据库操作都必须基于提前定义好的数据模型(Model) 实例发起,直接调用mongoose.query()不存在对应的方法实现,必然抛错。

手把手实现全链路耗时统计+MongoDB持久化方案

步骤1:定义耗时记录专用存储模型

首先新建专门存储链路耗时数据的Schema,用于持久化所有请求的耗时指标,可根据业务需要扩展字段:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const ApiMetricSchema = new Schema({
  apiPath: { type: String, required: true }, // 请求接口路径
  method: { type: String, required: true }, // 请求方法 GET/POST等
  executionTime: { type: Number, required: true }, // 全链路耗时,单位秒
  isSuccess: { type: Boolean, required: true }, // 请求是否成功
  errorMsg: { type: String, default: '' }, // 失败时的错误信息
  createdAt: { type: Date, default: Date.now, expires: '30d' } // 自动记录生成时间,配置30天自动过期,避免集合数据膨胀
});

// 初始化耗时统计模型
const ApiMetric = mongoose.model('ApiMetric', ApiMetricSchema);

步骤2:选择适配的耗时统计实现

根据你要统计的范围,二选一实现即可:

  • 方案A:全局中间件统计(推荐,真正覆盖全链路)
    直接在服务入口加全局中间件,覆盖所有HTTP请求从进入服务到响应返回的全流程耗时,包含业务逻辑、数据库操作、第三方调用等所有环节的总耗时,不需要修改每个业务接口代码,以Express框架为例:

    // 全局耗时统计中间件,注册在所有业务路由之前
    app.use((req, res, next) => {
      const startTime = Date.now();
    
      // 监听响应结束事件,此时请求全流程已经执行完成
      res.on('finish', () => {
        const executionTime = (Date.now() - startTime) / 1000;
        logger.info(`接口 ${req.method} ${req.path} 耗时: ${executionTime}s`);
    
        // 异步写入MongoDB,不要加await阻塞主流程
        ApiMetric.create({
          apiPath: req.path,
          method: req.method,
          executionTime,
          isSuccess: res.statusCode < 400,
          errorMsg: res.statusCode >= 400 ? res.statusMessage : ''
        }).catch(err => {
          // 统计逻辑报错仅打日志,绝对不能影响主业务
          logger.error('耗时记录写入Mongo失败:', err);
        });
      });
    
      next();
    });
    
  • 方案B:数据库查询层封装(仅统计数据库操作耗时)
    如果你只需要统计MongoDB查询本身的耗时,可以封装通用查询方法,对齐你之前使用pool.query的调用习惯:

    /**
     * 带耗时统计的通用查询方法
     * @param {mongoose.Model} model 要操作的业务模型,对应PG里的不同表
     * @param {Function} queryExec 具体查询逻辑
     * @param {Object} metricMeta 请求元信息
     * @returns 查询结果
     */
    const queryWithTiming = async (model, queryExec, metricMeta = {}) => {
      const startTime = Date.now();
      let result = null;
      let error = null;
      let isSuccess = false;
    
      try {
        result = await queryExec(model);
        isSuccess = true;
        return result;
      } catch (err) {
        error = err;
        throw err;
      } finally {
        // 无论成功失败都统一计算耗时、写日志、存库,避免重复代码
        const executionTime = (Date.now() - startTime) / 1000;
        logger.info(`DB查询耗时: ${executionTime}s`);
    
        ApiMetric.create({
          apiPath: metricMeta.apiPath || 'unknown',
          method: metricMeta.method || 'unknown',
          executionTime,
          isSuccess,
          errorMsg: error ? error.message : ''
        }).catch(err => logger.error('耗时记录写入Mongo失败:', err));
      }
    };
    

    实际业务调用方式和你之前写PG的逻辑基本一致:

    // 提前定义业务模型,以用户表为例
    const User = mongoose.model('User', new Schema({ name: String, age: Number }));
    
    // 业务接口中调用
    const getUserList = async (req, res) => {
      try {
        const userList = await queryWithTiming(
          User,
          // 传入具体查询逻辑,和直接调用Model方法写法完全一致
          (model) => model.find({ age: { $gte: 18 } }).limit(20),
          { apiPath: req.path, method: req.method }
        );
        res.json({ code: 0, data: userList });
      } catch (err) {
        res.status(500).json({ code: 500, msg: err.message });
      }
    };
    
实现注意事项
  • 耗时数据写入MongoDB时不要使用await等待写入完成,用异步非阻塞方式执行,避免统计逻辑拖慢正常业务响应
  • 所有统计逻辑的异常必须单独捕获,不能因为监控日志写入失败导致主业务请求报错
  • 存储耗时数据的集合一定要配置TTL过期索引,避免服务长期运行后日志数据占满磁盘
  • 如果使用Koa、Egg等其他Node框架,全局中间件的实现逻辑一致,仅响应监听的写法略有差异

内容的提问来源于stack exchange,提问作者Rumpelstiltskin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:09:26