Reputation: 39464
There's a common problem I've come across a few times when logging exceptions in Java. There seem to be various different types to deal with. E.g. some wrap other exceptions and some don't have a message at all - only a type.
Most code I've seen logs an exception by using either getMessage()
or toString()
. But these methods don't always capture all the information needed to pinpoint the problem - other methods such as getCause()
and getStackTrace()
sometimes provide additional info.
For example, the exception I'm looking at right now in my Eclipse Inspect window is an InvocationTargetException
. The exception itself has no cause, no message, no stacktrace ... but the target from getCause() is InvalidUseOfMatchersException
, with these details populated.
So my question is: Given an exception of any type as an input, please provide a single method that will output a nicely formatted string containing all relevant information about the Exception (e.g. possibly recursively calling getCause()
amongst other things?) Before posting, I was nearly going to have a stab at it myself but really don't want to reinvent the wheel - surely such a thing must have been done many times before...?
Upvotes: 85
Views: 168197
Reputation: 27103
The java.util.logging
package is standard in Java SE. Its Logger
includes an overloaded log method that accepts Throwable
objects.
It will log stacktraces of exceptions and their cause for you.
For example:
import java.util.logging.Level;
import java.util.logging.Logger;
[...]
Logger logger = Logger.getAnonymousLogger();
Exception e1 = new Exception();
Exception e2 = new Exception(e1);
logger.log(Level.SEVERE, "an exception was thrown", e2);
Will log:
SEVERE: an exception was thrown
java.lang.Exception: java.lang.Exception
at LogStacktrace.main(LogStacktrace.java:21)
Caused by: java.lang.Exception
at LogStacktrace.main(LogStacktrace.java:20)
Internally, this does exactly what @philipp-wendler suggests, by the way.
See the source code for SimpleFormatter.java
. This is just a higher level interface.
Upvotes: 100
Reputation: 548
Best practice is to log the whole exception:
log.error("Our custom message", ex);
By logging the developer message together with the whole exception object, we can see the developer message followed by the exception name and message, then the stack trace. This gives us a complete picture of what methods were invoked when the exception occurred, down to the library level.
Reference/credits:
https://medium.com/w-logs/how-to-log-exception-properly-6aa80b62ff8a
Upvotes: 0
Reputation: 11423
What's wrong with the printStacktrace()
method provided by Throwable
(and thus every exception)? It shows all the info you requested, including the type, message, and stack trace of the root exception and all (nested) causes. In Java 7, it even shows you the information about "supressed" exceptions that might occur in a try-with-resources statement.
Of course you wouldn't want to write to System.err
, which the no-argument version of the method does, so instead use one of the available overloads.
In particular, if you just want to get a String:
Exception e = ...
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionDetails = sw.toString();
If you happen to use the great Guava library, it provides a utility method doing this: com.google.common.base.Throwables#getStackTraceAsString(Throwable)
.
Upvotes: 24
Reputation: 481
Something that I do is to have a static method that handles all exceptions and I add the log to a JOptionPane to show it to the user, but you could write the result to a file in FileWriter
wraped in a BufeeredWriter
.
For the main static method, to catch the Uncaught Exceptions I do:
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
//Initializations...
}
});
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException( Thread t, Throwable ex ) {
handleExceptions( ex, true );
}
}
);
And as for the method:
public static void handleExceptions( Throwable ex, boolean shutDown ) {
JOptionPane.showMessageDialog( null,
"A CRITICAL ERROR APPENED!\n",
"SYSTEM FAIL",
JOptionPane.ERROR_MESSAGE );
StringBuilder sb = new StringBuilder(ex.toString());
for (StackTraceElement ste : ex.getStackTrace()) {
sb.append("\n\tat ").append(ste);
}
while( (ex = ex.getCause()) != null ) {
sb.append("\n");
for (StackTraceElement ste : ex.getStackTrace()) {
sb.append("\n\tat ").append(ste);
}
}
String trace = sb.toString();
JOptionPane.showMessageDialog( null,
"PLEASE SEND ME THIS ERROR SO THAT I CAN FIX IT. \n\n" + trace,
"SYSTEM FAIL",
JOptionPane.ERROR_MESSAGE);
if( shutDown ) {
Runtime.getRuntime().exit( 0 );
}
}
In you case, instead of "screaming" to the user, you could write a log like I told you before:
String trace = sb.toString();
File file = new File("mylog.txt");
FileWriter myFileWriter = null;
BufferedWriter myBufferedWriter = null;
try {
//with FileWriter(File file, boolean append) you can writer to
//the end of the file
myFileWriter = new FileWriter( file, true );
myBufferedWriter = new BufferedWriter( myFileWriter );
myBufferedWriter.write( trace );
}
catch ( IOException ex1 ) {
//Do as you want. Do you want to use recursive to handle
//this exception? I don't advise that. Trust me...
}
finally {
try {
myBufferedWriter.close();
}
catch ( IOException ex1 ) {
//Idem...
}
try {
myFileWriter.close();
}
catch ( IOException ex1 ) {
//Idem...
}
}
I hope I have helped.
Have a nice day. :)
Upvotes: 1
Reputation: 1885
It should be quite simple if you are using LogBack or SLF4J. I do it as below
//imports
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//Initialize logger
Logger logger = LoggerFactory.getLogger(<classname>.class);
try {
//try something
} catch(Exception e){
//Actual logging of error
logger.error("some message", e);
}
Upvotes: 27
Reputation: 426
You can also use Apache's ExceptionUtils.
Example:
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
public class Test {
static Logger logger = Logger.getLogger(Test.class);
public static void main(String[] args) {
try{
String[] avengers = null;
System.out.println("Size: "+avengers.length);
} catch (NullPointerException e){
logger.info(ExceptionUtils.getFullStackTrace(e));
}
}
}
Console output:
java.lang.NullPointerException
at com.aimlessfist.avengers.ironman.Test.main(Test.java:11)
Upvotes: 2
Reputation: 2250
A logging script that I have written some time ago might be of help, although it is not exactly what you want. It acts in a way like a System.out.println but with much more information about StackTrace etc. It also provides Clickable text for Eclipse:
private static final SimpleDateFormat extended = new SimpleDateFormat( "dd MMM yyyy (HH:mm:ss) zz" );
public static java.util.logging.Logger initLogger(final String name) {
final java.util.logging.Logger logger = java.util.logging.Logger.getLogger( name );
try {
Handler ch = new ConsoleHandler();
logger.addHandler( ch );
logger.setLevel( Level.ALL ); // Level selbst setzen
logger.setUseParentHandlers( false );
final java.util.logging.SimpleFormatter formatter = new SimpleFormatter() {
@Override
public synchronized String format(final LogRecord record) {
StackTraceElement[] trace = new Throwable().getStackTrace();
String clickable = "(" + trace[ 7 ].getFileName() + ":" + trace[ 7 ].getLineNumber() + ") ";
/* Clickable text in Console. */
for( int i = 8; i < trace.length; i++ ) {
/* 0 - 6 is the logging trace, 7 - x is the trace until log method was called */
if( trace[ i ].getFileName() == null )
continue;
clickable = "(" + trace[ i ].getFileName() + ":" + trace[ i ].getLineNumber() + ") -> " + clickable;
}
final String time = "<" + extended.format( new Date( record.getMillis() ) ) + "> ";
StringBuilder level = new StringBuilder("[" + record.getLevel() + "] ");
while( level.length() < 15 ) /* extend for tabby display */
level.append(" ");
StringBuilder name = new StringBuilder(record.getLoggerName()).append(": ");
while( name.length() < 15 ) /* extend for tabby display */
name.append(" ");
String thread = Thread.currentThread().getName();
if( thread.length() > 18 ) /* trim if too long */
thread = thread.substring( 0, 16 ) + "...";
else {
StringBuilder sb = new StringBuilder(thread);
while( sb.length() < 18 ) /* extend for tabby display */
sb.append(" ");
thread = sb.insert( 0, "Thread " ).toString();
}
final String message = "\"" + record.getMessage() + "\" ";
return level + time + thread + name + clickable + message + "\n";
}
};
ch.setFormatter( formatter );
ch.setLevel( Level.ALL );
} catch( final SecurityException e ) {
e.printStackTrace();
}
return logger;
}
Notice this outputs to the console, you can change that, see http://docs.oracle.com/javase/1.4.2/docs/api/java/util/logging/Logger.html for more information on that.
Now, the following will probably do what you want. It will go through all causes of a Throwable and save it in a String. Note that this does not use StringBuilder
, so you can optimize by changing it.
Throwable e = ...
String detail = e.getClass().getName() + ": " + e.getMessage();
for( final StackTraceElement s : e.getStackTrace() )
detail += "\n\t" + s.toString();
while( ( e = e.getCause() ) != null ) {
detail += "\nCaused by: ";
for( final StackTraceElement s : e.getStackTrace() )
detail += "\n\t" + s.toString();
}
Regards,
Danyel
Upvotes: 1