如何获取Nodejs中显示的console.log行号?

p3rjfoxz  于 2023-01-16  发布在  Node.js
关注(0)|答案(8)|浏览(174)

得到了一个旧的应用程序,打印出相当多的消息使用console.log,但我就是找不到在哪些文件和行console.log是调用。
有没有办法连接到应用程序并显示文件名和行号?

xmq68pz9

xmq68pz91#

对每个调用进行全栈跟踪有点麻烦,我刚刚改进了@noppa的解决方案,只打印发起者:

['log', 'warn', 'error'].forEach((methodName) => {
  const originalMethod = console[methodName];
  console[methodName] = (...args) => {
    let initiator = 'unknown place';
    try {
      throw new Error();
    } catch (e) {
      if (typeof e.stack === 'string') {
        let isFirst = true;
        for (const line of e.stack.split('\n')) {
          const matches = line.match(/^\s+at\s+(.*)/);
          if (matches) {
            if (!isFirst) { // first line - current function
                            // second line - caller (what we are looking for)
              initiator = matches[1];
              break;
            }
            isFirst = false;
          }
        }
      }
    }
    originalMethod.apply(console, [...args, '\n', `  at ${initiator}`]);
  };
});

它还修补了其他方法(对Nodejs很有用,因为warnerror不像Chrome那样带有堆栈跟踪)。
因此,您的控制台将类似于:

Loading settings.json
   at fs.readdirSync.filter.forEach (.../settings.js:21:13)
Server is running on http://localhost:3000 or http://127.0.0.1:3000
   at Server.app.listen (.../index.js:67:11)
9njqaruj

9njqaruj2#

对于临时黑客来说,找到您想要删除的日志语句并不太困难,您可以自己覆盖console.log

var log = console.log;
console.log = function() {
    log.apply(console, arguments);
    // Print the stack trace
    console.trace();
};


// Somewhere else...
function foo(){
    console.log('Foobar');
}
foo();

它会打印出

Foobar
Trace
at Console.console.log (index.js:4:13)
at foo (index.js:10:13)
at Object.<anonymous> (index.js:12:1)
...

这里有很多噪音,但是调用堆栈中的第二行at foo (index.js:10:13)应该会将您指向正确的位置。

qoefvg9y

qoefvg9y3#

到目前为止,这个问题的所有解决方案都依赖于将堆栈跟踪作为一个字符串进行拆分和匹配,这将在将来该字符串的格式发生变化的情况下(不太可能)中断。受this gist on GitHub和这里的其他答案的启发,我想提供自己的解决方案:

'use strict';

const path = require('path');

['debug', 'log', 'warn', 'error'].forEach((methodName) => {
    const originalLoggingMethod = console[methodName];
    console[methodName] = (firstArgument, ...otherArguments) => {
        const originalPrepareStackTrace = Error.prepareStackTrace;
        Error.prepareStackTrace = (_, stack) => stack;
        const callee = new Error().stack[1];
        Error.prepareStackTrace = originalPrepareStackTrace;
        const relativeFileName = path.relative(process.cwd(), callee.getFileName());
        const prefix = `${relativeFileName}:${callee.getLineNumber()}:`;
        if (typeof firstArgument === 'string') {
            originalLoggingMethod(prefix + ' ' + firstArgument, ...otherArguments);
        } else {
            originalLoggingMethod(prefix, firstArgument, ...otherArguments);
        }
    };
});

// Tests:
console.log('%s %d', 'hi', 42);
console.log({ a: 'foo', b: 'bar'});

与其他解决方案不同,此脚本

  • 不输出附加行,并且
  • 正确处理字符串替换。

您可以使用chalkcolor.jsprefix着色,但我不想在这里介绍这方面的依赖关系。
上述脚本使用V8 API自定义堆栈跟踪。callee是一个CallSite对象,如果要自定义prefix,请使用以下方法:

  • getThis:返回this的值
  • getTypeName:以字符串形式返回this的类型。这是存储在this的构造函数字段中的函数的名称(如果可用),否则是对象的[[Class]]内部属性。
  • 返回当前函数
  • getFunctionName:返回当前函数的名称,通常是其name属性。如果name属性不可用,则尝试从函数的上下文推断名称。
  • getMethodName:返回this的属性的名称或保存当前函数的原型之一
  • getFileName:如果此函数是在脚本中定义的,则返回脚本的名称
  • getLineNumber:如果此函数是在脚本中定义的,则返回当前行号
  • getColumnNumber:如果此函数是在脚本中定义的,则返回当前列号
  • getEvalOrigin:如果此函数是通过调用eval创建的,则返回表示调用eval的位置的字符串
  • isToplevel:这是顶级调用吗?也就是说,这是全局对象吗?
  • isEval:此调用是否发生在对eval的调用所定义的代码中?
  • isNative:此调用是否使用本地V8代码?
  • isConstructor:这是构造函数调用吗?
  • isAsync:这是异步调用吗(即awaitPromise.all())?
  • isPromiseAll:这是对Promise.all()的异步调用吗?
  • getPromiseIndex:返回在Promise.all()中跟随的promise元素的索引,用于异步堆栈跟踪,或者如果CallSite不是Promise.all()调用,则返回null

这个答案是an answer I just gave对类似问题的交叉发布,因为更多的人可能会找到这个页面。

wfypjpf4

wfypjpf44#

我发现Dmitry Druganov's answer非常不错,但是我在Windows 10(节点为8.9.4)上尝试了一下,效果不太好。它打印的是完整路径,类似于:

Loading settings.json
   at fs.readdirSync.filter.forEach (D:\Users\Piyin\Projects\test\settings.js:21:13)
Server is running on http://localhost:3000 or http://127.0.0.1:3000
   at Server.app.listen (D:\Users\Piyin\Projects\test\index.js:67:11)

因此,我采取了说的答案,并作出了这些改进(从我的Angular 来看):

  • 假设堆栈跟踪的重要行是第三行(第一行是单词Error,第二行是放置此脚本的位置)
  • 删除当前脚本文件夹路径(由__dirname给出,在我的例子中为D:\Users\Piyin\Projects\test)。* 注意:要使其正常工作,脚本应位于项目的主Javascript * 中
  • 删除起始at
  • 将文件信息放在实际日志之前
  • 将信息格式化为Class.method at path/to/file:line:column

这就是:

['log','warn','error'].forEach((methodName) => {
  const originalMethod = console[methodName];
  console[methodName] = (...args) => {
    try {
      throw new Error();
    } catch (error) {
      originalMethod.apply(
        console,
        [
          (
            error
            .stack // Grabs the stack trace
            .split('\n')[2] // Grabs third line
            .trim() // Removes spaces
            .substring(3) // Removes three first characters ("at ")
            .replace(__dirname, '') // Removes script folder path
            .replace(/\s\(./, ' at ') // Removes first parentheses and replaces it with " at "
            .replace(/\)/, '') // Removes last parentheses
          ),
          '\n',
          ...args
        ]
      );
    }
  };
});

下面是新的输出:

fs.readdirSync.filter.forEach at settings.js:21:13
 Loading settings.json
Server.app.listen at index.js:67:11
 Server is running on http://localhost:3000 or http://127.0.0.1:3000

下面是手工缩小的代码(240字节):

['log','warn','error'].forEach(a=>{let b=console[a];console[a]=(...c)=>{try{throw new Error}catch(d){b.apply(console,[d.stack.split('\n')[2].trim().substring(3).replace(__dirname,'').replace(/\s\(./,' at ').replace(/\)/,''),'\n',...c])}}});
oymdgrw7

oymdgrw75#

稍微修改了noppa的答案,这个版本将输出如下内容:

/file/in-which/console/is/called.js:75:23
 The stuff you want to log.

这是干净和方便的(特别是在VSCode中使用-这将把文件路径变成一个链接)。

const { log } = console;
function proxiedLog(...args) {
  const line = (((new Error('log'))
    .stack.split('\n')[2] || '…')
    .match(/\(([^)]+)\)/) || [, 'not found'])[1];
  log.call(console, `${line}\n`, ...args);
}
console.info = proxiedLog;
console.log = proxiedLog;

// test
console.log('Hello!');

这个代码段只能在NodeJS环境中正常工作...

dojqjjoe

dojqjjoe6#

将行号附加到日志的末尾

const stackTrace = function () {
  let obj = {}
  Error.captureStackTrace(obj, stackTrace)
  return obj.stack
}

const getLine = function (stack) {
  let matchResult = stack.match(/\(.*?\)|\s.+/g) || []
  let arr = matchResult.map((it) => {
    return it.split(' ').pop().replace(/\(|\)/g, '')
  })
  return arr[1] ?? ''
}

const log = function (...args) {
  let stack = stackTrace() || ''
  let matchResult = getLine(stack)
  let line = matchResult
  for (var i in arguments) {
    if (typeof arguments[i] == 'object') {
      // util.inspect(arguments[i], false, 2, false)
      arguments[i] = JSON.stringify(arguments[i])
    }
  }
  arguments[i] += '  ' + line
  console.log.apply(console, arguments)
}

log("test")
muk1a3rh

muk1a3rh7#

简单和详尽的解决方案,如果你想暂时找到日志的来源:

{
  const logOriginal = process.stdout.write
  // @ts-ignore
  const log = (msg) => logOriginal.call(process.stdout, msg + '\n')
  ;['stdout', 'stderr'].forEach((stdName) => {
    // @ts-ignore
    var methodOriginal = process[stdName].write
    // @ts-ignore
    process[stdName].write = function (...args) {
      log('LOG:')
      // @ts-ignore
      methodOriginal.apply(process[stdName], args)
      // @ts-ignore
      log(new Error().stack.replace(/^Error/, 'LOGGED FROM:'))
    }
  })

  Error.stackTraceLimit = Infinity
}
2wnc66cl

2wnc66cl8#

常量显示名称=(名称)=〉{

return 

      console.log(name)

}
显示名称(“挤压”)

相关问题