【问题标题】:How can one get the file path of the caller function in node.js?如何在 node.js 中获取调用者函数的文件路径?
【发布时间】:2012-11-05 07:05:27
【问题描述】:

这是来自三个文件的一些示例代码:

// 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,我需要动态获取调用函数的文件路径,而不需要传递任何额外的参数。

【问题讨论】:

标签: javascript node.js


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    你需要摆弄v8 的内部运作。请参阅:the wiki entry about the JavaScript Stack Trace API

    我对@9​​87654322@ 中的一些代码进行了一些测试,它似乎可以工作。你最终会得到一个绝对路径。

    // 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”问题。

    见:Stack Trace Collection for Custom Exceptions

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

    因此,我们可以用我们自己的函数覆盖Error.prepareStackTrace,该函数将返回我们想要的任何东西——在这种情况下,只是structuredStackTrace 参数。

    那么,structuredStackTrace[1].receiver 是一个代表调用者的对象。

    【讨论】:

    • 嗨,我是 nodejs 的新手,你能告诉我 getStack() 中实际发生了什么吗?
    • 谢谢解释,现在我可以得到调用函数的文件名了。
    • 这帮助我找出了内存泄漏发生在哪里!!!非常感谢!!!
    【解决方案3】:

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

    请记住,模块是缓存的,所以如果任何其他文件需要它并调用它,它将始终是第一个导入器的路径。

    【讨论】:

    • 是的,但是如果你在函数和调用者之间有多个module.exports,它就不起作用了。
    • @Sylvain 在这种情况下,您应该能够一路遍历父母。如果您要动态加载模块,这可能会有问题,但在大多数情况下,您不会这样做。
    • @Capaj 我正打算为同样的事情打电话给 Sylvain,但那是因为 正在寻找的解决方案,而不是 OP。 OP 要求提供调用 function 的文件名,而不是调用 module。您的回答完全符合我的问题,但与 OP 的问题不完全匹配,所以我必须同意他的观点。但是,您为我节省了很多继续搜索的时间:D
    • 有没有办法在每次需要您的模块时获取调用者的姓名,而不仅仅是第一个调用者(通过parent)?
    • 最终使用了github.com/sindresorhus/callsites,它至少可以在 Node 中使用,这正是我所需要的。
    【解决方案4】:

    我的 2 美分:

    假设您有一个 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}`);
    }
    

    重点:stackTrace.get()[0]返回最后一个响应的Caller(只是其中的一些)

    • getFileName()
    • getColumnNumber()
    • getFunctionName()
    • getLineNumber()
    • getMethodName()

    【讨论】:

      【解决方案5】:

      你可以使用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】:

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

            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;
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-19
          • 2020-03-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多