As a quick note, I just got a little bit better about how to log stack traces when writing Java or Scala code. This tutorial shows the best way I know to log exceptions. I’ll use Scala code here, but it converts easily to Java.
In Scala I used to get the text from a stack trace and then log it like this:
// this works, but it's not too useful/readable logger.error(exception.getStackTrace.mkString("\n"))
In that code, getStackTrace
returns a sequence, which I convert to a String
before printing it.
The best way to format and log stack traces
But recently I learned the following technique, which does a much better job of keeping the formatting when getting the text from a stack trace, and then writing it to a file with a logger:
// scala val sw = new StringWriter exception.printStackTrace(new PrintWriter(sw)) logger.error(sw.toString)
I wrote that code in Scala, but as you can see, it converts to Java very easily. In fact, here’s the Java version of the code, which I just used to log exceptions in Android:
// java StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); Log.e(TAG, sw.toString());
This new approach works really well at keeping the stack trace formatting, as I’ll show in the next example.
As I note at the end of this article, you don’t need to use this technique when writing to STDERR. I just use this approach when creating a string to work with a logger.
A stack trace example program
The output from this new approach is much more readable, as I verified with this little test program:
package com.devdaily.sarah.tests import scala.util.{Try, Success, Failure} import java.io._ object TrySuccessFailure extends App { badAdder(3) match { case Success(i) => println(s"success, i = $i") case Failure(t) => // this works, but it's not too useful/readable //println(t.getStackTrace.mkString("\n")) // this works much better val sw = new StringWriter t.printStackTrace(new PrintWriter(sw)) println(sw.toString) } /** * This method returns a `Success[Int]` if everything goes well, * otherwise it throws an exception wrapped in a `Failure`. */ def badAdder(a: Int): Try[Int] = { Try({ val b = a + 1 if (b == 3) b else { val ioe = new IOException("Boom!") throw new AlsException("Bummer!", ioe) } }) } class AlsException(s: String, e: Exception) extends Exception(s: String, e: Exception) }
In that code I intentionally wrap an IOException
inside a custom exception, which I then throw from my method.
The StringWriter
code prints the following output, which is very readable as stack traces go:
com.devdaily.sarah.tests.TrySuccessFailure$AlsException: Bummer! at com.devdaily.sarah.tests.TrySuccessFailure$$anonfun$badAdder$1.apply$mcI$sp(TrySuccessFailure.scala:31) at com.devdaily.sarah.tests.TrySuccessFailure$$anonfun$badAdder$1.apply(TrySuccessFailure.scala:27) at com.devdaily.sarah.tests.TrySuccessFailure$$anonfun$badAdder$1.apply(TrySuccessFailure.scala:27) at scala.util.Try$.apply(Try.scala:161) at com.devdaily.sarah.tests.TrySuccessFailure$.badAdder(TrySuccessFailure.scala:27) at com.devdaily.sarah.tests.TrySuccessFailure$delayedInit$body.apply(TrySuccessFailure.scala:8) at scala.Function0$class.apply$mcV$sp(Function0.scala:40) at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) at scala.App$$anonfun$main$1.apply(App.scala:71) at scala.App$$anonfun$main$1.apply(App.scala:71) at scala.collection.immutable.List.foreach(List.scala:318) at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32) at scala.App$class.main(App.scala:71) at com.devdaily.sarah.tests.TrySuccessFailure$.main(TrySuccessFailure.scala:6) at com.devdaily.sarah.tests.TrySuccessFailure.main(TrySuccessFailure.scala) Caused by: java.io.IOException: Boom! at com.devdaily.sarah.tests.TrySuccessFailure$$anonfun$badAdder$1.apply$mcI$sp(TrySuccessFailure.scala:30)
Summary
To be clear, you don’t need this approach when using things like System.out.println
or System.err.println
; you can just use e.printStackTrace
there, which prints to STDERR. But when you want to write to a logger, or otherwise convert a stack trace to a string, the second approach works very well. Here’s the approach encapsulated in a Scala method:
def getStackTraceAsString(t: Throwable) = { val sw = new StringWriter t.printStackTrace(new PrintWriter(sw)) sw.toString }