@cjanietz/cls-rtracer

中文文档
2.6.0发布于 2021/02/22MITCommonJS近 30 天下载 5853

Express & Koa middlewares and Fastify & Hapi plugins for CLS-based request id generation, batteries included

原始使用文档
文档目录15 节
  1. Supported Node.js versions
  2. How to use it - Step 1
  3. How to use it - Step 2 (Common instructions)
  4. How to use it - Step 2 (Express users)
  5. How to use it - Step 2 (Fastify users)
  6. Legacy Fastify middleware
  7. How to use it - Step 2 (Koa users)
  8. Koa v1 support
  9. How to use it - Step 2 (Hapi users)
  10. Integration with loggers
  11. Configuration
  12. Advanced features
  13. Troubleshooting
  14. Performance impact
  15. License

travis Coverage Status npm npm

cls-rtracer

Request Tracer - Express & Koa middlewares and Fastify & Hapi plugins for CLS-based request id generation, batteries included. An out-of-the-box solution for adding request ids into your logs. Check out this blog post that describes the rationale behind cls-rtracer.

Automatically generates a UUID V1 value as the id for each request and stores it in AsyncLocalStorage (CLS core API, see this blog post). Optionally, if the request contains X-Request-Id header, uses its value instead. Allows to obtain the generated request id anywhere in your routes later and use it for logging or any other purposes.

Tested and works fine with Express v4, Fastify v2 and v3, Koa v1 and v2, and Hapi v18.

Supported Node.js versions

As cls-rtracer v2 depends on AsyncLocalStorage API, it requires Node.js 12.17.0+, 13.14.0+, or 14.0.0+. If you happen to use an older Node.js version, you should use cls-rtracer v1 which is based on cls-hooked.

How to use it - Step 1

Install:

npm install --save cls-rtracer

Note for TypeScript users: typings are included.

How to use it - Step 2 (Common instructions)

Use the middleware (or plugin) provided by the library before the first middleware that needs to have access to request ids. Note that some middlewares, may cause CLS context (i.e. Async Hooks execution path) to get lost. To avoid such issues, you should use any third party middleware that does not need access to request ids before you use this middleware. See issue #20 as an example.

How to use it - Step 2 (Express users)

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

Obtain request id in middlewares on the incoming request:

// 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' req object.

// an imaginary entity-service.js
async function find (entityId) {
  // you can obtain the request id here
  const requestId = rTracer.id()
  // ...
}

How to use it - Step 2 (Fastify users)

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
// ...

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

Obtain request id in handlers on the incoming request:

// 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 request object.

// an imaginary entity-service.js
async function find (entityId) {
  // you can obtain the request id here
  const requestId = rTracer.id()
  // ...
}

Legacy Fastify middleware

There is a connect-style middleware available for Fastify v2, but it is deprecated and may be removed in one of upcoming releases. If you happen to use it in your application, you should migrate to the Fastify plugin.

fastify.use(rTracer.fastifyMiddleware())

How to use it - Step 2 (Koa users)

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

Obtain request id in middlewares on the incoming request:

// 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
  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 ctx object.

// an imaginary entity-service.js
async function find (entityId) {
  // you can obtain the request id here
  const requestId = rTracer.id()
  // ...
}

Koa v1 support

For Koa v1 use the koaV1Middleware(options) function.

How to use it - Step 2 (Hapi users)

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:

// 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 request object.

// an imaginary entity-service.js
async function find (entityId) {
  // you can obtain the request id here
  const requestId = rTracer.id()
  // ...
}

Integration with loggers

The main use case for this library is request id generation and logging automation. You can integrate with any logger library in a single place and get request ids in logs across your application.

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. You could generate request ids manually and store them in the 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. And that's when cls-rtracer comes to the rescue!

Here is how you can integrate cls-rtracer with winston, one of most popular logging libraries.

const { createLogger, format, transports } = require('winston')
const { combine, timestamp, printf } = format

// a custom format that outputs request 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()]
})

Complete samples for Express, Fastify and Koa are available in /samples/ directory.

Configuration

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,
}

Advanced features

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. In this case you may use the runWithId() function:

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
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)

Troubleshooting

To avoid weird behavior:

  • Make sure you use any third party middleware (or plugin) that does not need access to request ids before you use cls-rtracer. See this section.

Note: there is a small chance that you are using one of rare libraries that do not play nice with Async 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.

Performance impact

Note that this library has a certain performance impact on your application due to Async Hooks API usage. 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 cls-rtracer v2 due to migration to the core API. See this tweet to learn more.

License

Licensed under MIT.

版本与发布记录

@cjanietz/cls-rtracer 共发布 1 个版本,首次发布于 2021/02/22;过去 12 个月发布了 0 个版本。

版本发布日期距今
2.6.0latest2021/02/225 年前

关于 @cjanietz/cls-rtracer 的常见问题

@cjanietz/cls-rtracer 是什么?

Express & Koa middlewares and Fastify & Hapi plugins for CLS-based request id generation, batteries included

如何安装 @cjanietz/cls-rtracer?

在项目目录运行 npm install @cjanietz/cls-rtracer,或使用 pnpm add / yarn add / bun add。

@cjanietz/cls-rtracer 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/cjanietz__cls-rtracer 是否存在。

@cjanietz/cls-rtracer 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@cjanietz/cls-rtracer 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=12.17.0 <13.0.0 || >=13.14.0 <14.0.0 || >=14.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cjanietz/cls-rtracer 可以商用吗?

@cjanietz/cls-rtracer 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cjanietz/cls-rtracer 的下载量有多少?

根据 npm 官方下载统计,@cjanietz/cls-rtracer 在 2026/10/10 – 2026/11/08 这 30 天内被下载 5,853 次,日均约 195 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cjanietz/cls-rtracer 还在维护吗?

最新版本 2.6.0 发布于 2021/02/22(5 年前);至今共发布 1 个版本,其中过去 12 个月发布了 0 个;首次发布于 2021/02/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。