22

I have seen a few different flavors of exception logging utility out there, and a common concept is to capture the class and method that were running when the exception was thrown. In the case that made me wonder this, the logger is used for more than just exceptions. It has method signatures like:

public static void logMessage
    (LoggingLevel loggingLevel, String className, String methodName, String message)

It seems to me that we should be able to get the className and methodName parameters programatically. Can it be done? How?

2 Answers 2

29

My first thought (which does work) is to throw and catch a DmlException (or any Exception type you wish), and use its stack trace string as a starting point.

public class StackTrace
{
    public static String getCurrentString()
    {
        try { throw new DmlException(); }
        catch (DmlException e)
        {
            return e.getStackTraceString().substringAfter('\n');
        }
    }
}

Note the substringAfter('\n'). One of the tricky aspects of this pursuit is that you add calls to the stack in any attempt to write a generic tool to parse the stack trace for you.

While the above code works, upon further inspection it seems the Exception constructor does the work of building the stack trace, and we don't even need to throw. The following method is more concise, works just as well, and consumes less CPU:

public static String getCurrrentString()
{
    return new DmlException().getStackTraceString().substringAfter('\n');
}

So now we know how to get the stack trace. That's all well and good, but we still need to parse it to get the class name and method. Here are some example values:

Class.Demo.Subclass.doStuff: line 18, column 1
Class.Demo.doStuff: line 7, column 1
AnonymousBlock: line 1, column 1

So how do we get the class and method names? Here's one approach:

global static String getClassName(String line)
{
    if (line.startsWith('Class.'))
        line = line.substringAfter('Class.');
    return line.substringBefore(':').substringBeforeLast('.');
}    
global static String getMethod(String line)
{
    return line.substringBefore(':').substringAfterLast('.');
}

And if you want convenience methods to get the currently executing class and method, they would look something like:

global static String getClassName()
{
    return getClassName(new DmlException().getStackTraceString().substringAfter('\n'));
}
global static String getMethod()
{
    return getMethod(new DmlException().getStackTraceString().substringAfter('\n'));
}

Back to the initial problem, how would we integrate this approach into our Logger class? If we try the below approach, we'll log every time that we're in the Logger.smartLog method!

public static void smartLog(LoggingLevel level)
{
    logMessage(level, StackTrace.getClassName(), StackTrace.getMethod());
}

The most efficient and straightforward approach would be to simply not add that call to the stack.

public static void smartLog(LoggingLevel level)
{
    String trace = new DmlException().getStackTraceString().substringAfter('\n');
    logMessage(level, StackTrace.getClassName(trace), StackTrace.getMethod(trace));
}
10
  • 3
    +1 for the exception constructor; also avoid messy debug log pollution with exceptions thrown that can "throw off" the debug log reader with spurious noise
    – cropredy
    Commented Dec 27, 2016 at 16:19
  • 3
    If the stack trace is set in the constructor, what happens if the exception instance is passed off to another class and thrown there? Seems the trace would be way off. Probably doesn't matter for your purposes. Commented Dec 28, 2016 at 9:36
  • Yeah I wouldn't reuse these exceptions for other purposes.
    – Adrian Larson
    Commented Dec 28, 2016 at 14:55
  • 2
    Curiously, custom exceptions don't work in the expression return new MyException().getStackTraceString().substringAfter('\n'); -- no stacktrace is returned. I had to use DmlException or StringException or presumably any OOTB exception.
    – cropredy
    Commented Sep 8, 2017 at 0:15
  • 1
    @ysr I think you missed his point entirely. If you want to parse stack trace to determine calling context, then custom exceptions will not work.
    – Adrian Larson
    Commented Jul 18, 2018 at 17:27
0

I think the getClassName could be written this way :

public static String getClassName(Exception ex) {
    List<String> parts = ex.getStackTraceString().substringBefore(':').split('\\.');
    return parts[parts.size()-2];
}

It avoids having to care about 'Class' and seems to do less thing for the same result

But it seems to cost double in CPU Time compared to:

String.valueOf(myObject).substringBefore(':');
1
  • Hmm, I haven't tried it yet, but that seems brittle. It seems like there might be circumstances where there are fewer than two parts.
    – Adrian Larson
    Commented Jul 5 at 18:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .