获取日志输出的行号和文件名

Posted

技术标签:

【中文标题】获取日志输出的行号和文件名【英文标题】: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 行。

【问题讨论】:

这个问题与您希望完成的问题非常相似,除非您希望使用winston 明确记录。 ***.com/questions/14172455/… 【参考方案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

【讨论】:

我正在传递模块,但问题是我无法将文件名设为未定义。 你可以在这里查看,***.com/questions/53702679/… 还有我们如何使用摩根,在这里。所有的解决方案都显示没有摩根,我们如何使用摩根。我为此提出了一个问题。你对此有什么想法吗***.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

【讨论】:

“某处”应该通过添加答案链接来获得信任! 我没有参考,否则我会添加:) 我怀疑是从这里复制过来的: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)

【讨论】:

以上是关于获取日志输出的行号和文件名的主要内容,如果未能解决你的问题,请参考以下文章

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印内容带有文件文件名方法名行号 等信息,方便定位日志输出的地方

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印内容带有文件文件名方法名行号 等信息,方便定位日志输出的地方

Logback日志输出问号的问题解决方案

将文件名和行号添加到 Monolog 输出

java: 自定义java.util.logging.Logger的日志输出格式,输出IDE(ECLIPSE)能自动识别行号的格式

logrus中输出文件名行号及函数名