How to get the name of the current and calling function in dart?
Asked Answered
J

6

19

C# has:

System.Reflection.MethodBase.GetCurrentMethod().Name

Does Dart have something similar but returns results for both the function that is currently being run as well as the name of the function that called the currently run function.

Jylland answered 22/4, 2018 at 14:4 Comment(0)
L
6
import 'dart:mirrors';
...
MethodMirror methodMirror = reflect(functionOne).function;

See also https://github.com/dart-lang/sdk/issues/11916#issuecomment-108381556

This will only work in the Dart command line VM, but not in the browser or Flutter because there reflection is not supported.

Code generation solutions like https://pub.dartlang.org/packages/reflectable might work instead where reflection is not available.

https://github.com/dart-lang/sdk/issues/28372 seems related.

Lombardy answered 22/4, 2018 at 14:13 Comment(0)
C
19

There is no way to directly access the call stack in the Dart reflection library.

You can get a string representation of the stack trace, and then try to parse that:

var stack = StackTrace.current;
var stackString = "$stack"; // because the only method on StackTrace is toString.

The stack_trace package tries to do this for you for a number of known stack trace formats, so maybe:

import "package:stack_trace";
main() {
  print(Trace.current().frames[0].member);  // prints "main" unless minified.
}
Cirrocumulus answered 23/4, 2018 at 15:10 Comment(6)
I'd assume this to be rather slow when used a lot. What do you think?Neary
It's most likely slow, yes. If the stack frame package could do lazy parsing, so you only parse the first frame, if that is all you need, it would probably be more efficient, but never as efficient as something that accesses the stack directly.Cirrocumulus
I'm trying to find a dart equivalent to the #function literal expression in Swift. Is this the appropriate stand-in for this? docs.swift.org/swift-book/ReferenceManual/Expressions.htmlLinsk
There is no appropriate stand-in for that functionality in Dart. The only performant solution is a string literal containing the actual name, and you keeping it in sync if you change the name of the function. That only works for the current function, there is no way to reflect on the caller of the current function except parsing stack traces, and they have no guaranteed format.Cirrocumulus
What does minified mean?Walley
The dart2js compiler with the -O2 optimization flag replaces source names with smaller names in the output, which means that you won't see the original source names in error message or stack traces.Cirrocumulus
F
19

I wrote a simple class that gives the current function and the caller function, but also, the file name, line number and column line from the StackTrace.current property.

Heres the code:

class CustomTrace {
  final StackTrace _trace;

  String fileName;
  String functionName;
  String callerFunctionName;
  int lineNumber;
  int columnNumber;

  CustomTrace(this._trace) {
    _parseTrace();
  }

  String _getFunctionNameFromFrame(String frame) {
    /* Just giving another nickname to the frame */
    var currentTrace = frame;

    /* To get rid off the #number thing, get the index of the first whitespace */
    var indexOfWhiteSpace = currentTrace.indexOf(' ');

    /* Create a substring from the first whitespace index till the end of the string */
    var subStr = currentTrace.substring(indexOfWhiteSpace);

    /* Grab the function name using reg expr */
    var indexOfFunction = subStr.indexOf(RegExp(r'[A-Za-z0-9]'));

    /* Create a new substring from the function name index till the end of string */
    subStr = subStr.substring(indexOfFunction);

    indexOfWhiteSpace = subStr.indexOf(' ');

    /* Create a new substring from start to the first index of a whitespace. This substring gives us the function name */
    subStr = subStr.substring(0, indexOfWhiteSpace);

    return subStr;
  }

  void _parseTrace() {
    /* The trace comes with multiple lines of strings, (each line is also known as a frame), so split the trace's string by lines to get all the frames */
    var frames = this._trace.toString().split("\n");

    /* The first frame is the current function */
    this.functionName = _getFunctionNameFromFrame(frames[0]);

    /* The second frame is the caller function */
    this.callerFunctionName = _getFunctionNameFromFrame(frames[1]);

    /* The first frame has all the information we need */
    var traceString = frames[0];

    /* Search through the string and find the index of the file name by looking for the '.dart' regex */
    var indexOfFileName = traceString.indexOf(RegExp(r'[A-Za-z]+.dart'));

    var fileInfo = traceString.substring(indexOfFileName);

    var listOfInfos = fileInfo.split(":");

    /* Splitting fileInfo by the character ":" separates the file name, the line number and the column counter nicely.
      Example: main.dart:5:12
      To get the file name, we split with ":" and get the first index
      To get the line number, we would have to get the second index
      To get the column number, we would have to get the third index
    */

    this.fileName = listOfInfos[0];
    this.lineNumber = int.parse(listOfInfos[1]);
    var columnStr = listOfInfos[2];
    columnStr = columnStr.replaceFirst(")", "");
    this.columnNumber = int.parse(columnStr);
  }
}

This class takes in a StackTrace object and reads its string and parse it.

How to use it (get the info):

void main() {
  CustomTrace programInfo = CustomTrace(StackTrace.current);

  print("Source file: ${programInfo.fileName}, function: ${programInfo.functionName}, caller function: ${programInfo.callerFunctionName}, current line of code since the instanciation/creation of the custom trace object: ${programInfo.lineNumber}, even the column(yay!): ${programInfo.columnNumber}");
}

The variable programInfo now has the function name, the caller function name, line number, column number and even the file name of the current program's execution.

You can print to the console the following:

print(StackTrace.current.toString());

And you will see how the string looks and be able to understand how i parse the string in order to get the information.

The simple benefit of this is that you dont have to install any library. I made this because i was doing a project just using Dart and i didnt want to add/install any third party library into my simple project. And you will end up with an object having all of the information by just calling the constructor. The downside of this is that it if Dart, for some reason, changes the string format of the stack trace somewhere in the future, this will no longer work BUT if this somehow happens, you can easily change how this class parses the frames*/

NOTE: This code by no means is the most optimize code, but it works :D. I would like to see some better implementations and abstractions.

Fluctuant answered 18/12, 2019 at 5:42 Comment(0)
M
14

Tidied up @LuisDev99's answer a bit, optimizing for yourself:

class LoggerStackTrace {
  const LoggerStackTrace._({
    required this.functionName,
    required this.callerFunctionName,
    required this.fileName,
    required this.lineNumber,
    required this.columnNumber,
  });

  factory LoggerStackTrace.from(StackTrace trace) {
    final frames = trace.toString().split('\n');
    final functionName = _getFunctionNameFromFrame(frames[0]);
    final callerFunctionName = _getFunctionNameFromFrame(frames[1]);
    final fileInfo = _getFileInfoFromFrame(frames[0]);

    return LoggerStackTrace._(
      functionName: functionName,
      callerFunctionName: callerFunctionName,
      fileName: fileInfo[0],
      lineNumber: int.parse(fileInfo[1]),
      columnNumber: int.parse(fileInfo[2].replaceFirst(')', '')),
    );
  }

  final String functionName;
  final String callerFunctionName;
  final String fileName;
  final int lineNumber;
  final int columnNumber;

  static List<String> _getFileInfoFromFrame(String trace) {
    final indexOfFileName = trace.indexOf(RegExp('[A-Za-z]+.dart'));
    final fileInfo = trace.substring(indexOfFileName);

    return fileInfo.split(':');
  }

  static String _getFunctionNameFromFrame(String trace) {
    final indexOfWhiteSpace = trace.indexOf(' ');
    final subStr = trace.substring(indexOfWhiteSpace);
    final indexOfFunction = subStr.indexOf(RegExp('[A-Za-z0-9]'));

    return subStr
        .substring(indexOfFunction)
        .substring(0, subStr.substring(indexOfFunction).indexOf(' '));
  }

  @override
  String toString() {
    return 'LoggerStackTrace('
        'functionName: $functionName, '
        'callerFunctionName: $callerFunctionName, '
        'fileName: $fileName, '
        'lineNumber: $lineNumber, '
        'columnNumber: $columnNumber)';
  }
}
print(LoggerStackTrace.from(StackTrace.current).toString());
Menashem answered 27/1, 2021 at 17:30 Comment(0)
L
6
import 'dart:mirrors';
...
MethodMirror methodMirror = reflect(functionOne).function;

See also https://github.com/dart-lang/sdk/issues/11916#issuecomment-108381556

This will only work in the Dart command line VM, but not in the browser or Flutter because there reflection is not supported.

Code generation solutions like https://pub.dartlang.org/packages/reflectable might work instead where reflection is not available.

https://github.com/dart-lang/sdk/issues/28372 seems related.

Lombardy answered 22/4, 2018 at 14:13 Comment(0)
S
5

LuisDev99's answer doesn't cope well with inner methods and anonymous lambda blocks, so I used a more complex regex approach.

My solution:

/* 
  Define regex for each entry in the stack 

  group 0: full line
  group 1: stack index
  group 2: function name
  group 3: package
  group 4: file name
  group 5: line number
  group 6: column number

*/
RegExp regExp = new RegExp(r'^#(\d+) +(.+) +\(package:([^/]+)/(.+\.\w):(\d+):(\d+)\)$');

/* Get the stack as an array of strings */
var frames = StackTrace.current.toString().split("\n");

/* The second entry in the stack is the caller function */
var matches = regExp.allMatches(frames[1])

/* The regex matches each line of the stack only once so only one match */
var match = matches.elementAt(0);

/* Print all groups. Note that "groupCount" doesn't include group 0 (the whole line) */
for (int i = 0; i <= match.groupCount; i++) {
  print("group $i: " + match.group(i));
}
Spongin answered 2/2, 2020 at 0:36 Comment(0)
B
0

In my case, I just need the caller's class and function name in Flutter. I wrote a simple parse method as follows.

String getCaller(StackTrace currentStack) {
  var stack = currentStack.toString();
  var newLineNum = stack.indexOf("\n", 0);
  var secondLine = stack.substring(newLineNum + 9, newLineNum + 100);
  var endIndex = secondLine.indexOf(" ", 0);
  return secondLine.substring(0, endIndex);
}

// Call this method in another function
{
  String caller = getCaller(StackTrace.current);
  debugPrint("$caller ::: RED");
}

It prints SampleClass.someMethod ::: RED

Bruni answered 23/10, 2023 at 6:34 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.