NodeJS Express - Global Unique Request Id

2020-02-04 10:10发布

问题:

Is it possible to define a unique request Id that is included in each log statement without handing the logger to each method/function call?

Technologies in use: NodeJS, Express, Winston

回答1:

Edited

Finally, I have created a library that makes all the work. https://github.com/davicente/express-logger-unique-req-id

It is a wrapper of Winston library, so you can use it the same way.

Let me know if it helps you


We had this same problem in several projects, and I couldn't finde any complete solution for this question. We are using same technologies (Node.js, Express.js and Winston for logs) I found a solution to this using a couple of libraries and wrapping Winston library: - node-uuid for creating unique identificators for each request - continuation-local-storage for sharing this IDs among different modules without sending req object in all the calls.

First I need to create and set the unique identificator with each request. I do it in the middleware:

var uuid = require('node-uuid');
var createNamespace = require('continuation-local-storage').createNamespace;
var myRequest = createNamespace('my request');

// Run the context for each request. Assign a unique identifier to each request
app.use(function(req, res, next) {
    myRequest.run(function() {
        myRequest.set('reqId', uuid.v1());
        next();
    });
});

After that I had to wrap Winston library, recovering the id from the context and adding to the message of the log:

var winston = require('winston');
var getNamespace = require('continuation-local-storage').getNamespace;

// Wrap Winston logger to print reqId in each log
var formatMessage = function(message) {
    var myRequest = getNamespace('my request');
    message = myRequest && myRequest.get('reqId') ? message + " reqId: " + myRequest.get('reqId') : message;
    return message;
};

var logger = {
    log: function(level, message) {
        winstonLogger.log(level, formatMessage(message));
    },
    error: function(message) {
        winstonLogger.error(formatMessage(message));
    },
    warn: function(message) {
        winstonLogger.warn(formatMessage(message));
    },
    verbose: function(message) {
        winstonLogger.verbose(formatMessage(message));
    },
    info: function(message) {
        winstonLogger.info(formatMessage(message));
    },
    debug: function(message) {
        winstonLogger.debug(formatMessage(message));
    },
    silly: function(message) {
        winstonLogger.silly(formatMessage(message));
    }
};
module.exports = logger;

I think it was a little bit complex, so I decided to write it down in a post. You can get more information from there: Express.js: Logging info with global unique request ID – Node.js

I hope this helps with your problem.



回答2:

This answer has a problem: the counter goes back to 0 every time the node process is restarted. Turns out there is fairly simple to work around. You simply add an express middleware that tags each request called with a UUID using the uuid package.

For uuid Pre-2.0

const uuid = require('uuid');
const app = express();
app.use(function (req, next) {
  req.id = uuid.v4();
  next();
});

For uuid 3.0+

const uuidv4 = require('uuid/v4');
const app = express();
app.use(function (req, next) {
  req.id = uuidv4();
  next();
});


回答3:

At the very beginning of your request handling add something like the following (or put it in its own file):

var makeID = (function() {
  var index = 0;
  return function() {
   return index++;
  }
})();

app.use(function(req, res, next) {
  req.id = makeID()
  next()
})

This will give every request a unique (sequential) id. Do not allow people to identify themselves with it, only use it internally!

When logging in Winston, you can enter metadata, to be displayed after the message (in the form ${name}=${value}), which looks like this

app.use(function(req, res) {
  winston.log('info', 'Test Log Message', { id: req.id });
  res.end("Done.")
});

Hope this is helpful.