Add stack trace display to the rest of the log handlers in dev mode

Review at http://gwt-code-reviews.appspot.com/861801

Review by: fredsa@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8773 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/logging/client/ConsoleLogHandler.java b/user/src/com/google/gwt/logging/client/ConsoleLogHandler.java
index c8a1853..e522f79 100644
--- a/user/src/com/google/gwt/logging/client/ConsoleLogHandler.java
+++ b/user/src/com/google/gwt/logging/client/ConsoleLogHandler.java
@@ -29,7 +29,7 @@
 public class ConsoleLogHandler extends Handler {
 
   public ConsoleLogHandler() {
-    setFormatter(new TextLogFormatter());
+    setFormatter(new TextLogFormatter(true));
     setLevel(Level.ALL);
   }
   
diff --git a/user/src/com/google/gwt/logging/client/DevelopmentModeLogHandler.java b/user/src/com/google/gwt/logging/client/DevelopmentModeLogHandler.java
index 613ab04..c180499 100644
--- a/user/src/com/google/gwt/logging/client/DevelopmentModeLogHandler.java
+++ b/user/src/com/google/gwt/logging/client/DevelopmentModeLogHandler.java
@@ -29,7 +29,7 @@
 public class DevelopmentModeLogHandler extends Handler {
   
   public DevelopmentModeLogHandler() {
-    setFormatter(new TextLogFormatter());
+    setFormatter(new TextLogFormatter(false));
     setLevel(Level.ALL);
   }
 
diff --git a/user/src/com/google/gwt/logging/client/FirebugLogHandler.java b/user/src/com/google/gwt/logging/client/FirebugLogHandler.java
index 9e84c51..7b5da6f 100644
--- a/user/src/com/google/gwt/logging/client/FirebugLogHandler.java
+++ b/user/src/com/google/gwt/logging/client/FirebugLogHandler.java
@@ -28,7 +28,7 @@
 public class FirebugLogHandler extends Handler {
   
   public FirebugLogHandler() {
-    setFormatter(new TextLogFormatter());
+    setFormatter(new TextLogFormatter(true));
     setLevel(Level.ALL);  
   }
 
diff --git a/user/src/com/google/gwt/logging/client/HasWidgetsLogHandler.java b/user/src/com/google/gwt/logging/client/HasWidgetsLogHandler.java
index 7e6d062..f2fec42 100644
--- a/user/src/com/google/gwt/logging/client/HasWidgetsLogHandler.java
+++ b/user/src/com/google/gwt/logging/client/HasWidgetsLogHandler.java
@@ -37,7 +37,7 @@
 
   public HasWidgetsLogHandler(HasWidgets container) {
     this.widgetContainer = container;
-    setFormatter(new HtmlLogFormatter());
+    setFormatter(new HtmlLogFormatter(true));
     setLevel(Level.ALL);
   }
 
diff --git a/user/src/com/google/gwt/logging/client/HtmlLogFormatter.java b/user/src/com/google/gwt/logging/client/HtmlLogFormatter.java
index b6012b5..62f222f 100644
--- a/user/src/com/google/gwt/logging/client/HtmlLogFormatter.java
+++ b/user/src/com/google/gwt/logging/client/HtmlLogFormatter.java
@@ -16,8 +16,9 @@
 
 package com.google.gwt.logging.client;
 
+import com.google.gwt.logging.impl.FormatterImpl;
+
 import java.util.Date;
-import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 
@@ -26,13 +27,25 @@
  * and GetHtmlSuffix methods rather than format to ensure that the message
  * is properly escaped.
  */
-public class HtmlLogFormatter extends Formatter {
+public class HtmlLogFormatter extends FormatterImpl {
+  private static String newline = "__GWT_LOG_FORMATTER_BR__";
+  private boolean showStackTraces;
+  
+  public HtmlLogFormatter(boolean showStackTraces) {
+    this.showStackTraces = showStackTraces;
+  }
 
   // TODO(unnurg): Handle the outputting of Throwables.
   @Override
   public String format(LogRecord event) {
     StringBuilder html = new StringBuilder(getHtmlPrefix(event));
-    html.append(getEscapedMessage(event));
+    html.append(getHtmlPrefix(event));
+    html.append(getRecordInfo(event, " "));
+    html.append(getEscaped(event.getMessage()));
+    if (showStackTraces) {
+      html.append(getEscaped(getStackTraceAsString(
+          event.getThrown(), newline, "   ")));
+    }
     html.append(getHtmlSuffix(event));
     return html.toString();
   }
@@ -44,17 +57,10 @@
     prefix.append(getColor(event.getLevel().intValue()));
     prefix.append("'>");
     prefix.append("<code>");
-    prefix.append(date.toString());
-    prefix.append(" ");
-    prefix.append(event.getLoggerName());
-    prefix.append(" ");
-    prefix.append(event.getLevel().getName());
-    prefix.append(": ");
     return prefix.toString();
   }
   
   protected String getHtmlSuffix(LogRecord event) {
-    // TODO(unnurg): output throwables correctly
     return "</code></span>";
   }
   
@@ -86,11 +92,11 @@
     return "#000"; // black
   }
 
-  // TODO(unnurg): There must be a cleaner way to do this...
-  private String getEscapedMessage(LogRecord event) {
-    String text = event.getMessage();
+  private String getEscaped(String text) {
     text = text.replaceAll("<", "&lt;");
     text = text.replaceAll(">", "&gt;");
+    // but allow the line breaks that we put in ourselves
+    text = text.replaceAll(newline, "<br>");
     return text;
   }
 
diff --git a/user/src/com/google/gwt/logging/client/SystemLogHandler.java b/user/src/com/google/gwt/logging/client/SystemLogHandler.java
index 428bbb9..12aeac8 100644
--- a/user/src/com/google/gwt/logging/client/SystemLogHandler.java
+++ b/user/src/com/google/gwt/logging/client/SystemLogHandler.java
@@ -28,7 +28,7 @@
 public class SystemLogHandler extends Handler {
 
   public SystemLogHandler() {
-    setFormatter(new TextLogFormatter());
+    setFormatter(new TextLogFormatter(true));
     setLevel(Level.ALL);
   }
   
diff --git a/user/src/com/google/gwt/logging/client/TextLogFormatter.java b/user/src/com/google/gwt/logging/client/TextLogFormatter.java
index 1c01125..b001f7b 100644
--- a/user/src/com/google/gwt/logging/client/TextLogFormatter.java
+++ b/user/src/com/google/gwt/logging/client/TextLogFormatter.java
@@ -16,28 +16,27 @@
 
 package com.google.gwt.logging.client;
 
-import java.util.Date;
-import java.util.logging.Formatter;
+import com.google.gwt.logging.impl.FormatterImpl;
+
 import java.util.logging.LogRecord;
 
 /**
  * Formats LogRecords into 2 lines of text.
  */
-public class TextLogFormatter extends Formatter {
+public class TextLogFormatter extends FormatterImpl {
+  private boolean showStackTraces;
+  
+  public TextLogFormatter(boolean showStackTraces) {
+    this.showStackTraces = showStackTraces;
+  }
 
   @Override
   public String format(LogRecord event) {
-    Date date = new Date(event.getMillis());
     StringBuilder message = new StringBuilder();
-    message.append(date.toString());
-    message.append(" ");
-    message.append(event.getLoggerName());
-    message.append("\n");
-    message.append(event.getLevel().getName());
-    message.append(": ");
+    message.append(getRecordInfo(event, "\n"));
     message.append(event.getMessage());
-    if (event.getThrown() != null) {
-      // TODO(unnurg): output throwables correctly
+    if (showStackTraces) {
+      message.append(getStackTraceAsString(event.getThrown(), "\n", "\t"));
     }
     return message.toString();
   }
diff --git a/user/src/com/google/gwt/logging/impl/FormatterImpl.java b/user/src/com/google/gwt/logging/impl/FormatterImpl.java
new file mode 100644
index 0000000..051f6f7
--- /dev/null
+++ b/user/src/com/google/gwt/logging/impl/FormatterImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.impl;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * Base class for Formatters - provides common functionality
+ */
+public abstract class FormatterImpl extends Formatter {
+
+  /**
+   * Note that this format is likely to change in the future.
+   * Outputs the meta information in the log record in the following format:
+   * <pre>Date LoggerName\nLEVEL:</pre>
+   * Most formatters will append the message right after the colon
+   */
+  protected String getRecordInfo(LogRecord event, String newline) {
+    Date date = new Date(event.getMillis());
+    StringBuilder s = new StringBuilder();
+    s.append(date.toString());
+    s.append(" ");
+    s.append(event.getLoggerName());
+    s.append(newline);
+    s.append(event.getLevel().getName());
+    s.append(": ");
+    return s.toString();
+  }
+  
+  // This method is borrowed from AbstractTreeLogger. 
+  // TODO(unnurg): once there is a clear place where code used by gwt-dev and
+  // gwt-user can live, move this function there.
+  protected String getStackTraceAsString(Throwable e, String newline,
+      String indent) {
+    if (e == null) {
+      return "";
+    }
+    // For each cause, print the requested number of entries of its stack
+    // trace, being careful to avoid getting stuck in an infinite loop.
+    //
+    StringBuffer s = new StringBuffer(newline);
+    Throwable currentCause = e;
+    String causedBy = "";
+    HashSet<Throwable> seenCauses = new HashSet<Throwable>();
+    while (currentCause != null && !seenCauses.contains(currentCause)) {
+      seenCauses.add(currentCause);
+      s.append(causedBy);
+      causedBy = newline + "Caused by: "; // after 1st, all say "caused by"
+      s.append(currentCause.getClass().getName());
+      s.append(": " + currentCause.getMessage());
+      StackTraceElement[] stackElems = currentCause.getStackTrace();
+      if (stackElems != null) {
+        for (int i = 0; i < stackElems.length; ++i) {
+          s.append(newline + indent + "at ");
+          s.append(stackElems[i].toString());
+        }
+      }
+
+      currentCause = currentCause.getCause();
+    }
+    return s.toString();
+  }
+
+}