42

I'm using Winston in Node.js for logging. I know I can add metadata individually to each log message but is there a way to specify a default set of metadata that'll be added to every log message (such as the app name) as I don't want to specify it every time I need to send a log message.

Sean Bannister
  • 3,105
  • 4
  • 31
  • 43

7 Answers7

49

For Winston v2 (see comments)

There are now rewriters.

For example, the below will add a property app to every metadata going through this logger.

    logger.rewriters.push(function(level, msg, meta) {
      meta.app = 'myApp';
      return meta;
    });

You can also declare it when building the logger:

    new (winston.Logger)({
            level: config.log[file].level,
            rewriters: [
                (level, msg, meta) => {
                    meta.app = 'myApp';
                    return meta;
                }
            ],
            transports: [
                /*your transports*/
            ]
    });
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
DrakaSAN
  • 7,673
  • 7
  • 52
  • 94
  • 3
    winston v3 seems to remove the possibility to mutate the metadata. It means that @Jesse is the preferred solution for winston v3 & + if you want to add metadata. If the output log is a string and not JSON, a custom format can do the job (cf github.com/winstonjs/winston/issues/1140) – Thierry Abaléa Nov 11 '17 at 18:09
  • @ThierryAbaléa: Thanks for the tip, I ll try out the custom format solution, and update my answer accordingly – DrakaSAN Nov 13 '17 at 11:34
  • 1
    The release Winston v3 does allow mutating the metadata, and the issue mentioned by Thierry helped to get an example added to the migration guide and was how I found it too. I have added another answer with the new way. – shadowspawn Aug 07 '18 at 05:46
41

For Winston v3:

const addAppNameFormat = winston.format(info => {
  info.appName = "My Program";
  return info;
});

const logger = winston.createLogger({
  format: winston.format.combine(
    addAppNameFormat(),
    winston.format.json()
  ),
transports: [new winston.transports.Console()]
});

logger.warn('Danger Will Robinson!');
// {"message":"Danger Will Robinson!","level":"warn","appName":"My Program"}

See: https://github.com/winstonjs/winston/blob/HEAD/UPGRADE-3.0.md#migrating-filters-and-rewriters-to-formats-in-winston3

shadowspawn
  • 3,039
  • 22
  • 26
33

There is now (8/20/19) a property defaultMeta that is passed to createLogger that will inject the meta you specify to every log.

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  defaultMeta: { service: 'user-service' },
  transports: [
    //
    // - Write to all logs with level `info` and below to `combined.log` 
    // - Write all logs error (and below) to `error.log`.
    //
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});
TheNastyOne
  • 965
  • 11
  • 19
22

There's no built-in way to do this, but you can definitely add it yourself - here's how:

First, set up your logger like you normally would. For example:

var logger = new (winston.Logger)({
            "exitOnError" : true,
            "transports" : [
                new (winston.transports.Console)({ "colorize" : true, "level" : "silly", "silent" : false, "handleExceptions" : false }),
            ]
        });

Then override the log() method (this is always called by the level methods - logger.foo() actually calls logger.log('foo').)

logger.log = function(){
  var args = arguments;
  if(args[2]) args[3] = args[2];
  args[2] = {
    "foo" : "bar"
  }
  winston.Logger.prototype.log.apply(this,args);
}

All I'm doing above is making it so when logger.log() is called, it instead calls the above method, which adds the metadata (in this case, an object containing a foo key). Then it calls winston's Logger.log method from the proper context.

The above code would be in a module you create, at the bottom just export the logger:

module.exports = logger;

and import the logger module instead of the winston module in your sub classes.

var logger = require('./logger.js');

Hope that helps!

Jesse
  • 10,370
  • 10
  • 62
  • 81
  • 1
    Note: this will overwrite any metadata you specify in the log statement. You can use underscore's `_.defaults()` to augment it, or you could add a check for args[2] and not overwrite it if it exists to get around this. – Jesse Feb 22 '12 at 18:26
  • 1
    Instead of overwriting the arguments, just append them: logger.log = function(){ var args = arguments; if(args[2]) { args[2].foo = "bar" } winston.Logger.prototype.log.apply(this,args); } – harryBundles Apr 28 '16 at 12:17
  • @Jesse: There is now a supported way to do this, with `rewriter` – DrakaSAN Nov 03 '16 at 14:13
  • 2
    @DrakaSAN winston v3 seems to remove the possibility to mutate the metadata cf https://github.com/winstonjs/winston/issues/1140 – Thierry Abaléa Nov 11 '17 at 18:04
4

Another option with an output more like log4j(s):

There is a documented label property which will add a label to the output (json or line):

var _ = require('lodash');
var winston = require('winston');
var path = require('path');
var baseDir = path.resolve(__dirname, '..');

// SETUP WINSTON LOGGER
var container = new winston.Container();
container.add("exception", {
    console: {
        handleExceptions: true,
        timestamp: true,
        label: "EXCEPTION",
        colorize: true
    }
});
container.get("exception").exitOnError = false;
var keys = [];
    
module.exports = function(filename) {
    var label = path.relative(baseDir, filename);
    if (!_.contains(keys, label)) {
        container.add(label, {
            console: {
                handleExceptions: false,
                level: 'debug',
                timestamp: true,
                label: label,
                colorize: true
            }
        });
        keys.push(label);
    }
    var logger = container.get(label);
    logger.exitOnError = false;
    return logger;
};

And in the other modules require it like this:

var logger = require('./logger')(__filename);

Example output:

2014-07-23T07:05:27.770Z - info: [config/config.js] .......
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
Risadinha
  • 16,058
  • 2
  • 88
  • 91
4

I was looking for a way to add the github delivery ID to all my logs.

I use winston 3 and defaultMeta getters along with express-http-context

// logger.js 
const logger = (filename) => winston.createLogger({
    ...
    defaultMeta: {
        get deliveryId () { return httpContext.get('deliveryId'); },
        get deliveryEvent () { return httpContext.get('deliveryEvent'); },
        module: filename,
    },
    ...
});

module.exports = (fileName) => {
    return logger(fileName);
}

in my other modules:

// foo.js
const logger = require('./logger')(__filename)

module.exports = async(someInput) => {
    logger.debug('hello ' + someInput)
}

https://github.com/winstonjs/winston/issues/1626#issuecomment-531142958

Jason
  • 2,451
  • 2
  • 23
  • 31
1

I've found a better way using util-extend based on what I read on this blog. It will append the data in all cases, which I found useful for catching logger.info vs logger.log("info", message) and won't overwrite your other arguments.

logger.log = function(){
   var args = arguments;
   var level = args[0];

   var newArgs = {
        foo: "bar",
        baz: "abc"
   };
   var originalMeta = args[2] || {};
   args[2] = extend(originalMeta, newArgs);

   winston.Logger.prototype.log.apply(this,args);
};

Will output in the console and in your logs.

harryBundles
  • 150
  • 3
  • 15