I noticed some confusion initially with my question. I\'m not asking about how to configure a logger nor how to use a logger properly, but rather how to capture all of the infor
We tend to create our most important application specific runtime exception classes with some special constructors, some constants and a ResourceBundle.
Example snippet:
public class MyException extends RuntimeException
{
private static final long serialVersionUID = 5224152764776895846L;
private static final ResourceBundle MESSAGES;
static
{
MESSAGES = ResourceBundle.getBundle("....MyExceptionMessages");
}
public static final String NO_CODE = "unknown";
public static final String PROBLEMCODEONE = "problemCodeOne";
public static final String PROBLEMCODETWO = "problemCodeTwo";
// ... some more self-descriptive problem code constants
private String errorCode = NO_CODE;
private Object[] parameters = null;
// Define some constructors
public MyException(String errorCode)
{
super();
this.errorCode = errorCode;
}
public MyException(String errorCode, Object[] parameters)
{
this.errorCode = errorCode;
this.parameters = parameters;
}
public MyException(String errorCode, Throwable cause)
{
super(cause);
this.errorCode = errorCode;
}
public MyException(String errorCode, Object[] parameters, Throwable cause)
{
super(cause);
this.errorCode = errorCode;
this.parameters = parameters;
}
@Override
public String getLocalizedMessage()
{
if (NO_CODE.equals(errorCode))
{
return super.getLocalizedMessage();
}
String msg = MESSAGES.getString(errorCode);
if(parameters == null)
{
return msg;
}
return MessageFormat.format(msg, parameters);
}
}
In the properties file we specify the exception descriptions, e.g.:
problemCodeOne=Simple exception message
problemCodeTwo=Parameterized exception message for {0} value
Using this approach
throw new MyException(MyException.PROBLEMCODETWO, new Object[] {parameter}, bthe)
)EDIT: change getMessage
to getLocalizedMessage
as Elijah suggested.
EDIT2: Forgot to mention: this approach does not support Locale changing "on-the-fly" but it is intentional (it can be implemented if you need it).
I have created a key combination in eclipse for a catch block creation.
logmsg as key and the result will be:
catch(SomeException se){
String msg = ""; //$NON-NLS-1$
Object[] args = new Object[]{};
throw new SomeException(Message.format(msg, args), se);
}
You can put as many informations as you want in the Message like:
msg = "Dump:\n varA({0}), varB({1}), varC({2}), varD({3})";
args = new Object[]{varA, varB, varC, varD};
Or some user information:
msg = "Please correct the SMTP client because ({0}) seems to be wrong";
args = new Object[]{ smptClient };
You should think about using log4j as a logger, so you can print your states where ever you want. With the options DEBUG, INFO, ERROR you can define how many loggings you want to see in your log file.
When you deliver your application you will set the log level to ERROR, but when you want to debug your application you can use DEBUG as default.
When you are using a logger, you only have to print a hand full of information in your exception, because the state of some variables you would print into the log file before you are calling the critical try...catch block.
String msg = "Dump:\n varA({0}), varB({1}), varC({2}), varD({3})";
Object[] args = new Object[]{varA, varB, varC, varD};
logger.debug(Message.format(msg, args));
try{
// do action
}catch(ActionException ae){
msg = "Please correct the SMTP client because ({0}) seems to be wrong";
args = new Object[]{ smptClient };
logger.error(Message.format(msg, args), se);
throw new SomeException(Message.format(msg, args), se);
}
As for the type of debug information you need, why don't you just always log the value and don't bother so much with a local try/catch. Just use the Log4J config file to point your debug messages to a different log, or use chainsaw so you can remotely follow the log messages. If all that fails maybe you need a new log message type to add to debug()/info()/warn()/error()/fatal() so you have more control over which messages get sent where. This would be the case when defining appenders in the log4j config file is impractical due to the high number of places where this type of debug logging needs to be inserted.
While we're on the subject, you've touched on one of my pet peeves. Constructing a new exception in the catch block is a code smell.
Catch(MyDBException eDB)
{
throw new UnhandledException("Something bad happened!", eDB);
}
Put the message in the log and then rethrow the exception. Constructing Exceptions is expensive and can easily hide useful debugging information.
First off, inexperienced coders and those who like to cut-n-paste (or begin-mark-bug, end-mark-bug, copy-bug, copy-bug, copy-bug) it can transform easily to this:
Catch(MyDBException eDB)
{
throw new UnhandledException("Something bad happened!");
}
Now you've lost the original stacktrace. Even in the first case, unless the wrapping Exception handles the wrapped exception properly, you can still lose details of the original exception, the stacktrace being the most likely.
Rethrowing exceptions might be necessary but I've found that it should be handled more generally and as a strategy to communicate between layers, like between your business code and the persistance layer, like so:
Catch(SqlException eDB)
{
throw new UnhandledAppException("Something bad happened!", eDB);
}
and in this case, the catch block for the UnhandledAppException is much further up the call stack where we can give the user an indication that they either need to retry their action, report a bug, or whatever.
This let our main() code do something like this
catch(UnhandledAppException uae)
{
\\notify user
\\log exception
}
catch(Throwable tExcp)
{
\\for all other unknown failures
\\log exception
}
finally
{
\\die gracefully
}
Doing it this way meant that local code could catch the immediate and recoverable exceptions where debug logs could be done and the exception not have to be rethrown. This would be like for DivideByZero or maybe a ParseException of some sort.
As for "throws" clauses, having a layer-based exception strategy meant being able to limit the number of exception types that have to be listed for each method.
Why not keep a local copy/list of all messages that would have gone to the debug log if it was enabled, and pass that to the custom exception when you throw it? Something like:
static void logDebug(String message, List<String> msgs) {
msgs.add(message);
log.debug(message);
}
//...
try {
List<String> debugMsgs = new ArrayList<String>();
String myValue = someObject.getValue();
logDebug("Value: " + myValue, debugMsgs);
doSomething(myValue);
int x = doSomething2();
logDebug("doSomething2() returned " + x, debugMsgs);
}
catch (BadThingsHappenException bthe) {
// at the point when the exception is caught,
// debugMsgs contains some or all of the messages
// which should have gone to the debug log
throw new UnhandledException(bthe, debugMsgs);
}
Your exception class can make use of this List
parameter when forming getMessage()
:
public class UnhandledException extends Exception {
private List<String> debugMessages;
public UnhandledException(String message, List<String> debugMessages) {
super(message);
this.debugMessages = debugMessages;
}
@Override
public String getMessage() {
//return concatentation of super.getMessage() and debugMessages
}
}
The usage of this would be tedious - as you'd have to declare the local variable in every single try/catch where you wanted this type of information - but it might be worth it if you have just a few critical sections of code in which you'd like to maintain this state information on an exception.
If you want to somehow process the details of the error message, you could:
Use an XML text as the message, so you get a structured way:
throw new UnhandledException(String.format(
"<e><m>Unexpected things</m><value>%s</value></e>", value), bthe);
Use your own (and one for every case) exception types to capture variable information into named properties:
throw new UnhandledValueException("Unexpected value things", value, bthe);
Or else you could include it in the raw message, as suggested by others.