【问题标题】:get line number and filename for a log output获取日志输出的行号和文件名
【发布时间】:2017-12-10 16:46:32
【问题描述】:

是否可以获取每个日志输出的行号和文件?

例如:

  var winston = require('winston');

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

应在日志文件中指定“某些消息”来自 myFile.js 第 4 行。

【问题讨论】:

标签: node.js logging winston


【解决方案1】:

您可以将文件名传递为label,您可以从callingModule中获取文件名。

创建logger.js 文件和类似代码

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
            })
        ]
    });
};

现在这里是你的测试文件

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

如果你运行测试文件,输出看起来像

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

【讨论】:

  • 我正在传递模块,但问题是我无法将文件名设为未定义。
  • 你可以在这里查看,stackoverflow.com/questions/53702679/…
  • 还有我们如何使用摩根,在这里。所有的解决方案都显示没有摩根,我们如何使用摩根。我为此提出了一个问题。你对此有什么想法吗stackoverflow.com/questions/53718192/…
  • 使用 path.sep 而不是使用 '/' 来分割路径。 const path = require('path');
  • 我已经在一个带有strapi的大型项目中使用了它......我们最终在一个点之后收到了一个maxEventEmitters警告。这里可能存在内存泄漏。
【解决方案2】:

出于性能问题,温斯顿没有计划这样做。详情请check here

我试过https://github.com/baryon/tracer,但效果不好,例如行号有时不正确。

【讨论】:

    【解决方案3】:

    我在某处找到了这段代码,是的,但它正在工作。在新的winston.js 中使用它,然后在任何文件中都需要它。

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

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

    【讨论】:

    【解决方案4】:

    更新(针对 Winston 3.x)

    我还创建了一个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,
                ),
            })
        ]
    });
    

    原始答案(针对 Winston 2.x)

    我使用的是winston 2.x(但同样的解决方案也适用于winston 3.x)和that's the way我正在记录调用者的文件名和行号:

    重要:注意嵌入代码cmets!

    /**
     * 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;
        }
    };
    

    以及(简化版)格式化函数:

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

    然后声明传输使用格式化程序:

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

    要了解有关 prepareStackTrace 的更多信息,请阅读:https://v8.dev/docs/stack-trace-api#customizing-stack-traces

    【讨论】:

    • 我知道这在 typescript 中不可用,因为它是一个函数
    • @xenoterracide 使其与 TS 一起工作是另一个问题。随意发布一个单独的问题并提供更多信息,以便它可以重现。我对 TS 不太熟练,但如果你标记我,我会看看。
    • 您检查过代码的功能吗?其中有错误。 const boilerplateLines = line =&gt; line &amp;&amp; line.getFileName() &amp;&amp;(line.getFileName().indexOf('&lt;My Module Name&gt;') &amp;&amp; (line.getFileName().indexOf('/node_modules/') &lt; 0);
    • @Alex 可能令人困惑,但这是一个箭头函数。这段代码已经在生产环境中工作了几年 :) 也就是说,如果您认为自己发现了一个错误,请通过查明它到底是什么来帮助您!
    • @NirAlfasi .indexOf('/node_modules/') &lt; 0)); Missing )
    【解决方案5】:

    你可以对字符串进行操作

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

    获取行号和文件路径以及函数名。

    输出看起来像

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-11
      • 2016-03-29
      • 2012-04-21
      • 2016-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多