skip to Main Content

So, I’m implementing some tracing logic and want to standardize how my spans are created. I want them to be named after the function they’re instantiated inside without having to explicitly pass the name of the function every time. Seems like this should be trivial but I can’t find a good way to do it. The best way I came up with is pretty terrible:

function getCurrentFunctionName() {
    // Create an error object to capture the stack trace
    const error = new Error('fakeError')

    // Extract the stack trace and split it into individual lines
    const stackTrace = error.stack?.split('n')

    if(stackTrace) {
        // Get the second line of the stack trace, which contains the function information
        const functionLine = stackTrace[2]

        const regexResult = ((/ats+([w.]+)s+/u).exec(functionLine))

        if(regexResult && regexResult.length >= 2) {
            // Extract the function name from the function line
            return regexResult[1]
        }
    }

    return 'unknown'
}

This seems silly, especially the part where I use regex to get such basic information. It makes even less sense when you consider that the Error constructor clearly has a way to do this exact same thing otherwise it wouldn’t be able to construct the error stack.

I know that I could do something like:

function nonStrictGetFunctionName() {
   return nonStrictGetFunctionName.caller.name
}

Which would be ideal but won’t work in my codebase because using caller/arguments is forbidden in strict mode.

Is there any other way to do this?

2

Answers


  1. Using an error’s stack isn’t a bad idea imho for testing/debugging.
    An alternative could be using Proxy‘s apply hander.
    Note that in the snippet I used a global scope to override the declared functions with proxies.
    But there are many ways to override. For example you could override exact need functions manually.
    Or if you need all functions in the local scope you could somehow parse the current scope’s source code and extract function names and use eval to dynamically override the found functions with proxies.
    As a direction for parsing: Getting All Variables In Scope

    function func1(){};
    function func2(){};
    
    for(const name of Object.keys(globalThis)){
      try{
        if(typeof globalThis[name] !== 'function'){
          continue;
        }
        window[name] = new Proxy(globalThis[name], {
          apply(fn, thisArg, args){
            console.log(`calling "${name}" with arguments:`, ...args);
            return Reflect.apply(...arguments);
          }
        });
      }catch(_){}
    }
    
    func1('test', true);
    func2('test2', false);
    Login or Signup to reply.
  2. In case you don’t like your implenetation(which is basically okay imho), you can use npm library called stacktrace-js

    Here is example code:

    const stacktrace = require("stacktrace-js");
    
    function func() {
        console.log(stacktrace.getSync().map(({ functionName }) => functionName));
    }
    
    func();
    

    It will return following array:

    [
      'func', // function name
      'Object.<anonymous>',
      'Module._compile',
      'Object.Module._extensions..js',
      'Module.load',
      'Function.Module._load',
      'Function.executeUserEntryPoint [as runMain]',
      undefined
    ]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search