Skip to content

Commit

Permalink
Merge 58065ad into d0ace06
Browse files Browse the repository at this point in the history
  • Loading branch information
puzpuzpuz committed Jun 6, 2020
2 parents d0ace06 + 58065ad commit 3641b4e
Show file tree
Hide file tree
Showing 12 changed files with 351 additions and 217 deletions.
10 changes: 3 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
language: node_js
node_js:
- "8"
- "9"
- "9.5.0"
- "10.4"
- "12.9"
before_install:
- npm i -g npm@^6.9.0
- "12.17"
- "13.13"
- "14"
install:
- npm install
script:
Expand Down
26 changes: 11 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,29 @@

# cls-rtracer

Request Tracer - Express, Fastify and Koa middlewares, and Hapi plugin for CLS-based request id generation, batteries included. An out-of-the-box solution for adding request ids into your logs. Check out [this Medium post](https://medium.com/@apechkurov/request-id-tracing-in-node-js-applications-c517c7dab62d) that describes the rationale behind `cls-rtracer`.
Request Tracer - Express, Fastify and Koa middlewares, and Hapi plugin 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](https://medium.com/@apechkurov/request-id-tracing-in-node-js-applications-c517c7dab62d) that describes the rationale behind `cls-rtracer`.

Automatically generates a UUID V1 value as the id for each request and stores it in Continuation-Local Storage (CLS, see [cls-hooked](https://github.com/jeff-lewis/cls-hooked)). 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.
Automatically generates a UUID V1 value as the id for each request and stores it in `AsyncLocalStorage` (CLS core API, see [this blog post](https://itnext.io/one-node-js-cls-api-to-rule-them-all-1670ac66a9e8)). 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, Koa (both v1 and v2), and Hapi v18.

## Supported Node.js versions

As `cls-rtracer` v2 depends on [`AsyncLocalStorage API`](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage), it requires Node.js 12.17.0+, 13.13.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`](https://github.com/jeff-lewis/cls-hooked).

## How to use it - Step 1

Install:

```bash
npm install --save cls-rtracer cls-hooked
npm install --save cls-rtracer
```

Note: `cls-hooked` has to be installed explicitly, as it's a [peer dependency](https://nodejs.org/es/blog/npm/peer-dependencies/) for this library. See [this issue](https://github.com/puzpuzpuz/cls-rtracer/issues/18) for more details.

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, e.g. express-session, body-parser, or express-jwt, 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.
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 the 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)

Expand Down Expand Up @@ -290,21 +292,15 @@ These are the available config options for the middleware functions. All config

To avoid weird behavior:

* Make sure you require `cls-rtracer` as the first dependency in your app. Some popular packages may use async which breaks CLS.

* 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](#how-to-use-it---step-2-common-instructions).

Note: there is a small chance that you are using one of rare libraries that do not play nice with Async Hooks API, which is internally used by the `cls-hooked` library. So, if you face the issue when CLS context (and thus, the request id) is lost at some point of async calls chain, please submit GitHub issue with a detailed description.

Note for Node 10 users:

* Node 10.0.x-10.3.x is not supported. That's because V8 version 6.6 introduced a bug that breaks async_hooks during async/await. Node 10.4.x uses V8 v6.7 where the bug is fixed. See: https://github.com/nodejs/node/issues/20274.
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 CLS (or more precisely, 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.
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 and got about 10–15% RPS (request per second) degradation when `cls-rtracer` is used. See [this post](https://stackoverflow.com/questions/50595130/express-what-load-can-continuation-local-storage-handle/53647537#53647537) for more details.
The author of this library did some basic performance testing. See [this tweet](https://twitter.com/AndreyPechkurov/status/1234189388436967426) to see the results. The overhead also decreased in `cls-rtracer` v2 due to migration to the core API. See [this tweet](https://twitter.com/AndreyPechkurov/status/1268950294165143553?s=20) to learn more.

## License

Expand Down
155 changes: 9 additions & 146 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,154 +1,17 @@
'use strict'

const cls = require('cls-hooked')
const uuidv1 = require('uuid/v1')

// generate a unique value for namespace
const nsid = `rtracer:${uuidv1()}`
const ns = cls.createNamespace(nsid)

/**
* Generates a request tracer middleware for Express.
* @param {Object} options possible options
* @param {boolean} options.useHeader respect request header flag
* (default: `false`)
* @param {string} options.headerName request header name, used if `useHeader` is set to `true`
* (default: `X-Request-Id`)
*/
const expressMiddleware = ({
useHeader = false,
headerName = 'X-Request-Id'
} = {}) => {
return (req, res, next) => {
ns.bindEmitter(req)
ns.bindEmitter(res)

let requestId
if (useHeader) {
requestId = req.headers[headerName.toLowerCase()]
}
requestId = requestId || uuidv1()

ns.run(() => {
ns.set('requestId', requestId)
next()
})
}
}

/**
* Generates a request tracer middleware for Koa v2.
* @param {Object} options possible options
* @param {boolean} options.useHeader respect request header flag
* (default: `false`)
* @param {string} options.headerName request header name, used if `useHeader` is set to `true`
* (default: `X-Request-Id`)
*/
const koaMiddleware = ({
useHeader = false,
headerName = 'X-Request-Id'
} = {}) => {
return (ctx, next) => {
ns.bindEmitter(ctx.req)
ns.bindEmitter(ctx.res)

let requestId
if (useHeader) {
requestId = ctx.request.headers[headerName.toLowerCase()]
}
requestId = requestId || uuidv1()

return new Promise(ns.bind((resolve, reject) => {
ns.set('requestId', requestId)
return next().then(resolve).catch(reject)
}))
}
}

/**
* Generates a request tracer middleware for Koa v1.
* @param {Object} options possible options
* @param {boolean} options.useHeader respect request header flag
* (default: `false`)
* @param {string} options.headerName request header name, used if `useHeader` is set to `true`
* (default: `X-Request-Id`)
*/
const koaV1Middleware = ({
useHeader = false,
headerName = 'X-Request-Id'
} = {}) => {
return function * (next) {
ns.bindEmitter(this.req)
ns.bindEmitter(this.res)

const clsCtx = ns.createContext()
ns.enter(clsCtx)
try {
let requestId
if (useHeader) {
requestId = this.request.headers[headerName.toLowerCase()]
}
requestId = requestId || uuidv1()
ns.set('requestId', requestId)

yield next
} finally {
ns.exit(clsCtx)
}
}
}

const pluginName = 'cls-rtracer'

/**
* A request tracer plugin for Hapi
* @type {{once: boolean, name: string, register: hapiPlugin.register}}
*/
const hapiPlugin = ({
name: pluginName,
once: true,
register: async (server, options) => {
const {
useHeader = false,
headerName = 'X-Request-Id'
} = options

server.ext('onRequest', (request, h) => {
ns.bindEmitter(request.raw.req)
ns.bindEmitter(request.raw.res)

const clsCtx = ns.createContext()
ns.enter(clsCtx)

request.plugins[pluginName] = {
context: clsCtx
}

let requestId
if (useHeader) {
requestId = request.headers[headerName.toLowerCase()]
}
requestId = requestId || uuidv1()
ns.set('requestId', requestId)

return h.continue
})

server.events.on('response', request => {
const clsCtx = request.plugins[pluginName].context
ns.exit(clsCtx)
})
}
})

/**
* Returns request tracer id or `undefined` in case if the call is made from an outside CLS context.
*/
const id = () => ns.get('requestId')
const {
expressMiddleware,
fastifyMiddleware,
koaMiddleware,
koaV1Middleware,
hapiPlugin,
id
} = require('./src/middleware')

module.exports = {
expressMiddleware,
fastifyMiddleware: expressMiddleware,
fastifyMiddleware,
koaMiddleware,
koaV1Middleware,
hapiPlugin,
Expand Down
26 changes: 11 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cls-rtracer",
"version": "1.4.1",
"version": "2.0.0",
"description": "Express, Fastify and Koa middlewares, and Hapi plugin for CLS-based request id generation, batteries included",
"author": {
"name": "Andrey Pechkurov",
Expand All @@ -19,7 +19,7 @@
"homepage": "https://github.com/puzpuzpuz/cls-rtracer",
"main": "index.js",
"engines": {
"node": ">=8.0.0 <10.0.0 || >=10.4.0"
"node": ">=12.17.0 <13.0.0 || >=13.13.0 <14.0.0 || >=14.0.0"
},
"files": [
"index.js",
Expand All @@ -46,22 +46,18 @@
"logging"
],
"dependencies": {
"uuid": "3.3.3"
"uuid": "8.1.0"
},
"devDependencies": {
"@hapi/hapi": "^18.4.0",
"cls-hooked": "^4.2.2",
"coveralls": "^3.0.3",
"express": "^4.16.4",
"fastify": "^2.6.0",
"jest": "^24.7.1",
"koa": "^2.7.0",
"koav1": "npm:koa@^1.6.2",
"standard": "^12.0.1",
"@hapi/hapi": "^19.1.1",
"coveralls": "^3.1.0",
"express": "^4.17.1",
"fastify": "^2.14.1",
"jest": "^26.0.1",
"koa": "^2.12.0",
"koav1": "npm:koa@^1.7.0",
"standard": "^14.3.4",
"supertest": "^3.4.2",
"winston": "^3.2.1"
},
"peerDependencies": {
"cls-hooked": "^4.2.2"
}
}

0 comments on commit 3641b4e

Please sign in to comment.