Skip to content

Instantly share code, notes, and snippets.

@miguelmota
Created May 22, 2014 06:21
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save miguelmota/1868673cc004dfce5a69 to your computer and use it in GitHub Desktop.
Save miguelmota/1868673cc004dfce5a69 to your computer and use it in GitHub Desktop.
Node.js Winston logger wrapper to display filename
var log = require('./lib/logger')(module);
log.info('foo');
var winston = require('winston');
var getLogger = function(module) {
var path = module.filename.split('/').slice(-2).join('/');
return new winston.Logger({
transports: [
new winston.transports.Console({
colorize: true,
level: 'debug',
label: path
}),
new (winston.transports.File)({filename: 'debug.log', silent: false})
]
});
};
module.exports = getLogger;
@joseboretto
Copy link

Thanks!! 👍

@vimox-shah-genea
Copy link

If we require to import getLogger in lot of files then this will create lot of winston logger instance which will create a problem

@Strauteka
Copy link

Problems like this => (node:13236) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 31 unpipe listeners added to [Console]. Use emitter.setMaxListeners() to increase limit

@jonasbadstuebner
Copy link

jonasbadstuebner commented Jun 17, 2022

I think I found a somewhat good solution/middleway to just being able to print informative stuff with winston, without having to manually specify, where the message came from.
Please test it out, following these steps:

  1. Prepare server class in file server.js:
const winston = require('winston');
const Log2gelf = require('winston-log2gelf');
const getCurrentLine = require('get-current-line').default;

// PREPARE CONSTANTS:
// From Log2gelf (index.js) (since I use graylog as transport too):
const WINSTON_LEVELS = {
  error: 0,
  warn: 1,
  info: 2,
  verbose: 3,
  debug: 4,
  silly: 5
};

// SEE: https://github.com/Buzut/winston-log2gelf#protocol-specific-options
const glTransportOpts = {
  level: 'silly',
  name: 'Graylog',
  hostname: process.env.MY_IP, // custom to my setup
  host: process.env.GL_HOST,
  port: 12201,
  protocol: 'tcp', // only with Log2gelf (winston-log2gelf)
  silent: false,
  handleExceptions: true,
  exitOnError: false,
  reconnect: -1,
  wait: 2000,
  service: process.env.API_CONTAINER || 'node-api',
  release: process.env.MAX_VERSION || '0.0.0',
  environment: process.env.ENV || 'development',
};
const glTransport = new Log2gelf(glTransportOpts);

class SERVER {
  constructor() {
    // SETUP LOGGING
    this._logger = winston.createLogger({
      levels: WINSTON_LEVELS,
      exitOnError: false,
      transports: [
        new winston.transports.Console({
          format: winston.format.json(),
          level: 'silly',
        }),
        ...(process.env.GL_HOST ? glTransport : []),
      ],
    });

    this.logger = {}
    this.logger.prepareLog = function () {
      let newArguments = arguments;
      // newArguments: { "0": <message>, ("1": <details>) }
      let msg_location = getCurrentLine({ frames: 3 });
      if (newArguments["1"]) {
        newArguments["1"].msg_location = msg_location;
      } else {
        // https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules_typedoc_node_modules_typescript_lib_lib_es5_d_.iarguments.html
        newArguments["1"] = { "msg_location": msg_location };
        newArguments.length = 2;
      }
      return newArguments;
    };
    Object.keys(WINSTON_LEVELS).forEach(level => {
      this.logger[level] = function () { this._logger[level](...this.logger.prepareLog(...arguments)) }.bind(this)
    });
  }
}

this.server = new SERVER();
// Used like this (from basically anywhere, where this.server is an instance of class SERVER):
this.server.logger.info("Test-Message");
// SHOULD PRINT:
/*
{
  "level": "info",
  "message": "Test-Message",
  "msg_location": {
    "char": 20,
    "file": "[redacted]/test.js",
    "line": 73,
    "method": "Object.<anonymous>"
  }
}
*/
this.server.logger.info("Test-Message", { "withadditionalinfo": "Here is more info about the message" });
// SHOULD PRINT:
/*
{
  "level": "info",
  "message": "Test-Message",
  "msg_location": {
    "char": 20,
    "file": "[redacted]/test.js",
    "line": 87,
    "method": "Object.<anonymous>"
  },
  "withadditionalinfo": "Here is more info about the message"
}
*/
  1. In a different file (e.g. utils/Test.js):
class Test {
  constructor(server) {
    this.server = server;
  }

  testlog(){
    this.server.logger.error("Testerror...can you find me? :)")
  }
}

module.exports = Test;
  1. add test to server.js:
// [...] (see above)
const Test = require('./utils/Test.js')

let testinstance = new Test(this.server);
testinstance.testlog();
// SHOULD PRINT:
/*
{
  "level": "error",
  "message": "Testerror...can you find me? :)",
  "msg_location": {
    "char": 24,
    "file": "[redacted]/utils/Test.js",
    "line": 7,
    "method": "Test.testlog"
  }
}
*/
  1. run node server.js to see the result. Please let me know, if there is anything off.

@jonasbadstuebner
Copy link

You can also omit printing the char (for me at least it does not contain information I would want or need in my graylog-logs) with an instant anonymous function call and javacsript object deconstruction:

// [...]
    newArguments[1].msg_location = (({ method, file, line }) => ({ method, file, line }))(getCurrentLine({ frames: 3 }));
// [...]

But this is of course up to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment