是否有可能获得node.js服务的当前请求?

时间:2012-09-25 03:35:02

标签: javascript node.js express

我正在使用express.js。每当有人试图记录消息时,我都需要能够记录某些请求数据。为此,我想创建一个像这样的辅助方法

function log_message(level, message){
  winston.log(level, req.path + "" + message);
}

然后我会使用这样的方法。

exports.index = function(req, res){
  log_message("info", "I'm here");
}

请注意,我没有将req对象传递给log_message函数。我希望透明地完成它,以便log_message API用户不需要知道正在记录的公共数据。

有没有办法用express.js / node.js实现这一目标。请求对象是否可以从某种全局变量中获得?

6 个答案:

答案 0 :(得分:10)

有趣的方法是使用新的域名功能。 http://nodejs.org/api/domain.html

域虽然提供出色的错误恢复,但可以用作“线程本地存储”类型 - 基本上为每个请求存储数据。

创建一些中间件,将每个请求/响应添加到域中。

app.use(function(req, res, next) {
  var reqd = domain.create();
  reqd.add(req);
  reqd.add(res);
  reqd._req = req; // Add request object to custom property
  // TODO: hook error event on reqd (see docs)
  next();
});

在日志功能中,您现在可以获取当前域并提取请求对象。

function log_message(level, message) {
  // Pull the request from the current domain.
  var request = process.domain._req;

  // TODO: log message
};

域仍然是实验性的,但从现在到1.0版本之间听起来不会有太多变化。

答案 1 :(得分:5)

与域名回答类似,现在使用continuation-local-storage更容易做到这一点:https://datahero.com/blog/2014/05/22/node-js-preserving-data-across-async-callbacks/

在DataHero中,我们使用所有日志消息保存事务ID,用户ID和会话ID。您不需要将请求对象一直传递下去,因此它也有助于保持模型/业务层的清洁。

答案 2 :(得分:3)

log_message如何向调用者(模块等)公开,对路由前的管道有什么控制?

您可以在此路由调用之前应用中间件并使函数log_message来自闭包内,或者您可以利用req EventEmitter工具并将调用包装到winston.log中req.end的处理程序,只记录请求期间发出的所有消息。这将有效地将您的log_message更改为日志消息的累加器(可能在数组中),并在请求结束时将它们全部记录下来。

这一切都取决于你如何暴露这些东西。

许多猫在这里被剥皮了:)

答案 3 :(得分:3)

创建中间件:

app.use(function(req, res, next) {
       var tid = uuid.v4();
     var cls = require('continuation-local-storage');
       var namespace = cls.createNamespace('com.storage');
      var pre_ip;
          if(get_ip(req))
          { ip_info= get_ip(req).clientIp;
              pre_ip=ip_info
          }
          namespace.bindEmitter(req);
          namespace.bindEmitter(res);
        namespace.run(function() {
               console.log(logobj);
              namespace.set('tid', tid);
              namespace.set('ip',ip_info);
           namespace.set('logobj',logobj);   
              next();
          });
      });

并使用它:

var cls = require('continuation-local-storage');
 var namespace = cls.getNamespace('com.storage');
      namespace.get('ip');

答案 4 :(得分:1)

以下解决方案对我来说是可以接受的。

在这里,我有一个中间件,它将log_message方法添加到请求对象上。之后,我只需调用req.log_message来记录消息。虽然这与将req对象传递给每个日志记录调用非常相似,但它只是稍微清晰一点。

function logging_middleware(req, res, next){
    req.log_message = function(level, message){
        winston.log(level, req.path + ":" + message);
    }
    next();
}

答案 5 :(得分:1)

找到了一个有效的“线程安全”解决方案,它可以在整个请求中传递数据,而无需修改req对象,并将其传递给所有层。

因此,我们拥有continuation-local-storage软件包,该软件包使我们能够在req的开头绑定req,然后在下一次调用时重用。但是,当您尝试使用异步调用链时,CLS并不是“线程安全的”,并且如果您尝试在少数并发请求中对其进行调用,则感觉很糟糕-它会失败并丢失“上下文”。

因此,我们有了cls-hooked软件包,它可以解决此问题,并且可以轻松地在请求开始时捕获当前上下文绑定请求。

有一个使用awilix(依赖关系注入框架)的小例子,我们将创建“ RequestContext”类,并能够在需要的任何地方使用它

让我们创建RequestContext类:

module.exports = class RequestContext {

 constructor(req, res, dependencies) {
 const { myService1, myService2 } = dependencies;    
 this.req = req;
 this.res = res;
}

getMyTestHeader() {
 return this.req.headers.testHeader;
}

这是一个简单的“请求包装”。如果需要,它将消耗请求,响应和其他依赖关系,并提供getMyTestHeader以在类外使用。

让我们在请求开始时创建中间件(在所有其他组件之前):

  //Registering our namespace. creating it one per app
  const clsNamespace = require('cls-hooked').createNamespace('my-per-request-session'); 
  app.use((req, res, next) => {
      // binding continuation-local-storage (cls-hooked) to request and response
      clsNamespace.bind(req);
      clsNamespace.bind(res);
      clsNamespace.run(() => {
      // save here req and res to use it later in requestContext instance, it will alive during request and could be picked up from DI as other dependencies
       clsNamespace.set('req', req);
       clsNamespace.set('res', res);
       next();
      });

  });

现在,让我们使用awilix来注册DI呼叫:

container.register({  
requestContextProvider: asFunction(dependencies => ({
 getCurrentContext: () => {
  //retrieve res and req on each DI call "from current request"
  //also, clsNamespace should be the same as we registered before, on original code it is also registered using DI container.
  const req = dependencies.clsNamespace.get('req');
  const res = dependencies.clsNamespace.get('res');
  //and return our RequestContext (wrapper for req and res)
  return new RequestContext(req, res, dependencies);
 },
})),

因此,我们正在将requestContextProvider注册为函数,该函数将在每个DI相关性调用中的每个请求中,使用来自外部上下文(cls)的req和res实例化。 结果,我们可以将其用于其他方式(例如在控制器中):

module.exports = (dependencies) => ({
 myControllerAction: async (req, res) => {
  const {requestContextProvider} = dependencies;
  const requestContext = requestContextProvider.getCurrentContext();
  //it will return header from req!
  const myHeader = requestContext.getMyTestHeader(); 
  res.status(200).json({ myHeader });
 },    
});
如您所见,

我们现在可以在每个层级(控制器/ BLL / DAL /帮助器等)访问DI的任何地方都有“ requestContext”。因此,它是“线程安全的”,易于测试并且不需要通过所有“中间”层丢弃req对象。

同意,不是最好和容易的例子,但是希望,它可以帮助某人。