Lesbare Stacktraces auf einer JSF-Fehlerseite

Der JavaEE-Stack beherbergt einige wunderbare APIs (JSF, EJB, JPA, um die wichtigsten
zu nennen), die einem das Leben in der Regel 😉 einfacher machen und es dem geneigten
Entwickler erlauben, auf einem hohen Abstraktionsgrad zu programmieren.

Der Entwickler kennt aber auch folgendes Problem, wenn er sich auf diese APIs einlÀsst:
Stacktraces from Hell (123.000! Suchtreffer in Google, Stand von heute, dem 21.07.2012).

Stacktrace from Hell

Im Fehlerfall hat man einen Stacktrace, der locker mal mehrere hundert Zeilen umfasst.
Irgendwo im Stacktrace findet sich dann – ganz versteckt – die interessante Stelle:
der Verweis auf die eigene Klasse, die den Fehler verursacht hat.

Um diese Stelle besser zu finden, zeige ich mir auf meiner Error-Page (eine XHTML-Exception-
Seite) die betreffenden Stellen einfach fett an, sprich: der Verweis auf
meine Klasse innerhalb des Stacktraces sieht dann so aus:

Stacktrace

Wie funktioniert’s?

Um in HTML fett zu drucken, nutzen wir das Tag <b>Text</b>.
Wenn wir innerhalb eines Stacktraces unser Package fettgedruckt sehen wollen, genĂŒgt es,
den Stacktrace mit einem regulÀren Ausdruck zu bearbeiten:

stacktrace.replaceAll("(de.project.triona.*)", "$1");

Und schon wird aus der Zeile

at java.lang.reflect.Method.invoke()
at de.project.triona.MyClass.method

ein

at java.lang.reflect.Method.invoke()
at de.project.triona.MyClass.method. // Diese Zeile ist fett gedruckt

Technisch sieht das so aus:

Im Webdeskriptor web.xml definiere ich die Fehlerseite:

    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/errorException.xhtml</location>
    </error-page>

Die XHTML-Seite errorException.xhtml hat nur einen relevanten Befehl:

<h:outputText escape="false" value="#{errorView.stackTrace}"/>

Die Klasse ErrorView (eine JSF-ManagedBean) macht die Formatierung
mittels des o.a. regulÀren Ausdruck und sieht wie folgt aus:

@ManagedBean
@RequestScoped
public class ErrorView {

  public String getStackTrace() {
    Map requestMap = FacesUtil.getExternalContext().getRequestMap();
    Throwable ex = (Throwable) requestMap.get("javax.servlet.error.exception");

    // Create a writer for keeping the stacktrace of the exception
    StringWriter writer = new StringWriter();
    PrintWriter pw = new PrintWriter(writer);

    // Fill the stack trace into the write
    fillStackTrace(ex, pw);
    String result = writer.toString();
    //result = result.replace("\n", "\n;<br/>");
    return result.replaceAll("(de.ard.hr.zonk.*)", "$1");
  }

  private void fillStackTrace(Throwable ex, PrintWriter pw) {
    if (null == ex) {
      return;
    }

    ex.printStackTrace(pw);

    // The first time fillStackTrace is called it will always
    // be a ServletException
    if (ex instanceof ServletException) {
      Throwable cause = ((ServletException) ex).getRootCause();
      if (null != cause) {
        pw.println("Root Cause:");
        fillStackTrace(cause, pw);
      }
    } else {
      // Embedded cause inside the ServletException
      Throwable cause = ex.getCause();

      if (null != cause) {
        pw.println("Cause:");
        fillStackTrace(cause, pw);
      }
    }
  }
}

Viel Spaß mit dieser Lösung.