Подтвердить что ты не робот

Как получить путь к файлу вызывающей функции в node.js?

Вот пример кода из трех файлов:

// 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.

4b9b3361

Ответ 1

Вам нужно возиться с внутренней работой 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 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 свойство a stack, которое по умолчанию оценивает значение String (в порядке FormatStackTrace). Если Error.prepareStackTrace(error, structuredStackTrace) является Function, тогда он вызывается вместо FormatStackTrace.

Таким образом, мы можем переопределить Error.prepareStackTrace своей собственной функцией, которая вернет нам то, что мы хотим - в этом случае просто параметр structuredStackTrace.

Затем structuredStackTrace[1].receiver - объект, представляющий вызывающего.

Ответ 2

Или вместо того, чтобы возиться с внутренними работами двигателя V8, вы используете module.parent.filename, чтобы получить абсолютный путь к модулю, который требует ваш модуль. Как показано здесь: https://gist.github.com/capaj/a9ba9d313b79f1dcd9a2

Просто имейте в виду, что модули кэшируются, поэтому, если любой другой файл требует его и вызывает его, он всегда будет путем к первому импортеру.

Ответ 3

Мои 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()

Ответ 4

Вы можете использовать caller-callsite package:

console.log(callerCallsite().getFileName());

Альтернативы callsites и stackman. 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);
    }
    ...