Node.js Express请求ID

15

我想为记录目的创建某种请求ID,该ID将通过请求流程中的每个功能对我可用。我希望记录每个请求流程的每个步骤,并使用一个ID说明哪个日志行是哪个请求的。

我查看了一些想法,并遇到了2个主要建议:

第一个是创建一个中间件,将在'req'对象中添加一个字段,如下所示(如此处建议):

var logIdIterator = 0;

app.all('*', function(req, res, next) {
  req.log = {
    id: ++logIdIterator
  }
  return next();
});

第二种方法是使用continuation-local-storage

存在以下问题:

对于第一种方法-这意味着我将不得不向流程中的每个函数传递一个额外的参数,而且这并不是一个容易解决的方案,尤其是在一个有无数API和流程的成熟应用程序中。

第二种方法看起来很有前途,但不幸的是它存在一些问题,其中状态会丢失(例如请参见这里)。 此外,有几次我们使用了Redis库时也发生了类似问题-这很糟糕,因为Redis请求会在我们的每个流程中发生。

我想如果找不到其他解决方案,我将不得不使用第一种方法,只是我想避免向数千个现有函数传递额外的参数。

我的问题是-您建议如何通过请求流程维护请求ID?


1
将带有仅一个属性(仅为数字)的“req”对象传递,会引起什么问题?我认为这不会导致任何明显的性能损失。 - leroydev
1
我担心的不是性能问题。向大量函数添加额外参数会耗费时间且容易出错。 - gibson
3
因为我们的内部功能不会得到'req'参数,所以为了让它们获得'req'对象或者仅仅是'requestId',需要添加一个额外的参数。 - gibson
1
@SethHolladay 谢谢您的回复。看起来这个 id 是请求对象中的一个字段,但是为了在整个 API 流程中使用它,您必须将其作为参数传递给流程中的所有函数,对吗?如果是这样,那么它与 express 解决方案并没有太大区别。 - gibson
1
在Express中,这确实是一个问题,因为应用程序往往是跨越许多边界转发状态的复杂函数流。在实践中,Hapi避免了这种情况,因为其架构鼓励高度组织化的应用程序结构和隔离的组件。请求对象通常可以在不经过大量传递的情况下使用。查看API。将您可以的内容放入插件中,使用server.handler()或各种扩展点 - Seth Holladay
显示剩余4条评论
5个回答

4
您可以使用这个包:https://www.npmjs.com/package/express-request-id 这是一个中间件,将为每个请求附加uuid。

var app = require('express')();
var addRequestId = require('express-request-id')();
 
app.use(addRequestId);
 
app.get('/', function (req, res, next) {
    res.send(req.id);
    next();
});
 
app.listen(3000, function() {
    console.log('Listening on port %d', server.address().port);
});
 
// curl localhost:3000 
// d7c32387-3feb-452b-8df1-2d8338b3ea22 


1
您有异步通信,想要在不使用闭包或参数传递的情况下保留上下文。恐怕您最好的选择是向所有需要知道它的函数传递一些东西 - 无论是req对象、请求ID、柯里化的log函数调用等等。

无论您传递什么,都可以很容易地重构为其他内容之一 - 例如,一个普通的ID可以从全局存储中查找对象(不好,但可能),等等。考虑到这一点 - 您可能已经将something传递给了唯一标识请求的方法; 在这种情况下,使用它作为键,并从全局存储中查找其他数据(即,require文件与module.exports.cache = new Map();或其他类似的方式,没有理由污染全局命名空间)。

正如您所注意到的,试图对语言进行奇怪的事情通常是脆弱的(特别是当遇到其他奇怪的事情时)。话虽如此,您可以弄清楚continuation-local-storage是如何内部工作的,与您的破坏性库一起进行调试,并使用它或自制解决方案。

你似乎对于维护代码的成本感到不适。这是一种代码异味,添加隐式全局连续本地状态听起来只会使理解和维护代码变得更加困难,而不是更加简单。你可以把这看作是一个学习机会,询问为什么需要请求 ID,以及在编写代码时为什么不需要它。很抱歉,没有了解代码库本身,这是我能给出的最好答案。

1
对于 node >= 16,你可以使用 nodejs 提供的 AsyncLocalStorage。来自 nodejs 文档。
这些类用于关联状态并在回调和 Promise 链中传播它。它们允许在 Web 请求或任何其他异步持续时间中存储数据。它类似于其他语言中的线程本地存储。
你需要创建一个 asyncLocalStorage 实例并导出它。这个相同的实例应该在所有文件中使用。
//asyncLocalStorage.js
const { AsyncLocalStorage } = require('node:async_hooks');

const asyncLocalStorage = new AsyncLocalStorage();

moduel.exports = asyncLocalStorage;

然后,您可以使用中间件来创建requestId,并将该状态与特定请求关联起来。
//middleware.js
const crypto = require('crypto');
const asyncLocalStorage = require('./asyncLocalStorage.js);

function createRequestId(req, res, next) {
  asyncLocalStorage.run(crypto.randomUUID, () => {
    next()
  }
}

module.exports = createRequestId

我们在asyncLocalStorage.run()内部调用next(),以便整个请求都从该函数内部执行,并且我们可以在该请求的整个生命周期中访问requestId。
//service.js
const asyncLocalStorage = require('./asyncLocalStorage.js')
const testfunction() {
  const requestId = asyncLocalStorage.getStore();
}

0

如果您正在使用Express并想要导入express-request-id而不是所需的方法,可以尝试以下操作。

import expressRequestId from 'express-request-id'
import express from 'express'

const framework = express()
framework.use(expressRequestId())

每个函数都会有一个唯一的ID。只需像这样访问它(req.id)。

0

你可以通过以下两种方式使你的原始程序更加复杂:(a) 将信息存储为仅限服务器的 cookie,(b) 在 cookie 中添加更多信息,而不仅仅是计数器。例如,我使用以下内容作为我的应用程序所有调用的跟踪:

console.log('['+count+'] at: '+format.asString('hh:mm:ss.SSS', new Date())+' Url is: ' + req.url);

其中'count'从应用程序启动时开始递增(最好使用持久的单例)。这给了我每次对服务器的调用(req.url)以及调用的确切时间。如果您的应用程序正在进行会话级别管理,这可以轻松扩展以获取sessionID。


1
我不太理解这个解决方案。 假设我刚刚启动了应用程序 - 所以计数器为0,然后服务器处理了3个并发请求。计数器现在将设置为3 - 这意味着如果我想记录函数的整个流程,我将得到所有请求的3,这对我区分它们没有帮助。 我有什么遗漏吗? - gibson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接