如何在node.js中获取调用函数的文件路径?

42

以下是来自三个文件的示例代码:

// foo.js
var myFunc = require("./myFunc");
function foo(){
   myFunc("message");
}

// bar.js
var myFunc = require("./myFunc");
function bar(){
   myFunc("message");
}

// myFunc.js
module.exports = myFunc;
function myFunc(arg1){
   console.log(arg1);
   // Here I need the file path of the caller function
   // For example, "/path/to/foo.js" and "/path/to/bar.js"
}

我需要动态地获取调用函数的文件路径,而不需要传递任何额外参数,对于myFunc


2
https://dev59.com/tmQn5IYBdhLWcg3wn4NS - zloctb
可能是重复的问题:Nodejs:获取调用函数的文件名 - ripper234
console.log((new Error()).stack.split("\n")[1].split("/").slice(-1)[0].split(":")[0]) // "caller_file_name.js" - marcelosalloum
6个回答

48
你需要调整v8的内部工作方式。请参阅:关于JavaScript堆栈跟踪API的维基页面
我基于一个建议的提交的一些代码进行了一些测试,它似乎可以工作。你最终会得到一个绝对路径。
// omfg.js

module.exports = omfg

function omfg() {
  var caller = getCaller()
  console.log(caller.filename)
}

// private

function getCaller() {
  var stack = getStack()

  // Remove superfluous function calls on stack
  stack.shift() // getCaller --> getStack
  stack.shift() // omfg --> getCaller

  // Return caller's caller
  return stack[1].receiver
}

function getStack() {
  // Save original Error.prepareStackTrace
  var origPrepareStackTrace = Error.prepareStackTrace

  // Override with function that just returns `stack`
  Error.prepareStackTrace = function (_, stack) {
    return stack
  }

  // Create a new `Error`, which automatically gets `stack`
  var err = new Error()

  // Evaluate `err.stack`, which calls our new `Error.prepareStackTrace`
  var stack = err.stack

  // Restore original `Error.prepareStackTrace`
  Error.prepareStackTrace = origPrepareStackTrace

  // Remove superfluous function call on stack
  stack.shift() // getStack --> Error

  return stack
}

包含omfg模块的测试:

#!/usr/bin/env node
// test.js

var omfg = require("./omfg")

omfg()

并且您将在控制台上获得test.js的绝对路径。


解释

这不仅仅是一个“node.js”问题,更是一个“v8”问题。

请参见:自定义异常的堆栈跟踪集合

Error.captureStackTrace(error, constructorOpt)error参数添加了一个stack属性,默认情况下会计算为一个String(通过FormatStackTrace)。 如果Error.prepareStackTrace(error, structuredStackTrace)是一个Function,那么就会调用它而不是FormatStackTrace

因此,我们可以使用我们自己的函数来覆盖Error.prepareStackTrace,该函数将返回我们想要的任何内容--在本例中,仅为structuredStackTrace参数。

然后,structuredStackTrace[1].receiver是一个表示调用者的对象。


嗨,我是nodejs的新手,您能告诉我getStack()中到底发生了什么吗? - Dexter
1
谢谢解释,现在我可以获取调用函数的文件名了。 - Dexter
这帮助我找出了内存泄漏发生的位置!!!非常感谢你!!! - Marcello DeSales

43

或者,您可以使用module.parent.filename来获取需要您的模块的绝对路径,而不是操纵V8引擎的内部工作。如此所示:https://gist.github.com/capaj/a9ba9d313b79f1dcd9a2

请记住,模块会被缓存,因此如果其他文件需要它并调用它,那么它将始终是第一个导入程序的路径。


8
如果在函数和调用者之间存在多个 module.exports,那么它将无法正常工作。 - Sylvain B
@Sylvain,如果是这样的话,你应该能够一直遍历到父级。如果你正在动态加载模块,那可能会有问题,但在大多数情况下,你不会这样做。 - Capaj
1
@Capaj 我本来也想指出Sylvain同样的问题,但那是因为我在寻找解决方案,而不是OP。 OP要求调用“function”的文件名,而不是调用“module”的文件名。你的答案完美地回答了我的问题,但与OP的问题不完全匹配,所以我必须同意他的观点。不过,你让我省去了很多持续搜索的时间:D - Swivel
有没有办法在每次需要您的模块时获取调用者的名称,而不仅仅是第一个调用者(通过 parent)? - odigity
最终使用了 https://github.com/sindresorhus/callsites/,至少在我需要的Node环境中可以工作。 - odigity

6
您可以使用caller-callsite包:
console.log(callerCallsite().getFileName());

替代方案有callsitesstackman包。 callsites提供所有调用站点(在v8术语中为“堆栈帧”)。而stackman则可提供带有自定义函数和行为的调用站点。此外,还有源上下文等其他信息,即调用站点行周围的代码行数。如果有源映射,则它也会使用源映射。

使用stackman的问题是它异步返回调用站点。当从调试器运行时,这并不特别实用。

以下是一些我使用过的代码,您可能会发现有用:

var callsites = require('callsites');
var util = require('util');
var path = require('path');
function printStackTrace() {
    callsites().slice(1).forEach(function(cs) {
        printCallSite(cs);
    });
}
function printCallSite(cs) {
    console.log(util.format('%s:%i',
        path.relative(process.cwd(), cs.getFileName()),
        cs.getLineNumber()));
    console.log('  getTypeName(): ' + cs.getTypeName());
    console.log('  getFunctionName(): ' + cs.getFunctionName());
    console.log('  getMethodName(): ' + cs.getMethodName());
    // console.log('  getEvalOrigin(): ' + cs.getEvalOrigin());
    // console.log('  isTopLevel(): ' + (cs.isTopLevel ? cs.isTopLevel() : null));
    // console.log('  isEval(): ' + cs.isEval());
    // console.log('  isNative(): ' + cs.isNative());
    // console.log('  isConstructor(): ' + cs.isConstructor());
}
function getCallSiteIndexes(cond) {
    var cond = cond || function() { return true; };
    var options = arguments[1] || {};
    var css = options['callsites'] || callsites().slice(1);
    var r = [];
    for (var i = 0; i < css.length; i++) {
        var cs = css[i];
        if (cond(cs)) {
            if (options['first'])
                return i;
            r.push(i);
        }
    }
    return options['first'] ? null : r;
}
function getFirstCallSiteIndex(cond) {
    var css = callsites().slice(1);
    return getCallSiteIndexes(cond, {first: true, callsites: css});
}
function getCallSites(cond) {
    var options = arguments[1] || {};
    var css = options['callsites'] || callsites().slice(1);
    var indexes = getCallSiteIndexes(cond,
        Object.assign({}, {callsites: css}, options));
    if (options['first'])
        return css[indexes];
    return indexes.map(function(i) {
        return css[i];
    });
}
function getFirstCallSite(cond) {
    var css = callsites().slice(1);
    return getCallSites(cond, {first: true, callsites: css});
}

fucntion f() {
    var firstCS = callsites()[0];
    var runAsChildCSIndex = getFirstCallSiteIndex(function(cs) {
        return cs.getFileName() == firstCS.getFileName() && cs.getFunctionName() == 'Compiler.runAsChild';
    });
    if (runAsChildCSIndex) {
        printCallSite(callsites()[runAsChildCSIndex + 1]);
    } else {
        var compilerRunCS = getFirstCallSite(function(cs) {
            return cs.getFileName() == firstCS.getFileName() && cs.getFunctionName() == 'Compiler.run';
        });
        printCallSite(compilerRunCS);
    }
    ...

6

在Node中,获取调用者函数的路径唯一的方式是通过堆栈跟踪(忘记外部库):

function getCallerFilePath(path) {
    let stack = new Error().stack.split('\n')
    return stack[2].slice(
        stack[2].lastIndexOf('(')+1, 
        stack[2].lastIndexOf('.js')+3
    )
}

3

我的意见:

假设您有一个log对象,它会向控制台添加调用者的文件名作为额外信息,例如log响应log.info(msg)并执行类似于以下操作:

// my_module.js
log.info('hello')
$> [[my_module.js]] hello

info 将会是:

info: function(msg) {
  let caller = path.basename(module.parent.filename);
  console.log(`[[${caller}]] ${msg}`);
}
问题: 如前所述,parent.filename将返回第一个要求模块的人,而不是调用者本身。 替代方案: stack-trace是一个可以解决这个问题的模块。
const stackTrace = require('stack-trace');
...
info: function(msg) {
  let caller = path.basename(stackTrace.get()[0].getFilename());
  console.log(`[[${caller}]] ${msg}`);
}

The point: stackTrace.get()[0] 返回响应以下某些内容的最后一个 Caller
  • getFileName()
  • getColumnNumber()
  • getFunctionName()
  • getLineNumber()
  • getMethodName()

0

如果您不想使用第三方库,可以像这样操作:

    function getFileCallerURL(): string {
        const error: Error = new Error();

        const stack: string[] = error.stack?.split('\n') as string[];

        const data: string = stack[3];

        const filePathPattern: RegExp = new RegExp(`(file:[/]{2}.+[^:0-9]):{1}[0-9]+:{1}[0-9]+`);

        const result: RegExpExecArray = filePathPattern.exec(data) as RegExpExecArray;

        let filePath: string = '';

        if (result && (result.length > 1)) {
            filePath = result[1];
        }

        return filePath;
    }

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