It is kind of easy:
Throwable has methods getStackTrace()
and setStackTrace()
.
From one of my projects (non open-source, but maybe I'll some day open the remote call engine):
/**
* Setzt den Stack-Trace zusammen. Das untere Ende (tiefer in der
* Aufrufhierarchie, am Anfang des Arrays/der Ausgabe) ist das,
* welches im Throwable schon drin ist, das obere Ende wird aus
* dem aktuellen Stack genommen. Dazwischen
* kommt ein "Remote-Aufruf-Markierer".
*/
Translated for your convenience:
Merges the stack trace. The lower end (deeper in the call hierarchy, at the
end of the array/the output) is what already is in the stack, the upper end
will be taken from the current stack. Between them we will put an
Remote call marker.
private void mergeStackTraces(Throwable error)
{
StackTraceElement[] currentStack =
new Throwable().getStackTrace();
int currentStackLimit = 5; // TODO: raussuchen
StackTraceElement[] oldStack =
error.getStackTrace();
StackTraceElement[] zusammen =
new StackTraceElement[currentStack.length - currentStackLimit +
oldStack.length + 1];
System.arraycopy(oldStack, 0, zusammen, 0, oldStack.length);
zusammen[oldStack.length] =
new StackTraceElement("══════════════════════════",
"<remote call %" +callID+ ">",
"", -3);
System.arraycopy(currentStack, currentStackLimit,
zusammen, oldStack.length+1,
currentStack.length - currentStackLimit);
error.setStackTrace(zusammen);
}
(On the server side, I'm already cutting off the parts of the stack trace which do not relate to the method call itself, i.e. everything related to the message handling.)
This results in a combined stack trace like this:
java.lang.SecurityException: Das Passwort für Nutzer »Paul« ist falsch.
at de.fencing_game.db.userdb.Db4oUserDB.login(Db4oUserDB.java:304)
at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:316)
at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:314)
at java.security.AccessController.doPrivileged(Native Method)
at de.fencing_game.server.impl.StandardServers$SSServer.login(StandardServers.java:313)
at de.fencing_game.transport.server.ServerTransport$ConnectionInfo$4.login(ServerTransport.java:460)
at ══════════════════════════.<remote call %2>()
at $Proxy1.login(Unknown Source)
at de.fencing_game.gui.basics.LoginUtils.login(LoginUtils.java:80)
at de.fencing_game.gui.Lobby.connectTo(Lobby.java:302)
at de.fencing_game.gui.Lobby$20.run(Lobby.java:849)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:647)
at java.awt.EventQueue.access$000(EventQueue.java:96)
at java.awt.EventQueue$1.run(EventQueue.java:608)
at java.awt.EventQueue$1.run(EventQueue.java:606)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:617)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)
I suppose the RMI system does something quite similar (just without the ══════════════════════════
).
Edit:
For your usecase, you would have to save the stack trace of the outer thread when the inner thread is started, then in the run method catch the exception and append the outer stack trace to the stack trace of the inner exception. I would really recommend putting some type of separator, though.