Request Tracer - Express & Koa middlewares and Fastify & Hapi plugins for CLS-based request id generation, batteries included. 请求跟踪程序-Express和Koa和Fastify以及用于基于CLS的请求id生成的Hapi插件,包括电池。An out-of-the-box solution for adding request ids into your logs. 将请求ID添加到日志中的现成解决方案。Check out this blog post that describes the rationale behind 查看这篇描述cls rtracer背后原理的博客文章。cls-rtracer
.
Automatically generates a UUID V1 value as the id for each request and stores it in 自动生成一个UUID V1值作为每个请求的id,并将其存储在AsyncLocalStorage
(CLS core API, see this blog post). AsyncLocalStorage
中(CLS core API,请参阅此文章)。Optionally, if the request contains 或者,如果请求包含X-Request-Id
header, uses its value instead. X-request-Id
头,则使用其值。Allows to obtain the generated request id anywhere in your routes later and use it for logging or any other purposes.允许以后在路由中的任何位置获取生成的请求id,并将其用于日志记录或任何其他目的。
Tested and works fine with Express v4, Fastify v2 and v3, Koa v1 and v2, and Hapi v18.测试和工作良好的Express V4,FASTUP V2和V3,膝关节炎V1和V2,和HAPI V18。
As 由于cls-rtracer
v2 depends on AsyncLocalStorage API, it requires Node.js 12.17.0+, 13.14.0+, or 14.0.0+. cls-rtracer
v2依赖于AsyncLocalStorage API,因此需要Node.js 12.17.0+、13.14.0+或14.0.0+。If you happen to use an older Node.js version, you should use cls-rtracer v1 which is based on 如果您碰巧使用了较旧的Node.js版本,那么应该使用基于cls-hooked
.cls-hooked
的cls-rtracer v1。
Install:安装:
npm install --save cls-rtracer
Note for TypeScript users: typings are included.TypeScript用户注意:包括打字。
Use the middleware (or plugin) provided by the library before the first middleware that needs to have access to request ids. 在需要访问请求ID的第一个中间件之前,使用库提供的中间件(或插件)。Note that some middlewares, may cause CLS context (i.e. Async Hooks execution path) to get lost. 请注意,某些中间件可能会导致CLS上下文(即异步挂钩执行路径)丢失。To avoid such issues, you should use any third party middleware that does not need access to request ids before you use this middleware. 为了避免此类问题,在使用此中间件之前,应该使用任何不需要访问请求ID的第三方中间件。See issue #20 as an example.以第20期为例。
Use the middleware provided by the library:使用库提供的中间件:
const express = require('express')
const rTracer = require('cls-rtracer')
const app = express()
// any third party middleware that does not need access to request ids goes here
// ...
app.use(rTracer.expressMiddleware())
// optionally, you can override default middleware config:
// app.use(rTracer.expressMiddleware({
// useHeader: true,
// headerName: 'X-Your-Request-Header'
// }))
// all code in middlewares, starting from here, has access to request ids从这里开始,中间件中的所有代码都可以访问请求ID
Obtain request id in middlewares on the incoming request:在传入请求的中间件中获取请求id:
// an example middleware for a generic find entity endpoint通用查找实体端点的示例中间件
app.get('/api/v1/entity/{id}', (req, res, next) => {
entityService.find(req.params.id)
.then((entity) => {
// you can obtain the request id here
const requestId = rTracer.id()
console.log(`requestId: ${requestId}`)
res.json(entity)
})
.catch(next)
})
You can access the same request id from code that does not have access to the Express' 您可以从无法访问Express的req
object.req
对象的代码中访问相同的请求id。
// an imaginary entity-service.js
async function find (entityId) {
// you can obtain the request id here
const requestId = rTracer.id()
// ...
}
Use the plugin provided by the library:使用库提供的插件:
const fastify = require('fastify')()
const rTracer = require('cls-rtracer')
// any third party plugin that does not need access to request ids goes here任何不需要访问请求ID的第三方插件都可以访问此处
// ...
fastify.register(rTracer.fastifyPlugin)
// optionally, you can override default plugin config:
// fastify.register(rTracer.fastifyPlugin, {
// useHeader: true,
// headerName: 'X-Your-Request-Header'
// }))
// all code in plugins or handlers, starting from here, has access to request ids从这里开始,插件或处理程序中的所有代码都可以访问请求ID
Obtain request id in handlers on the incoming request:在传入请求的处理程序中获取请求id:
// an example handler for a generic find entity endpoint通用查找实体终结点的示例处理程序
// router config is skipped for the sake of simplicity为了简单起见,跳过路由器配置
app.get('/test', async (request, reply) => {
const entity = await entityService.find(request.params.id)
// you can obtain the request id here
const requestId = rTracer.id()
console.log(`requestId: ${requestId}`)
reply.send(entity)
})
You can access the same request id from code that does not have access to the Fastify's 您可以从无法访问Fastify的request
object.request
对象的代码中访问相同的请求id。
// an imaginary entity-service.js
async function find (entityId) {
// you can obtain the request id here
const requestId = rTracer.id()
// ...
}
There is a connect-style middleware available for Fastify v2, but it is deprecated and may be removed in one of upcoming releases. Fastify v2有一个连接式中间件,但它已被弃用,可能会在即将发布的版本中删除。If you happen to use it in your application, you should migrate to the Fastify plugin.如果您碰巧在应用程序中使用它,您应该迁移到Fastify插件。
fastify.use(rTracer.fastifyMiddleware())
Use the middleware provided by the library:使用库提供的中间件:
const Koa = require('koa')
const rTracer = require('cls-rtracer')
const app = new Koa()
// any third party middleware that does not need access to request ids goes here
// ...
app.use(rTracer.koaMiddleware())
// optionally, you can override default middleware config:
// app.use(rTracer.koaMiddleware({
// useHeader: true,
// headerName: 'X-Your-Request-Header'
// }))
// all code in middlewares, starting from here, has access to request ids从这里开始,中间件中的所有代码都可以访问请求ID
Obtain request id in middlewares on the incoming request:在传入请求的中间件中获取请求id:
// an example middleware for a generic find entity endpoint通用查找实体端点的示例中间件
// router config is skipped for the sake of simplicity为了简单起见,跳过路由器配置
app.use(async (ctx) => {
const entity = await entityService.find(req.params.id)
// you can obtain the request id here您可以在此处获取请求id
const requestId = rTracer.id()
console.log(`requestId: ${requestId}`)
ctx.body = entity
})
You can access the same request id from code that does not have access to the Koa's 您可以从无法访问Koa的ctx
object.ctx
对象的代码中访问相同的请求id。
// an imaginary entity-service.js
async function find (entityId) {
// you can obtain the request id here
const requestId = rTracer.id()
// ...
}
For Koa v1 use the 对于Koa V1使用koaV1Middleware(options)
function.koaV1Middleware(options)
函数。
Use the plugin provided by the library:使用库提供的插件:
const Hapi = require('@hapi/hapi')
const rTracer = require('cls-rtracer')
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
})
// any third party plugin that does not need access to request ids goes here
// ...
await server.register({
plugin: rTracer.hapiPlugin
})
// optionally, you can override default middleware config:
// await server.register({
// plugin: rTracer.hapiPlugin,
// options: {
// useHeader: true,
// headerName: 'X-Your-Request-Header'
// }
// })
// all code in routes, starting from here, has access to request ids
}
init()
Obtain request id in route handlers on the incoming request:在传入请求的路由处理程序中获取请求id:
// an example route for a generic find entity endpoint
server.route({
method: 'GET',
path: '/test',
handler: async (request, h) => {
const entity = await entityService.find(request.params.id)
// you can obtain the request id here
const requestId = rTracer.id()
console.log(`requestId: ${requestId}`)
return entity
}
})
You can access the same request id from code that does not have access to the Hapi's 您可以从没有访问Hapirequest
object.request
对象权限的代码中访问相同的请求id。
// an imaginary entity-service.js
async function find (entityId) {
// you can obtain the request id here
const requestId = rTracer.id()
// ...
}
The main use case for this library is request id generation and logging automation. 该库的主要用例是请求id生成和日志记录自动化。You can integrate with any logger library in a single place and get request ids in logs across your application.您可以在单个位置与任何记录器库集成,并在整个应用程序的日志中获取请求ID。
Without having request id, as a correlation value, in your logs, you will not be able to determine which log entries belong to code that handles the same request. 如果日志中没有请求id作为相关值,则无法确定哪些日志条目属于处理同一请求的代码。You could generate request ids manually and store them in the Express' 您可以手动生成请求ID并将其存储在Express的req
object (or Fastify's request
, or Koa's ctx
), but then you will have to explicitly pass the object into all other modules on the route. req
对象(或Fastify的request
或Koa的ctx
)中,但随后您必须将该对象显式地传递到路由上的所有其他模块中。And that's when 那就是cls-rtracer
comes to the rescue!cls-rtracer
来营救的时候了!
Complete samples for Express, Fastify and Koa are available in Express、FASTURE和膝关节炎的完整样品可在/samples/
directory./samples/
目录中找到。
Here is how you can integrate 以下是如何将cls-rtracer
with winston, one of most popular logging libraries.cls-rtracer
与winston(最流行的日志库之一)集成。
const { createLogger, format, transports } = require('winston')
const { combine, timestamp, printf } = format
// a custom format that outputs request id输出请求id的自定义格式
const rTracerFormat = printf((info) => {
const rid = rTracer.id()
return rid
? `${info.timestamp} [request-id:${rid}]: ${info.message}`
: `${info.timestamp}: ${info.message}`
})
const logger = createLogger({
format: combine(
timestamp(),
rTracerFormat
),
transports: [new transports.Console()]
})
This is how you can integrate 这就是如何将cls-rtracer
with pino logger.cls-rtracer
与pino logger集成的方法。
// mixin function adds properties of the returned object to the logged JSON.
const logger = require('pino')({
mixin () {
return { requestId: rTracer.id() }
}
})
These are the available config options for the middleware/plugin functions. 这些是中间件/插件功能的可用配置选项。All config entries are optional.所有配置项都是可选的。
{
// Add request id to response header (default: false).
// If set to true, the middleware/plugin will add request id to the specified
// header. Use headerName option to specify header name.
echoHeader: false,
// Respect request header flag (default: false).
// If set to true, the middleware/plugin will always use a value from
// the specified header (if the value is present).
useHeader: false,
// Request/response header name, case insensitive (default: 'X-Request-Id').
// Used if useHeader/echoHeader is set to true.
headerName: 'X-Request-Id',
// A custom function to generate your request ids (default: UUID v1).
// The function will receive the intercepted request (as-is from the framework
// being used) as its first argument. The returned id could be a usual string,
// or a number, or any custom object, like in the example below.
// Ignored if useHeader is set to true.
requestIdFactory: (req) => ({
id: 'Your request id',
customHeader: req.headers['X-Custom-Header']
}),
// Use request id generated by Fastify instead of generating a new id.
// Only available for the Fastify plugin.
useFastifyRequestId: false,
}
In certain situations you may want to have an id available outside of the request handler scope, say, in a code that acts as a background job. 在某些情况下,您可能希望在请求处理程序范围之外(例如,在充当后台作业的代码中)有一个可用的id。In this case you may use the 在这种情况下,可以使用runWithId()
function:runWithId()
函数:
const rTracer = require('cls-rtracer')
rTracer.runWithId(() => {
console.log(rTracer.id()) // id is available here
setInterval(() => {
console.log(rTracer.id()) // and here
}, 1000)
})
// you may override id by providing the 2nd argument您可以通过提供第二个参数来覆盖id
rTracer.runWithId(() => {
// ...
}, 42) // 42 is the id override here
// async/await syntax is also supported, as `runWithId()`
// returns the result of `fn`
await rTracer.runWithId(myAsyncFn)
To avoid weird behavior:要避免奇怪的行为:
cls-rtracer
. cls-rtracer
之前,请确保使用任何不需要访问请求ID的第三方中间件(或插件)。Note: there is a small chance that you are using one of rare libraries that do not play nice with Async Hooks API. 注意:您很可能正在使用一个罕见的库,它不能很好地处理异步Hooks API。So, if you face the issue when the context (and thus, the request id) is lost at some point of async calls chain, please submit GitHub issue with a detailed description.因此,如果您在异步调用链的某个点丢失了上下文(因此,请求id)时遇到问题,请提交GitHub问题并提供详细说明。
Note that this library has a certain performance impact on your application due to Async Hooks API usage. 请注意,由于异步钩子API的使用,此库对应用程序有一定的性能影响。So, you need to decide if the benefit of being able to trace requests in logs without any boilerplate is more valuable for you than the disadvantage of performance impact.因此,您需要确定能够在没有任何样板文件的情况下在日志中跟踪请求的好处是否比性能影响的缺点更有价值。
The author of this library did some basic performance testing. 这个库的作者做了一些基本的性能测试。See this tweet to see the results. 查看此推文以查看结果。The overhead also decreased in 由于迁移到核心API,cls-rtracer
v2 due to migration to the core API. cls-rtracer
中的开销也减少了。See this tweet to learn more.查看此推文了解更多信息。
Licensed under MIT.