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 information that would have been logged at a lower logging level than what the current logging level is in the exception message.
I have been noticing two patterns in Java for logging information that may be useful to a developer when an exception occurs.
The following pattern seems very common. Basically, you just have your logger log information in-line as needed, so that when an exception occurs you have the log trace.
try {
String myValue = someObject.getValue();
logger.debug("Value: {}", myValue);
doSomething(myValue);
}
catch (BadThingsHappenException bthe) {
// consider this a RuntimeException wrapper class
throw new UnhandledException(bthe);
}
The drawback with the above approach is that if your users require relatively quiet logs and need a high level of reliability to the point where they just can't "try it again in debug mode", the exception message contains insufficient data by itself to be useful to the developer.
The next pattern is one that I have seen that tries to mitigate this problem but seems ugly:
String myValue = null;
try {
myValue = someObject.getValue();
doSomething(myValue);
}
catch (BadThingsHappenException bthe) {
String pattern = "An error occurred when setting value. [value={}]";
// note that the format method below doesn't barf on nulls
String detail = MessageFormatter.format(pattern, myValue);
// consider this a RuntimeException wrapper class
throw new UnhandledException(detail, bthe);
}
The above pattern seems to somewhat solve the problem, however, I'm not sure I like to declare so many variables outside the scope of the try block. Especially, when I have to deal with very complicated states.
The only other approach I have seen is using a Map to store key-value pairs that are then dumped into the exception message. I'm not sure I like that approach either since it seems to create code bloat.
Is there some Java voodoo out there that I am missing? How do you handle your exception state information?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With