'get line number and filename for a log output

Is it possible to get the line number and file for each log output ?

For example:

  var winston = require('winston');

  winston.log('info', 'some message!'); // this is at line 4 of myfile.js

should specify in log file that 'some message' came from myFile.js line 4.



Solution 1:[1]

You can pass the file name as label and you can get the file name from callingModule.

Create logger.js file and code like

var winston = require('winston');
var getLabel = function (callingModule) {
    var parts = callingModule.filename.split('/');
    return parts[parts.length - 2] + '/' + parts.pop();
};

module.exports = function (callingModule) {
    return new winston.Logger({
        transports: [
            new winston.transports.Console({
                label: getLabel(callingModule),
                json: false,
                timestamp: true,
                depth:true,
                colorize:true
            })
        ]
    });
};

Now Here your test file

var logger = require('./logger')(module);
function test() {
    logger.info('test logger');
}
test();

and if you run test file than the output looks like

2017-07-08T07:15:20.671Z - info: [utils/test.js] test logger

Solution 2:[2]

winston didn't have the plan to do that due to performance concerns. Please check here for detailed information.

I tried https://github.com/baryon/tracer but it isn't good, e.g. line number is incorrect from time to time.

Solution 3:[3]

Update (for Winston 3.x)

I also created a gist for the following code:

const { format } = require('winston');
const { combine, colorize, timestamp, printf } = format;

/**
 * /**
 * Use CallSite to extract filename and number, for more info read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
 * @param numberOfLinesToFetch - optional, when we want more than one line back from the stacktrace
 * @returns {string|null} filename and line number separated by a colon, if numberOfLinesToFetch > 1 we'll return a string
 * that represents multiple CallSites (representing the latest calls in the stacktrace)
 *
 */
const getFileNameAndLineNumber = function getFileNameAndLineNumber (numberOfLinesToFetch = 1) {
    const oldStackTrace = Error.prepareStackTrace;

    const boilerplateLines = line => line &&
        line.getFileName() &&
        (line.getFileName().indexOf('<My Module Name>') &&
        (line.getFileName().indexOf('/node_modules/') < 0));

    try {
        // eslint-disable-next-line handle-callback-err
        Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
        Error.captureStackTrace(this);
        // we need to "peel" the first CallSites (frames) in order to get to the caller we're looking for
        // in our case we're removing frames that come from logger module or from winston
        const callSites = this.stack.filter(boilerplateLines);
        if (callSites.length === 0) {
            // bail gracefully: even though we shouldn't get here, we don't want to crash for a log print!
            return null;
        }
        const results = [];
        for (let i = 0; i < numberOfLinesToFetch; i++) {
            const callSite = callSites[i];
            let fileName = callSite.getFileName();
            fileName = fileName.includes(BASE_DIR_NAME) ? fileName.substring(BASE_DIR_NAME.length + 1) : fileName;
            results.push(fileName + ':' + callSite.getLineNumber());
        }
        return results.join('\n');
    } finally {
        Error.prepareStackTrace = oldStackTrace;
    }
};

function humanReadableFormatter ({ level, message, ...metadata }) {
    const filename = getFileNameAndLineNumber();
    return `[${level}] [${filename}] ${message} ${JSON.stringify(metadata)}`;
}    

const logger = winston.createLogger({
  transports: [
      new winston.transports.Console({
            level: 'info',
            handleExceptions: true,
            humanReadableUnhandledException: true,
            json: false,
            colorize: { all: true },
            stderrLevels: ['error', 'alert', 'critical', 'bizAlert'],
            format: combine(
                colorize(),
                timestamp(),
                humanReadableFormatter,
            ),
        })
    ]
});

Original Answer (for Winston 2.x)

I'm using winston 2.x (but the same solution will work for winston 3.x) and that's the way I'm logging the filename and linenumber of the caller:

IMPORTANT: pay attention to the embedded code comments!

/**
 * Use CallSite to extract filename and number
 * @returns {string} filename and line number separated by a colon
 */
const getFileNameAndLineNumber = () => {
    const oldStackTrace = Error.prepareStackTrace;
    try {
        // eslint-disable-next-line handle-callback-err
        Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
        Error.captureStackTrace(this);
        // in this example I needed to "peel" the first 10 CallSites in order to get to the caller we're looking for, hence the magic number 11
        // in your code, the number of stacks depends on the levels of abstractions you're using, which mainly depends on winston version!
        // so I advise you to put a breakpoint here and see if you need to adjust the number!
        return this.stack[11].getFileName() + ':' + this.stack[11].getLineNumber();
    } finally {
        Error.prepareStackTrace = oldStackTrace;
    }
};

And (a simplified version of) the formatter function:

function humanReadableFormatter ({level, message}) {
    const filename = getFileNameAndLineNumber();
    return `[${level}] ${filename} ${message}`;
}

Then declare the transport to use the formatter:

new winston.transports.Console({
        level: 'info',
        handleExceptions: true,
        humanReadableUnhandledException: true,
        json: false,
        colorize: 'level',
        stderrLevels: ['warn', 'error', 'alert'],
        formatter: humanReadableFormatter,
    })

To read more about prepareStackTrace read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces

Solution 4:[4]

I found this code somewhere, yeah but It is working. Use it in a new winston.js and then requires that in any file.

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')
    }
  }
}

Source: https://gist.github.com/transitive-bullshit/39a7edc77c422cbf8a18

Solution 5:[5]

You can do string operations on

console.log(new Error().stack.split('\n')[1].slice(7));

to get line Number and file path too along with function name.

The output would look like

AutomationFramework._this.parsingLogic (/Users/user_name/Desktop/something/some-file.js:49:25)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nitin
Solution 2 Qiulang
Solution 3
Solution 4 M--
Solution 5 Hitesh Salavi