10

我正在使用 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 实现这一点。请求对象是否可以从某种全局变量中获得?

4

6 回答 6

11

一个有趣的方法是新的域功能。 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 版本之间不会有太大变化。

于 2012-09-25T04:14:12.067 回答
6

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

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

于 2014-05-22T16:07:24.247 回答
5

创建中间件:

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');
于 2015-03-16T09:10:49.707 回答
3

如何log_message暴露给调用者(模块等)以及您对路由之前的管道有什么控制?

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

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

许多猫在这里被剥皮:)

于 2012-09-25T03:43:35.373 回答
2

我可以接受以下解决方案。

在这里,我有一个中间件,它将 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();
}
于 2012-09-25T16:15:43.493 回答
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/helpers 等)可以访问 DI 的任何地方都有“requestContext”。因此,它是“线程安全的”,易于测试,并且不需要通过所有“中间”层抛出 req 对象。

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

于 2020-03-20T23:53:06.057 回答