在V8 JavaScript(Chrome和Node.js)中获取行号

66

曾经在类似C语言的编程语言中工作过的JavaScript开发者常常会想念使用某些类型的内省能力,例如记录行号以及当前方法是由哪个方法调用的。如果你正在使用V8 (Chrome, Node.js),可以采用以下方法。

2个回答

102
Object.defineProperty(global, '__stack', {
  get: function(){
    var orig = Error.prepareStackTrace;
    Error.prepareStackTrace = function(_, stack){ return stack; };
    var err = new Error;
    Error.captureStackTrace(err, arguments.callee);
    var stack = err.stack;
    Error.prepareStackTrace = orig;
    return stack;
  }
});

Object.defineProperty(global, '__line', {
  get: function(){
    return __stack[1].getLineNumber();
  }
});

console.log(__line);

上述代码将记录19

结合使用arguments.callee.caller,你可以更接近于C语言中宏定义所提供的有用日志记录。


3
https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi#Customizing_stack_traces 中列出了v8 StackTrace API中的其他方法。 一般列表包括:getThis,getTypeName,getFunction,getFunctionName,getMethodName,getFileName,getLineNumber,getColumnNumber,getEvalOrigin,isToplevel,isEval,isNative,isConstructor。 - zamnuts
还可以参考这个答案,其中包含一些输出整个跟踪的示例代码。https://dev59.com/SVfUa4cB1Zd3GeqPKK7L#10942404 - Charles Kendrick
@zamnuts 那个页面似乎不再列出任何这些函数了。 - Michael
@Midas 在Node.js中它可以在严格模式下工作——只需将'use strict'和我的代码示例粘贴到Node REPL中进行测试即可。尽管如此,'__stack'技术是一种特殊操作,您在生产代码中大部分情况下不应该使用它。顺便说一句,我通常在Node.js中使用它而不是在Chrome中使用它。通过在最新版本的Node中使用ESLint w/ ES6,可以获得许多严格模式的好处。如果需要,您可以使用转换器或捆绑器插件添加'strict'并剥离__stack以用于生产。请参见Bunyan,这是一个推荐有条件使用此技术的软件包。 - james_womack
2
2019年v8堆栈跟踪API文档的URL为https://v8.dev/docs/stack-trace-api。 - gtzilla
显示剩余2条评论

8

我认为接受的答案存在问题,因为当您想要打印内容时,您可能正在使用记录器。在这种情况下,使用接受的解决方案将始终打印相同的行:)

进行一些小改动将有助于避免这种情况!

在我们的情况下,我们正在使用Winston进行记录,因此代码如下所示(请注意下面的代码注释):

/**
 * Use CallSite to extract filename and number, for more info read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
 * @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 CallSites in order to get to the caller we're looking for
        // in your code, the number of stacks depends on the levels of abstractions you're using
        // in my code I'm stripping frames that come from logger module and winston (node_module)
        const callSite = this.stack.find(line => line.getFileName().indexOf('/logger/') < 0 && line.getFileName().indexOf('/node_modules/') < 0);
        return callSite.getFileName() + ':' + callSite.getLineNumber();
    } finally {
        Error.prepareStackTrace = oldStackTrace;
    }
};

2
这是另一个很好的解决方案。我的答案早就写好了,但我相信我提到的 arguments.callee.caller 也可以解决你在这里提出的问题。 - james_womack
@james_womack arguments.callee.caller 不总是可达的(在我的nodejs应用程序中不可达)。另外:https://eslint.org/docs/rules/no-caller 但我猜你在回答时使用它是可以的 :) - Nir Alfasi
理解——你所说的有道理 - james_womack
1
ES5严格模式中已删除arguments.callee:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments/callee - Jason Stewart
这个答案唯一的问题就是arguments.callee。它真的不应该被使用。我很惊讶它甚至还被支持。 - JΛYDΞV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接