A couple of things... In the presentation tier, you, obviously have all your context in the user session context. If your middle-tier services add other parameters that you want to trace, you may pass you custom context object (defined in your domain model, not the HttpSession object, of course
) to all your service API and, from then on, attach it to your ThreadLocal.
However, make sure you don't over-engineer things. Think carefully. In many cases, all you need to see what exactly happens is a complete stack trace of the original unaltered exception. Checked exceptions all but guarantee that you will be losing that info more than often. Use proper exception handling. That is, implement exception handlers where they belong - on the boundaries of the components that actually know what to do with the error, and let those exceptions freely bubble up to those handlers. For that you absolutely need unchecked exceptions. They make life easier, as soon as you understand that exceptions - in the first place - are not error codes, but rather a mechanism designed to ease error handling away from the origin of errors but rather where it is most appropriate, without having to mess with the error info until it gets to the dedicated handler. If you use proper exception handling, you will end up with very few centralized handler and no error-handling code anywhere else in your system. Errors will be freely traveling through your call stacks to the handler with the complete information. Analyze stack traces, and never discard them, never replace them with "error messages." The latter is just wrong and very dangerous.
Here's something off the top of my head... If you have a web application, in most cases (according to what you have described), you would have an error handler in the presentation tier that would catch all bubbled-up RTEs, perhaps handle one or two distinctly, and redirect the rest of the Throwables to the generic system error page - with just a user friendly message. At the same time, the handler will log the full exception stack trace + the context to the logger destination (including the file system and, possibly, database if required.) You will take some of the user/application context data that you find necessary from the user session, that's easy. Instead of introducing additional contracts with the service APIs and passing the context object back and forth, to get the context from your MT services you can do something like this... You can implement some base RT exception class that allows setting a context - arbitrary objects - on it. Place that class in some common package. Your services should then throw custom service specific RT exceptions that extend that exception class. So, when your developers throw an exception within the service, they may first set the contextual objects on the exception. The class may provide some sort of getContextInfo() method that would simply write out the toString representations of all application objects the developer stored in the exception instance before throwing it. Here's a very rough implementation of such exception class:
Code:
package com.yourcompany.common;
import java.util.ArrayList;
import java.util.List;
/**
* This exception class allows storing contextual information on the exception instances. The contextual data may
* be added to an exception instance as arbitrary application objects and written out as a simple string of toString()
* representation of all stored objects.
*/
public class ContextualException extends RuntimeException {
private final transient List<Object> context = new ArrayList<Object>();
public ContextualException(String msg) {
super(msg);
}
public ContextualException(String msg, Throwable t) {
super(msg, t);
}
/**
* Adds an object to the exception context.
* @param o application object whose content may be of interest in the context of this exception
*/
public void addToContext(Object o) {
context.add(o);
}
/**
* Gets the string representation of all objects stored in this exception's context.
* @return concatenated string of all context object toString() representations
*/
public String getContext() {
StringBuilder contextStr = new StringBuilder(1000);
if (context != null) {
for (Object o : context) {
contextStr.append(o.toString());
}
}
return contextStr.toString();
}
}
Note that it doesn't format your context string in any way, but you probably won't need it as long as your objects have decent toString() methods. Also, there is one ugly issue with serialization here. I made the context list transient (that's why I am checking for null in the getContext() method) so it won't get initialized on deserialization. I just don't want to go crazy here and implement a fully blown thing with "readObject", etc. But you get the general idea.