43

Here is some sample code from three files:

// 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" } 

I need to get the file path of the caller function dynamically, without any extra argument passing, for myFunc.

3

6 Answers 6

48

You need to fiddle with the inner workings of v8. See: the wiki entry about the JavaScript Stack Trace API.

I've based a little test on some code in a proposed commit and it seems to work. You end up with an absolute path.

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

And a test that includes omfg module:

#!/usr/bin/env node // test.js var omfg = require("./omfg") omfg() 

And you will get on the console the absolute path of test.js.


EXPLANATION

This is not so much a "node.js" issue as it is a "v8" issue.

See: Stack Trace Collection for Custom Exceptions

Error.captureStackTrace(error, constructorOpt) adds to the error parameter a stack property, which evaluates by default to a String (by way of FormatStackTrace). If Error.prepareStackTrace(error, structuredStackTrace) is a Function, then it is called instead of FormatStackTrace.

So, we can override Error.prepareStackTrace with our own function that will return whatever we want--in this case, just the structuredStackTrace parameter.

Then, structuredStackTrace[1].receiver is an object representing the caller.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi, I am new bie in nodejs, Can you please tell me what is actually happening in getStack() ?
thanks for the explanation, now I can get the file name of the caller function.
This helped me figure out where the memory leak was occurring!!! Thanks a lot for this!!!
43

Or instead of fiddling with inner workings of the V8 engine, you use module.parent.filename to get absolute path to the module that required your module. As demonstrated here: https://gist.github.com/capaj/a9ba9d313b79f1dcd9a2

Just keep in mind that modules are cached, so if any other file requires it and calls it, it will always be the path to the first importer.

5 Comments

Yes but if you have multiple module.exports between the function and the caller, it doesn't work.
@Sylvain in that case, you should be able to traverse parents all the way. That might be problematic, if you are loading modules dynamically, but in most cases, you wouldn't do that.
@Capaj I was just going to call out Sylvain for the same thing, but that was because of the solution I was looking for, not the OP. The OP asked for the filename of the calling function, not calling module. Your answer fits my question perfectly, but doesn't match fully with the OP's question, so I'd have to agree with him. However, you saved me a lot of continued searching :D
Is there a way to get the caller's name every time your module is required instead of just the first caller (via parent)?
Ended up using github.com/sindresorhus/callsites, which at least works in Node, which is what I need.
9

the only way to get the path of the caller function in a node is via the stack trace (forget external library):

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

Comments

6

You can make use of caller-callsite package:

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

The alternatives are callsites and stackman packages. callsites provides you with all call sites ("stack frames" in v8 terminology). And stackman gives the call sites decorated with custom functions and behavior. Source context, among other things. Which is lines of code surrounding call site line. Also it makes use of source maps if available.

The issue with stackman is that it returns call sites asynchronously. Which is not particularly usable when running from debugger.

Here's some code I used which you might find useful:

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

Comments

3

My 2 cents:

Let's say you have a log object which adds as extra information to the console the filename of the caller, for example log responds to log.info(msg) and will do something similar to:

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

info would be:

info: function(msg) { let caller = path.basename(module.parent.filename); console.log(`[[${caller}]] ${msg}`); } 

Problem: as said before, parent.filename will return you who did require the module in first place, not the caller itself.

Alternative: stack-trace is a module which will do the trick:

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] returns the last Caller which responds to (just some of them)

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

Comments

0

If you don't want use a third-party library you can do like this:

 function getFileCallerURL(): string { const error = new Error(); const stack = error.stack?.split('\n'); const filePathPattern = /file:\/{3}(.+)(?=:[0-9]+:[0-9]+)/; let data = ''; let result: string | undefined; let filePath = ''; if (errorStack) { result = [...errorStack].reverse().find( item => item.match(callerPatternOfUrlType) ); } if (result) { filePath = (result.match(callerPatternOfUrlType) as RegExpExecArray)[1]; } return filePath; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.