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

时间:2017-07-30 00:49:06

标签: node.js

有一个旧应用程序,使用console.log打印出大量消息,但我找不到调用哪些文件和行console.log

有没有办法挂钩应用并显示文件名和行号?

5 个答案:

答案 0 :(得分:13)

每次通话都有完整的堆栈跟踪有点吵。我刚刚改进了@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}`]);
  };
});

它还修补了其他方法(对于Nodej很有用,因为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)

答案 1 :(得分:10)

对于临时hack来找到你想要删除的日志语句,自己覆盖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)应该指向正确的位置。

答案 2 :(得分:5)

到目前为止,此问题的所有解决方案都依赖于将堆栈跟踪作为字符串进行拆分和匹配,这在将来不太可能更改该字符串的格式的情况下(不太可能)中断。受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 to customize stack tracescallee是一个CallSite对象,可以通过以下方法自定义prefix

  • getThis:返回this的值
  • getTypeName:以字符串形式返回this的类型。这是存储在this的构造函数字段中的函数的名称(如果有),否则是对象的[[Class]]的内部属性。
  • getFunction:返回当前函数
  • 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元素的索引,如果null不是{{1},则返回CallSite }。

此答案是an answer I just gave的交叉帖子,与更多人可能会发现此页面的问题类似。

答案 3 :(得分:3)

我发现Dmitry Druganov's answer非常好,但我在Windows 10(使用Node 8.9.4)上尝试过它并且它运行良好。它打印了完整的路径,如:

CentOS 7

所以我接受了所说的答案并做了这些改进(从我的观点来看):

  • 假设堆栈跟踪的重要行是第三行(第一行是单词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) ,第二行是放置此脚本的地方)
  • 删除当前脚本文件夹路径(由__dirname给出,在我的情况下为Error)。 注意:为了使其运行良好,脚本应该位于项目的主要Javascript
  • 删除起始D:\Users\Piyin\Projects\test
  • 将文件信息放在实际日志之前
  • 将信息格式化为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
        ]
      );
    }
  };
});

这里是手写代码(240字节):

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

答案 4 :(得分:0)

略微修改了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环境中才能正常运行...