我正在使用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实现这一目标。请求对象是否可以从某种全局变量中获得?
答案 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对象。
同意,不是最好和容易的例子,但是希望,它可以帮助某人。