73

I have several modules - let's say server.js, module1.js,...,moduleN.js.

I would like define the log file in my server.js:

winston.add(winston.transports.File, { filename: 'mylogfile.log' });

and then use it in all my modules.

What is the best way to do that? I could exports.winston=winston; in each module and then set it in the server.js, but is there any better solution?

Thank you in advance!

Alexander
  • 7,484
  • 4
  • 51
  • 65
  • 2
    The short answer is: Node.js modules behave like Singletons (except some "weird" cases https://medium.com/@lazlojuly/are-node-js-modules-singletons-764ae97519af). So **'winston' can be required and configured in the entry-point file (e.g. server.js) and then just required and used in any other module.js (it will be the same instance of the logger)**. – Alexander Aug 29 '17 at 11:14

11 Answers11

112

The default logger concept handles this nicely.

Winston defines a default logger that any straight require (and subsequent require) to winston will retrieve. Thus you simply configure this default logger once, and it's available for subsequent module use via vanilla require('winston') in its glorious tweaked multi-transport mode.

e.g. here is my complete logging setup that defines 3 transports. I swap Loggly for MongoDB sometimes.

server.js

var logger=require('./log.js'); 
// requires winston and configures transports for winstons default logger- see code below.

all other .js files

var logger=require('winston'); // this retrieves default logger which was configured in log.js
logger.info("the default logger with my tricked out transports is rockin this module");

log.js - this is a one time configuration of the DEFAULT logger

var logger = require('winston');
var Loggly = require('winston-loggly').Loggly;
var loggly_options={ subdomain: "mysubdomain", inputToken: "efake000-000d-000e-a000-xfakee000a00" }
logger.add(Loggly, loggly_options);
logger.add(winston.transports.File, { filename: "../logs/production.log" });
logger.info('Chill Winston, the logs are being captured 3 ways- console, file, and Loggly');
module.exports=logger;

Alternatively for more complex scenarios you can use winston containers and retrieve the logger from a named container in other modules. I haven't used this.

My only issue with this was a missing logs directories on my deployment host which was easily fixed.

starball
  • 20,030
  • 7
  • 43
  • 238
Nick
  • 1,451
  • 1
  • 11
  • 6
  • 4
    This does not seem to work if I want to modify the Console transport: var logger = new (winston.Logger)({ transports: [ new (winston.transports.Console)({ timestamp: true, level: 'verbose', colorize: true }) ] }); module.exports = logger; Then if I use require('winston') in another file my Console logger is not correct. – lostintranslation May 16 '14 at 14:19
  • How do you configure the `logger.handleExceptions()` in the `log.js` file to log unhandled exceptions from all files? When I set it up to `logger..handleExceptions(new winston.transports.Console({ colorize: true, json: true }));` the unhandled exception logging works only in my `server.js` file. If the error is thrown in another file it is not logged. – Ivan Stoyanov Jul 15 '15 at 13:04
  • this is so helpful, should be accepted as answer imho – Daniel Mar 01 '16 at 17:50
  • 5
    Not working in other files. It resets to default configuration of winston. – udgeet patel May 27 '16 at 11:47
  • 10
    A quick tip for those who, same as I, didn't get it working in other modules. **do not** create a `new winston.Logger`, configure the actual `logger` var that you included in the first place. return it and it will not only be available in the module you called it but in any other modules where you required winston. – Oleg Shemetov Jul 17 '16 at 12:34
  • It should be noted that the log.js file is incomplete (line 5, the variable 'winston' is referenced but never defined). Also, if you're logging to the console, this approach won't work, you have to remove it first like in [these](https://github.com/winstonjs/winston/issues/802) [answers](https://stackoverflow.com/a/23714002/1827734) – BrDaHa Sep 29 '17 at 21:46
  • This answer is totally antipattern and should not be used – zoran Dec 15 '20 at 17:31
13

What I do ( which may not be the best way ) is use a 'global' module where I export all the stuff that I use through my applications. For instance:

//Define your winston instance
winston.add(winston.transports.File, { filename: 'mylogfile.log' });
exports.logger = winston;

exports.otherGlobals = ....

Now just require this globally used module from your other modules

var Global = require(/path/to/global.js);

Because the file is cached after the first time it is loaded (which you can verify by including a log statement in your global; it will only log once), there's very little overhead in including it again. Putting it all into one file is also easier than requiring ALL your globally used modules on every page.

Nick Mitchinson
  • 5,452
  • 1
  • 25
  • 31
12

I wanted to use custom colours and levels.

So I removed the default console-transport and set a colorized one

here is my logger.js

var logger = require('winston');

logger.setLevels({
    debug:0,
    info: 1,
    silly:2,
    warn: 3,
    error:4,
});
logger.addColors({
    debug: 'green',
    info:  'cyan',
    silly: 'magenta',
    warn:  'yellow',
    error: 'red'
});

logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, { level: 'debug', colorize:true });

module.exports = logger;



Loading from app.js:

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



Loading from other modules:

 var logger = require('winston');        
user3575777
  • 251
  • 4
  • 3
  • 1
    I prefer Nick's answer, because I saw you required 'winston' and assumed that your answer was incorrect (I know now that it is not because Nick explained why it works). – Jacob McKay Feb 26 '15 at 22:44
  • winston.setLevels is now depricated in 3.0 https://github.com/winstonjs/winston-syslog/issues/87 – platinums Apr 01 '19 at 12:25
7

Slightly off topic (as the OP asks about Winston), but I like the 'child-logger' approach by Bunyan:

var bunyan = require('bunyan');
var log = bunyan.createLogger({name: 'myapp'});

app.use(function(req, res, next) {
  req.log = log.child({reqId: uuid()});
  next();
});

app.get('/', function(req, res) {
  req.log.info({user: ...});
});

It solves the OP's problem as the logger is available through the req object (hence no need for 'require(log)' in each module). Additionally, all log entries belonging to a particular request will have a unique ID that connects them together.

{"name":"myapp","hostname":"pwony-2","pid":14837,"level":30,"reqId":"XXXX-XX-XXXX","user":"...@gmail.com","time":"2014-05-26T18:27:43.530Z","v":0}

I'm not sure if Winston supports this as well.

chrisvdb
  • 2,080
  • 2
  • 20
  • 28
6

I am working on Winston 3.0.0 right now. And it seems the way to configure the default logger has changed a little bit. The way that works for me is folloing:

log.js// the setting for global logger

const winston= require('winston');

winston.configure({
  level:"debug",
  format: winston.format.combine(
    winston.format.colorize(),
    winston.format.simple()
  ),
  transports: [
    new winston.transports.Console()
  ]
});

The other part is the same. In the beginning of you application, require('log.js'), and also require ('winston'), While in all other files, simply require('winston')

.

Haijin
  • 2,561
  • 2
  • 17
  • 30
5

I'm creating a new Winston logger.

log.js

'use strict';

const winston = require('winston');

module.exports = new(winston.Logger)({
    transports: [
        new(winston.transports.Console)({
            level: 'info'
        })
    ]
});

a.js

const log = require('./log');

log.info("from a.js");

b.js

const log = require('./log');

log.info("from b.js");
Kevin Struillou
  • 856
  • 10
  • 19
3

Here is my logger configuration with winston version is 3.2.1.

It storing logs in application.log file and for error stack trace I am using errors({ stack: true }) and small trick in printf function to print stack trace in error case.

Configuration

const {format, transports} = require('winston');
const { timestamp, colorize, printf, errors } = format;
const { Console, File } = transports;
LoggerConfig = {
        level: process.env.LOGGER_LEVEL || 'debug',
        transports: [
            new Console(),
            new File({filename: 'application.log'})
        ],
        format: format.combine(
            errors({ stack: true }),
            timestamp(),
            colorize(),
            printf(({ level, message, timestamp, stack }) => {
                if (stack) {
                    // print log trace 
                    return `${timestamp} ${level}: ${message} - ${stack}`;
                }
                return `${timestamp} ${level}: ${message}`;
            }),
        ),
        expressFormat: true, // Use the default Express/morgan request formatting
        colorize: false, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
        ignoreRoute: function (req, res) {
            return false;
        } // optional: allows to skip some log messages based on request and/or response
}

Declare

I am using this same configuration in express-winston and for general log also. I declared __logger object globally so that you don't need to import every time in every file. Generally in node js all the global variable prefix with 2 time underscore(__) so it will be good to follow this.

Server.js

const winston = require('winston');
const expressWinston = require('express-winston');

/**
 * winston.Logger
 * logger for specified log message like console.log
 */
global.__logger = winston.createLogger(LoggerConfig);
/**
 * logger for every HTTP request comes to app
 */
app.use(expressWinston.logger(LoggerConfig));

Use

__logger is global so you can use it any place, for example:

blog.controller.js

function save(req, res) {
  try {
    __logger.debug('Blog add operation');
    .
    .
    return res.send(blog);
  } catch (error) {
    __logger.error(error);
    return res.status(500).send(error);
  }
}

Hope this will help !

Kiran Mali
  • 597
  • 4
  • 12
2

I use a factory function and pass in the module name so it can be added to the meta data:

logger-factory.js

const path = require('path');
const { createLogger, format, transports } = require('winston');
const { combine, errors, timestamp } = format;

const baseFormat = combine(
  timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
  errors({ stack: true }),
  format((info) => {
    info.level = info.level.toUpperCase();
    return info;
  })(),
);

const splunkFormat = combine(
  baseFormat,
  format.json(),
);

const prettyFormat = combine(
  baseFormat,
  format.prettyPrint(),
);

const createCustomLogger = (moduleName) => createLogger({
  level: process.env.LOG_LEVEL,
  format: process.env.PRETTY_LOGS ? prettyFormat : splunkFormat,
  defaultMeta: { module: path.basename(moduleName) },
  transports: [
    new transports.Console(),
  ],
});

module.exports = createCustomLogger;

app-harness.js (so I can run the exported index module)

const index = require('./index');

// https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html
const sampleEvent = {
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-2",
      "eventTime": "2019-09-03T19:37:27.192Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "AWS:AIDAINPONIXQXHT3IKHL2"
      },
      "requestParameters": {
        "sourceIPAddress": "205.255.255.255"
      },
      "responseElements": {
        "x-amz-request-id": "D82B88E5F771F645",
        "x-amz-id-2": "vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo="
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "828aa6fc-f7b5-4305-8584-487c791949c1",
        "bucket": {
          "name": "lambda-artifacts-deafc19498e3f2df",
          "ownerIdentity": {
            "principalId": "A3I5XTEXAMAI3E"
          },
          "arn": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df"
        },
        "object": {
          "key": "b21b84d653bb07b05b1e6b33684dc11b",
          "size": 1305107,
          "eTag": "b21b84d653bb07b05b1e6b33684dc11b",
          "sequencer": "0C0F6F405D6ED209E1"
        }
      }
    }
  ]
};

index.handler(sampleEvent)
  .then(() => console.log('SUCCESS'))
  .catch((_) => console.log('FAILURE'));

index.js

const logger = require('./logger-factory')(__filename);
const app = require('./app');

exports.handler = async function (event) {
  try {
    logger.debug('lambda triggered with event', { event });
    await app.run(event);
    logger.debug(`lambda finished`);
  } catch(error) {
    logger.error('lambda failed: ', error);
    // rethrow the error up to AWS
    throw error;
  }
}

app.js

const logger = require('./logger-factory')(__filename);

const run = async (event) => {
  logger.info('processing S3 event', event);
  try {
    logger.info('reading s3 file')
    // throws because I used "Record" instead of "Records"
    const s3 = event.Record[0].s3;
    // use s3 to read the file
  } catch (error) {
    logger.error('failed to read from S3: ', error);
    throw error;
  }
};

module.exports = { run };

when I run the application locally at WARN level:

~/repos/ghe/lambda-logging (master * u=)> LOG_LEVEL=warn node -r dotenv/config ./src/app-harness.js
{
  module: 'app.js',
  level: 'ERROR',
  message: "failed to read from S3: Cannot read property '0' of undefined",
  stack: "TypeError: Cannot read property '0' of undefined\n" +
    '    at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
    '    at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
    '    at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
    '    at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
    '    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
    '    at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
    '    at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
    '    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
    '    at internal/main/run_main_module.js:18:47',
  timestamp: '2020-05-11 17:34:06'
}
{
  module: 'index.js',
  level: 'ERROR',
  message: "lambda failed: Cannot read property '0' of undefined",
  stack: "TypeError: Cannot read property '0' of undefined\n" +
    '    at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
    '    at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
    '    at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
    '    at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
    '    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
    '    at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
    '    at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
    '    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
    '    at internal/main/run_main_module.js:18:47',
  timestamp: '2020-05-11 17:34:06'
}

when I run at DEBUG level:

~/repos/ghe/lambda-logging (master * u=)> LOG_LEVEL=debug node -r dotenv/config ./src/test-harness.js 
{
  module: 'index.js',
  event: {
    Records: [
      {
        eventVersion: '2.1',
        eventSource: 'aws:s3',
        awsRegion: 'us-east-2',
        eventTime: '2019-09-03T19:37:27.192Z',
        eventName: 'ObjectCreated:Put',
        userIdentity: { principalId: 'AWS:AIDAINPONIXQXHT3IKHL2' },
        requestParameters: { sourceIPAddress: '205.255.255.255' },
        responseElements: {
          'x-amz-request-id': 'D82B88E5F771F645',
          'x-amz-id-2': 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
        },
        s3: {
          s3SchemaVersion: '1.0',
          configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
          bucket: {
            name: 'lambda-artifacts-deafc19498e3f2df',
            ownerIdentity: { principalId: 'A3I5XTEXAMAI3E' },
            arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
          },
          object: {
            key: 'b21b84d653bb07b05b1e6b33684dc11b',
            size: 1305107,
            eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
            sequencer: '0C0F6F405D6ED209E1'
          }
        }
      }
    ]
  },
  level: 'DEBUG',
  message: 'lambda triggered with event',
  timestamp: '2020-05-11 17:38:21'
}
{
  module: 'app.js',
  Records: [
    {
      eventVersion: '2.1',
      eventSource: 'aws:s3',
      awsRegion: 'us-east-2',
      eventTime: '2019-09-03T19:37:27.192Z',
      eventName: 'ObjectCreated:Put',
      userIdentity: { principalId: 'AWS:AIDAINPONIXQXHT3IKHL2' },
      requestParameters: { sourceIPAddress: '205.255.255.255' },
      responseElements: {
        'x-amz-request-id': 'D82B88E5F771F645',
        'x-amz-id-2': 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
      },
      s3: {
        s3SchemaVersion: '1.0',
        configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
        bucket: {
          name: 'lambda-artifacts-deafc19498e3f2df',
          ownerIdentity: { principalId: 'A3I5XTEXAMAI3E' },
          arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
        },
        object: {
          key: 'b21b84d653bb07b05b1e6b33684dc11b',
          size: 1305107,
          eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
          sequencer: '0C0F6F405D6ED209E1'
        }
      }
    }
  ],
  level: 'INFO',
  message: 'processing S3 event',
  timestamp: '2020-05-11 17:38:21'
}
{
  message: 'reading s3 file',
  level: 'INFO',
  module: 'app.js',
  timestamp: '2020-05-11 17:38:21'
}
{
  module: 'app.js',
  level: 'ERROR',
  message: "failed to read from S3: Cannot read property '0' of undefined",
  stack: "TypeError: Cannot read property '0' of undefined\n" +
    '    at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
    '    at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
    '    at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
    '    at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
    '    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
    '    at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
    '    at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
    '    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
    '    at internal/main/run_main_module.js:18:47',
  timestamp: '2020-05-11 17:38:21'
}
{
  module: 'index.js',
  level: 'ERROR',
  message: "lambda failed: Cannot read property '0' of undefined",
  stack: "TypeError: Cannot read property '0' of undefined\n" +
    '    at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
    '    at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
    '    at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
    '    at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
    '    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
    '    at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
    '    at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
    '    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
    '    at internal/main/run_main_module.js:18:47',
  timestamp: '2020-05-11 17:38:21'
}
Jason
  • 2,451
  • 2
  • 23
  • 31
1

if you want to make the logger a global variable- you have to do specifically by assign it to the global variable like so

logger.js

var winston = require('winston')

var winston = winston.createLogger({
transports: [
  new (winston.transports.Console)(),
  new (winston.transports.File)({
    filename: './logs/logger.log'
  })
]
});
module.exports=winston;

app.js

let logger  = require('./logger')
global.__logger = logger

someController.js

__logger.info('created log successfully')

Note: it's good practice to assign a prefix for every global variable so you will know that is a global one. i'm using __ as prefix (double low dash)

Veeresh Honnaraddi
  • 1,011
  • 1
  • 9
  • 20
1

Just create logger.js and put

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.colorize(),
    winston.format.simple()
  ),
  transports: [
    new winston.transports.Console()
  ]
});

module.exports = logger

Then you can require and use it anywhere, since logger is now singleton.

const logger = require('./utils/logger');
logger.info('Hello!');

This even gives you an option to swap logging library if needed. The accepted answer is totally wrong and one step closer to spaghetti code.

zoran
  • 943
  • 11
  • 22
0

In my team we have created a private npm package with all default configs (as you've shown in previous answers)

I've just one question: would it be a good practice to declare the logger object as a global in order to avoid import in each and every module?

Carmine Ingaldi
  • 856
  • 9
  • 23
  • I wouldn't recommend that. What about other packages you will use in multiple modules (e.g. the db package, http package etc) you want them all to make global in order to not require them in all the relevant modules? That's wrong. If you ask this question only because in your case you should mention the relative path - then just publish your wrapper as a package and require it without the path (just like you do for winston) or use this package `module-alias` which will allow you to give names to paths and then just require it always same way, e.g. `const myWinston = require('@lib/my-winston')` – Alexander Feb 12 '20 at 20:28
  • @Alexander sure, this is what I've done, I understand your point but I had the doubt because there is a difference between logger object and "infrastructure" modules: the logger is needed literally everywhere (or I hope so, since we promote the importance of code instrumentation) – Carmine Ingaldi Feb 13 '20 at 07:41