views:

840

answers:

9

I'd like to create a routine that does some logging, takes some other actions, and then throws an Exception. I'd like this routine to be called from many different locations. However, creating Exceptions in this routine means they will have this routine in their stack trace. I would rather the stack trace not report this utility routine. Is there a way to do this without creating the Exception in the caller and passing it to the utility routine?

public static void die(String message) throws MyException {
  log(message);
  ...
  throw new MyException();
}

For programmers who are Perl/Java bilingual: how do I carp in Java?

+2  A: 

There is no way to removing the throwing function from the stack trace. The whole purpose of the stack trace is to log the exception path so allowing a function to opt out would defeat the purpose.

The only way you could change this is if you returned the exception instead of throwing it. But that forces you to depend on the caller to know to throw the exception.

throw die("someReason).fillInStackTrace();

Modified function

public static Exception die(String message) {  
  log(message);  
  ...  
  return new MyException();
}

EDIT

Added the fillInStackTrace() call to ensure the stack is reset to the point of the throw.

http://java.sun.com/j2se/1.3/docs/api/java/lang/Throwable.html#Throwable()

JaredPar
I think the stack trace shows where the Exception was created, not where it was thrown from, so I don't think even returning it would accomplish that. I may be wrong. Will check. :)
skiphoppy
@skiphoppy, i'm pretty sure it's from the point of the throw. Otherwise the JVM would report false stack traces in cases where the exception was passed around a bit.
JaredPar
Throwable() calls fillInStackTrace() -- it's done at creation time
Scott Stanchfield
@Scott, wow that's correct (it's the exact opposite on the CLR). I'll update my sample
JaredPar
A: 

No can do... I tried doing something like this a while back (I was trying to capture the stack trace to log method calls before AOP existed).

The stack trace is filled in when the exception is created, and that's done natively. For the thing I was working on, I ended up reading the stack trace and looking at the second element, but that wouldn't help you here...

Scott Stanchfield
A: 

You might consider having your method receive a Logger as a parameter to the method. This would allow you to control the logging output based on the calling class.

I would advise against wanting your exception to exclude this part of the stack trace though. When you leave and some new person gets to maintain your code, the are not going to appreciate this non-standard error handling.

Nathan Feger
A: 

Do you throw the stack trace just to be able to analyze it? In that case you could call the getStackTrace() method on the Exception which returns a StackTraceElement[]. There you can filter the elements you don't want (f.ex. the "die" method).

+1  A: 

Mmm.. you could subclass exception and override all the methods in it, and wrap the original exception. Internally, generate a new stack trace using the getStackTrace() method from the wrapped exception. I haven't looked at the source of Exception, but you may not even have to override that many methods.

John Ellinwood
+4  A: 

If you want to use an Exception to control the flow and what happens afterwards, a good advice it to override the fillInStackTrace() method:

public Throwable fillInStackTrace() {
   return this;
}

As a result you'll have an Exception without the stacktrace and with a reduced overhead (filling in the stack trace takes time).

+10  A: 

You can set the stack trace of any exception you want to throw:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CarpTest {
    public static void main(String[] args) {
        new CarpTest().run();
    }

    public void run() {
        methodThatCarps();
    }

    private void methodThatCarps() {
        carp("Message");
    }

    private void carp(String message) {
        RuntimeException e = new RuntimeException(message);
        e.fillInStackTrace();
        List<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
        stack.remove(0);
        e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
        throw e;
    }
}

This will print the following stacktrace at runtime:

Exception in thread "main" java.lang.RuntimeException: Message
    at CarpTest.methodThatCarps(CarpTest.java:18)
    at CarpTest.run(CarpTest.java:14)
    at CarpTest.main(CarpTest.java:10)

Note that as you want the method "carp" does not appear in the stacktrace. However the manipulation of stacktraces shoud only be done with greates care.

ordnungswidrig
That's it; thank you! I think I may also be able to combine that with the approach of subclassing Exception and overriding fillInStackTrace(), and put the code to remove the first StackTraceElement in there.
skiphoppy
+1  A: 

Maybe you should consider approaching the problem from a different direction. Rather than modify the stack trace, why not just have your exception generator method (die in your example) return the exception rather than throw it? Then your call is throw die();.

For example:

// revised die() method:
public static MyException die(String message){
  log(message);
  //...
  return new MyException();
}


// calling code:
throw die("a-whoopsie daisy!");

Now, granted, throw die() might seem a little un-aesthetic, so you could rename die() to newException() or something. But the requirement that the exception processing method does not show in the stack trace is met -- die() (or newException()) returns before the exception is thrown and is therefore not part of the stack to be traced.

Edit: My bad. I've spent so much time working with C# that I forgot that in Java exception stack traces are generated at instantiation, where in C#/.NET exception stack traces are generated at throw-time.

So this trick would work in C#, but not in Java.

Randolpho
I don't think this will work. The stacktrace is populated when the Exception is instantiated, not what it's thrown.
Outlaw Programmer
Well, it's cool to know that it would work in C#. I might need that some day. :) I voted you back up to 0 from -1. :)
skiphoppy
call fillInStackTrace before throwing
ordnungswidrig
+1  A: 

Based on what ordnungswidrig said about setting the stack trace, and what unknown (google) said about overriding fillInStackTrace(), I've created a CarpException that does exactly what I want. Note that I found I had to strip out four stack trace frames instead of just one, as I was picking up frames from both Throwable and Exception.

public class CarpException extends Exception {
  @Override
  public Throwable fillInStackTrace() {
    super.fillInStackTrace();
    StackTraceElement[] origStackTrace = getStackTrace();
    StackTraceElement[] newStackTrace = new StackTraceElement[origStackTrace.length - 4];
    System.arraycopy(origStackTrace, 4, newStackTrace, 0, origStackTrace.length - 4);
    setStackTrace(newStackTrace);
    return this;
  }
}
skiphoppy