26

For every logger statement with any level, I need to display the file name from where the log statement executed, below is the illustration I given below:

Example : Below is the line executed from JobWork.js

logger.info("getInCompleteJobs in job works");

Actual :

2012-11-05T06:07:19.158Z - info: getInCompleteJobs in job works

Required :

2012-11-05T06:07:19.158Z - info JobWork.js : getInCompleteJobs in job works

Without passing the fileName as a parameter from the log statement it should give the filename.

Dexter
  • 12,172
  • 9
  • 27
  • 30

5 Answers5

47

Looks like you're using Winston here - I typically pass module into my logger module and then set Winston's label property to a parsed version of module.filename. Something like:

logger.js:

const path = require('path');

// Return the last folder name in the path and the calling
// module's filename.
const getLabel = function(callingModule) {
  const parts = callingModule.filename.split(path.sep);
  return path.join(parts[parts.length - 2], parts.pop());
};

module.exports = function (callingModule) {
  return new winston.Logger({
    transports: [new winston.transports.Console({
      label: getLabel(callingModule)
    })]
  });
};

Usage (assume module is controllers/users.js):

const logger = require('./logger')(module);
logger.info('foo');

Result:

2014-11-25T15:31:12.186Z - info: [controllers/users.js] foo
ehynds
  • 4,435
  • 3
  • 24
  • 23
  • Nicely done! Note: I couldn't get winston to treat `label` as a function instead of a string, so I ended up determining the label before `return new winston.Logger...`. More efficient anyways. – lambinator Jan 07 '15 at 21:44
  • Thanks, I extracted the code from my project incorrectly. Fixed now. – ehynds Mar 05 '15 at 12:43
  • Woah, so simple with 'module' as parameter. Thx :-) – synthomat Nov 05 '15 at 12:43
  • 3
    this is cool out of all the other ones I tried, but how do you get the line number? – Rjk May 10 '17 at 06:39
  • Thanks to the author! Just one thing, for the others reading trying this good technique... at the line where the `var parts` is declared, in case you are running at windows, use instead `var parts = callingModule.filename.split('\\');`, otherwise we will end up with an `undefined`. It is up to you, how you are going to make the code portable (Windows and Unix like S.O.), share with us the good ideas. :-) – Ualter Jr. Oct 12 '17 at 14:57
  • 2
    Related to the comment from @UalterJr. - you can use Node's [path.sep](https://nodejs.org/api/path.html#path_path_sep) for portable code: `const path = require('path'); ... var parts = callingModule.filename.split(path.sep)` – Peter W Sep 12 '18 at 08:09
  • getting calling module as undefined , any one knows whats the issue ? – Learner Dec 10 '18 at 06:57
  • Nice one! this is also working on Winston 3 with some code changes. – NVO Aug 27 '19 at 14:27
  • @ehynds , Is there any way to print he name of the calling function too ? – stackUser67 Dec 30 '19 at 13:02
18

You can use the stack trace information attached to v8's Error object to find out what file/line your code was called from. This approach works well, but it does not perform well; so you will want to disable it in production.

So you could do something like this:

  var logger_info_old = logger.info;
  logger.info = function(msg) {
    var fileAndLine = traceCaller(1);
    return logger_info_old.call(this, fileAndLine + ":" + msg);
  }

  /**
  * examines the call stack and returns a string indicating 
  * the file and line number of the n'th previous ancestor call.
  * this works in chrome, and should work in nodejs as well.  
  *
  * @param n : int (default: n=1) - the number of calls to trace up the
  *   stack from the current call.  `n=0` gives you your current file/line.
  *  `n=1` gives the file/line that called you.
  */
  function traceCaller(n) {
    if( isNaN(n) || n<0) n=1;
    n+=1;
    var s = (new Error()).stack
      , a=s.indexOf('\n',5);
    while(n--) {
      a=s.indexOf('\n',a+1);
      if( a<0 ) { a=s.lastIndexOf('\n',s.length); break;}
    }
    b=s.indexOf('\n',a+1); if( b<0 ) b=s.length;
    a=Math.max(s.lastIndexOf(' ',b), s.lastIndexOf('/',b));
    b=s.lastIndexOf(':',b);
    s=s.substring(a+1,b);
    return s;
  }
Lee
  • 13,462
  • 1
  • 32
  • 45
  • thanks for the comment, Can you please look over this issue https://github.com/flatiron/winston/issues/197 – Dexter Nov 16 '12 at 06:36
  • 1
    ok, what specifically, did you want me to do with regards to that github issue? mmalecki@github is correct: it's a huge performance hit. That's probably ok for development, but not ok for production. And certainly not ok for inclusion in winston itself. If you want this functionality in your program, you could override the log function (as I've shown above), or you could get your own copy of winston, and make a development-only version that has the necessary modifications. – Lee Nov 16 '12 at 06:47
  • 3
    And why is `b` global? – Petah May 31 '15 at 00:37
4

Assuming each file is a separate node process, you could use something like process.argv[1].match(/[\w-]+\.js/gi)[0]

If you are looking for something that will work in modules this might work:

process.mainModule.filename.match(/[\w-]+\.js/gi)[0]
Sdedelbrock
  • 5,192
  • 1
  • 18
  • 13
  • Thanks for the comment, it is only used to provide the same file name everytime i.e the filename from where started execution – Dexter Nov 16 '12 at 06:17
  • in that case `process.mainModule.filename.match(/[\w-]+\.js/gi)[0]` should work for you! (it works every in root) You can use it as `logger.info(process.mainModule.filename.match(/[\w-]+\.js/gi)[0] + "getInCompleteJobs in job works");` or get fancy and write it directly into the logger. – Sdedelbrock Nov 16 '12 at 06:26
1

Use this code and it will give you the log with the filename and line number. Paste this code in a new file winston.js and this requires this file to use it.

var winston = require('winston')
var path = require('path')
var PROJECT_ROOT = path.join(__dirname, '..')
var appRoot = require('app-root-path');


const options = {
  file: {
    level: 'info',
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    json: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    colorize: false,
    timestamp: true
  },
  console: {
    level: 'debug',
    handleExceptions: true,
    json: true,
    colorize: true,
    timestamp: true
  }
};

var logger = new winston.Logger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console)
  ],
  exitOnError: false // do not exit on handled exceptions
});

logger.stream = {
  write: function (message) {
    logger.info(message)
  }
}

// A custom logger interface that wraps winston, making it easy to instrument
// code and still possible to replace winston in the future.

module.exports.debug = module.exports.log = function () {
  logger.debug.apply(logger, formatLogArguments(arguments))
}

module.exports.info = function () {
  logger.info.apply(logger, formatLogArguments(arguments))
}

module.exports.warn = function () {
  logger.warn.apply(logger, formatLogArguments(arguments))
}

module.exports.error = function () {
  logger.error.apply(logger, formatLogArguments(arguments))
}

module.exports.stream = logger.stream

/**
 * Attempts to add file and line number info to the given log arguments.
 */
function formatLogArguments (args) {
  args = Array.prototype.slice.call(args)

  var stackInfo = getStackInfo(1)

  if (stackInfo) {
    // get file path relative to project root
    var calleeStr = '(' + stackInfo.relativePath + ':' + stackInfo.line + ')'

    if (typeof (args[0]) === 'string') {
      args[0] = calleeStr + ' ' + args[0]
    } else {
      args.unshift(calleeStr)
    }
  }

  return args
}

/**
 * Parses and returns info about the call stack at the given index.
 */
function getStackInfo (stackIndex) {
  // get call stack, and analyze it
  // get all file, method, and line numbers
  var stacklist = (new Error()).stack.split('\n').slice(3)

  // stack trace format:
  // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
  // do not remove the regex expresses to outside of this method (due to a BUG in node.js)
  var stackReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi
  var stackReg2 = /at\s+()(.*):(\d*):(\d*)/gi

  var s = stacklist[stackIndex] || stacklist[0]
  var sp = stackReg.exec(s) || stackReg2.exec(s)

  if (sp && sp.length === 5) {
    return {
      method: sp[1],
      relativePath: path.relative(PROJECT_ROOT, sp[2]),
      line: sp[3],
      pos: sp[4],
      file: path.basename(sp[2]),
      stack: stacklist.join('\n')
    }
  }
}
Rupesh
  • 850
  • 2
  • 13
  • 30
  • else if (args[0].constructor === Object) { args[0] = calleeStr + ' ' + JSON.stringify(args[0], null, 4); } This additional else block will work for logging object as well. – Mitendra Aug 27 '20 at 10:21
1

solutions with "consola" package (ES module only):

import { createLogger } from '@nitra/consola'
consola = createLogger(import.meta.url)

consola.debug('TEST')

output:

[my-file.js] › TEST
vitaliytv
  • 694
  • 7
  • 9