Revision: 8795
Author: [email protected]
Date: Wed Sep 15 17:53:58 2010
Log: Add server side deobfuscation of stack traces to RF Remote log handler

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

Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=8795

Added:
 /trunk/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java
Modified:
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
 /trunk/samples/dynatablerf/war/WEB-INF/web.xml
 /trunk/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java
 /trunk/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java
 /trunk/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
 /trunk/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
 /trunk/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
/trunk/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
 /trunk/user/src/com/google/gwt/requestfactory/server/Logging.java
/trunk/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
 /trunk/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
 /trunk/user/super/com/google/gwt/emul/java/util/logging/Level.java

=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java Wed Sep 15 17:53:58 2010
@@ -0,0 +1,148 @@
+/*
+ * 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.server;
+
+import com.google.gwt.logging.shared.SerializableLogRecord;
+import com.google.gwt.logging.shared.SerializableThrowable;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Deobfuscates stack traces on the server side. This class requires that you
+ * have turned on emulated stack traces and moved your symbolMap files to a
+ * place accessible by your server. More concretely, you must compile with the + * -extra command line option, copy the symbolMaps directory to somewhere your + * server side code has access to it, and then set the symbolMapsDirectory in
+ * this class through the constructor, or the setter method.
+ * For example, this variable could be set to "WEB-INF/classes/symbolMaps/"
+ * if you copied the symbolMaps directory to there.
+ *
+ * TODO(unnurg): Combine this code with similar code in JUnitHostImpl
+ */
+public class StackTraceDeobfuscator {
+
+  private static class SymbolMap extends HashMap<String, String> { }
+
+  // From JsniRef class, which is in gwt-dev and so can't be accessed here
+ // TODO(unnurg) once there is a place for shared code, move this to there.
+  private static Pattern JsniRefPattern =
+    Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?");
+
+  private String symbolMapsDirectory = "";
+
+  private Map<String, SymbolMap> symbolMaps =
+    new HashMap<String, SymbolMap>();
+
+  public StackTraceDeobfuscator(String symbolMapsDirectory) {
+    this.symbolMapsDirectory = symbolMapsDirectory;
+  }
+
+  public SerializableLogRecord deobfuscateLogRecord(
+      SerializableLogRecord slr) {
+    if (slr.getThrown() != null) {
+ slr.setThrown(deobfuscateThrowable(slr.getThrown(), slr.getStrongName()));
+    }
+    return slr;
+  }
+
+  public void setSymbolMapsDirectory(String dir) {
+    symbolMapsDirectory = dir;
+  }
+
+  private StackTraceElement[] deobfuscateStackTrace(
+      StackTraceElement[] st, String strongName) {
+    StackTraceElement[] newSt = new StackTraceElement[st.length];
+    for (int i = 0; i < st.length; i++) {
+      newSt[i] = resymbolize(st[i], strongName);
+    }
+    return newSt;
+  }
+
+  private SerializableThrowable deobfuscateThrowable(
+      SerializableThrowable t, String strongName) {
+    if (t.getStackTrace() != null) {
+ t.setStackTrace(deobfuscateStackTrace(t.getStackTrace(), strongName));
+    }
+    if (t.getCause() != null) {
+      t.setCause(deobfuscateThrowable(t.getCause(), strongName));
+    }
+    return t;
+  }
+
+  private SymbolMap loadSymbolMap(
+      String strongName) {
+    SymbolMap toReturn = symbolMaps.get(strongName);
+    if (toReturn != null) {
+      return toReturn;
+    }
+    toReturn = new SymbolMap();
+    String line;
+    String filename = symbolMapsDirectory + strongName + ".symbolMap";
+    try {
+      BufferedReader bin = new BufferedReader(new FileReader(filename));
+      while ((line = bin.readLine()) != null) {
+        if (line.charAt(0) == '#') {
+          continue;
+        }
+        int idx = line.indexOf(',');
+        toReturn.put(new String(line.substring(0, idx)),
+            line.substring(idx + 1));
+      }
+    } catch (IOException e) {
+      toReturn = null;
+    }
+
+    symbolMaps.put(strongName, toReturn);
+    return toReturn;
+  }
+
+  private String[] parse(String refString) {
+    Matcher matcher = JsniRefPattern.matcher(refString);
+    if (!matcher.matches()) {
+      return null;
+    }
+    String className = matcher.group(1);
+    String memberName = matcher.group(2);
+    String[] toReturn = new String[] {className, memberName};
+    return toReturn;
+  }
+
+  private StackTraceElement resymbolize(StackTraceElement ste,
+      String strongName) {
+    SymbolMap map = loadSymbolMap(strongName);
+    String symbolData = map == null ? null : map.get(ste.getMethodName());
+
+    if (symbolData != null) {
+      // jsniIdent, className, memberName, sourceUri, sourceLine
+      String[] parts = symbolData.split(",");
+      if (parts.length == 5) {
+        String[] ref = parse(
+            parts[0].substring(0, parts[0].lastIndexOf(')') + 1));
+        return new StackTraceElement(
+            ref[0], ref[1], ste.getFileName(), ste.getLineNumber());
+      }
+    }
+    // If anything goes wrong, just return the unobfuscated element
+    return ste;
+  }
+}
=======================================
--- /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml Mon Sep 13 15:18:55 2010 +++ /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml Wed Sep 15 17:53:58 2010
@@ -32,6 +32,12 @@
   <set-property name="gwt.logging.systemHandler" value="ENABLED" />
   <set-property name="gwt.logging.simpleRemoteHandler" value="DISABLED" />

+ <!-- Uncomment if you are enabling server side deobfuscation of StackTraces
+  <set-property name="compiler.emulatedStack" value="true" />
+ <set-configuration-property name="compiler.emulatedStack.recordLineNumbers" value="true" /> + <set-configuration-property name="compiler.emulatedStack.recordFileNames" value="true" />
+  -->
+
<entry-point class='com.google.gwt.sample.dynatablerf.client.DynaTableRf' />

<set-configuration-property name="CssResource.obfuscationPrefix" value="empty" />
=======================================
--- /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java Wed Sep 15 06:48:28 2010 +++ /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java Wed Sep 15 17:53:58 2010
@@ -44,7 +44,7 @@
   }

private static final Logger log = Logger.getLogger(DynaTableRf.class.getName());
-
+
   @UiField(provided = true)
   SummaryWidget calendar;

@@ -73,11 +73,10 @@
         public LoggingRequest getLoggingRequest() {
           return requests.loggingRequest();
         }
-      };
+    };
     Logger.getLogger("").addHandler(
         new RequestFactoryLogHandler(provider, Level.WARNING,
-            "WireActivityLogger"));
-
+            "WireActivityLogger", GWT.getPermutationStrongName()));
     FavoritesManager manager = new FavoritesManager(requests);
     PersonEditorWorkflow.register(eventBus, requests, manager);

=======================================
--- /trunk/samples/dynatablerf/war/WEB-INF/web.xml      Mon Sep 13 09:30:34 2010
+++ /trunk/samples/dynatablerf/war/WEB-INF/web.xml      Wed Sep 15 17:53:58 2010
@@ -9,6 +9,12 @@
   <servlet>
     <servlet-name>requestFactoryServlet</servlet-name>
<servlet-class>com.google.gwt.requestfactory.server.RequestFactoryServlet</servlet-class>
+    <init-param>
+      <param-name>symbolMapsDirectory</param-name>
+ <!-- You'll need to compile with -extras and move the symbolMaps directory + to this location if you want stack trace deobfuscation to work -->
+      <param-value>WEB-INF/classes/symbolMaps/</param-value>
+    </init-param>
   </servlet>

   <servlet-mapping>
=======================================
--- /trunk/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java Wed Sep 15 17:53:58 2010
@@ -49,6 +49,7 @@
     obj.put("level", getJsonString(slr.getLevel()));
     obj.put("loggerName", getJsonString(slr.getLoggerName()));
     obj.put("msg", getJsonString(slr.getMsg()));
+    obj.put("strongName", getJsonString(slr.getStrongName()));
     if (slr.getTimestamp() != null) {
       obj.put("timestamp", new JSONString(slr.getTimestamp().toString()));
     }
=======================================
--- /trunk/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java Wed Jun 30 08:49:02 2010 +++ /trunk/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java Wed Sep 15 17:53:58 2010
@@ -74,6 +74,7 @@
       // would lead to an infinite loop.
       return;
     }
-    service.logOnServer(new SerializableLogRecord(record), callback);
+    service.logOnServer(new SerializableLogRecord(
+        record, GWT.getPermutationStrongName()), callback);
   }
 }
=======================================
--- /trunk/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java Wed Sep 15 17:53:58 2010
@@ -40,10 +40,12 @@
       String level = slr.getString("level");
       String loggerName = slr.getString("loggerName");
       String msg = slr.getString("msg");
+      String strongName = slr.getString("strongName");
       long timestamp = Long.parseLong(slr.getString("timestamp"));
       SerializableThrowable thrown =
         serializableThrowableFromJson(slr.getString("thrown"));
- return new SerializableLogRecord(level, loggerName, msg, thrown, timestamp);
+      return new SerializableLogRecord(level, loggerName, msg, thrown,
+          timestamp, strongName);
     } catch (JSONException e) {
     }
     return null;
=======================================
--- /trunk/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java Wed Sep 15 17:53:58 2010
@@ -28,16 +28,23 @@
  * the wire directly.
  */
 public class SerializableLogRecord implements IsSerializable {
+ // If you add/remove a field here, be sure to update JsonLogRecordServerUtil
+  // and JsonLogRecordClientUtil as well.
   private String level;
   private String loggerName = "";
   private String msg;
+ // TODO(unnurg): if/when we ever start passing the strong name in all headers
+  // and we're able to access request headers in the Vega logging handler
+  // remove this strongName variable from here.
+  private String strongName = "";
   private SerializableThrowable thrown = null;
   private long timestamp;

   /**
    * Create a new SerializableLogRecord from a LogRecord.
    */
-  public SerializableLogRecord(LogRecord lr) {
+  public SerializableLogRecord(LogRecord lr, String strongName) {
+    this.strongName = strongName;
     level = lr.getLevel().toString();
     loggerName = lr.getLoggerName();
     msg = lr.getMessage();
@@ -48,12 +55,13 @@
   }

   public SerializableLogRecord(String level, String loggerName, String msg,
-      SerializableThrowable thrown, long timestamp) {
+      SerializableThrowable thrown, long timestamp, String strongName) {
     this.level = level;
     this.loggerName = loggerName;
     this.msg = msg;
     this.timestamp = timestamp;
     this.thrown = thrown;
+    this.strongName = strongName;
   }

   protected SerializableLogRecord() {
@@ -84,6 +92,10 @@
   public String getMsg() {
     return msg;
   }
+
+  public String getStrongName() {
+    return strongName;
+  }

   public SerializableThrowable getThrown() {
     return thrown;
@@ -92,4 +104,8 @@
   public Long getTimestamp() {
     return timestamp;
   }
-}
+
+  public void setThrown(SerializableThrowable t) {
+    thrown = t;
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/logging/shared/SerializableThrowable.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/logging/shared/SerializableThrowable.java Wed Sep 15 17:53:58 2010
@@ -28,24 +28,24 @@
   private SerializableThrowable cause = null;
   private String message;
   private StackTraceElement[] stackTrace;
+
+  public SerializableThrowable(String message, SerializableThrowable cause,
+      StackTraceElement[] stackTrace) {
+    this.message = message;
+    this.cause = cause;
+    this.stackTrace = stackTrace;
+  }

   /**
    * Create a new SerializableThrowable from a Throwable.
    */
   public SerializableThrowable(Throwable t) {
     message = t.getMessage();
-    if (t.getCause() != null) {
+    if (t.getCause() != null && t.getCause() != t) {
       cause = new SerializableThrowable(t.getCause());
     }
     stackTrace = t.getStackTrace();
   }
-
-  public SerializableThrowable(String message, SerializableThrowable cause,
-      StackTraceElement[] stackTrace) {
-    this.message = message;
-    this.cause = cause;
-    this.stackTrace = stackTrace;
-  }

   protected SerializableThrowable() {
     // for serialization
@@ -54,7 +54,7 @@
   public SerializableThrowable getCause() {
     return cause;
   }
-
+
   public String getMessage() {
     return message;
   }
@@ -62,7 +62,7 @@
   public StackTraceElement[] getStackTrace() {
     return stackTrace;
   }
-
+
   /**
    * Create a new Throwable from this SerializableThrowable.
    */
@@ -76,4 +76,12 @@
     t.setStackTrace(stackTrace);
     return t;
   }
-}
+
+  public void setCause(SerializableThrowable c) {
+    cause = c;
+  }
+
+  public void setStackTrace(StackTraceElement[] st) {
+    stackTrace = st;
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java Wed Sep 15 17:53:58 2010
@@ -53,9 +53,14 @@
   private static Logger logger =
     Logger.getLogger(RequestFactoryLogHandler.class.getName());

+  // A separate logger for wire activity, which does not get logged
+  // by the remote log handler, so we avoid infinite loops.
+ private static Logger wireLogger = Logger.getLogger("WireActivityLogger");
+
   private boolean closed;
   private LoggingRequestProvider requestProvider;
   private String ignoredLoggerSubstring;
+  private String strongName;

   /**
    * Since records from this handler go accross the wire, it should only be
@@ -67,9 +72,10 @@
    * infinite loop would occur.
    */
   public RequestFactoryLogHandler(LoggingRequestProvider requestProvider,
-      Level level, String ignoredLoggerSubstring) {
+      Level level, String ignoredLoggerSubstring, String strongName) {
     this.requestProvider = requestProvider;
     this.ignoredLoggerSubstring = ignoredLoggerSubstring;
+    this.strongName = strongName;
     closed = false;
     setLevel(level);
   }
@@ -92,16 +98,14 @@
     if (record.getLoggerName().contains(ignoredLoggerSubstring)) {
       return;
     }
-    SerializableLogRecord slr = new SerializableLogRecord(record);
+    SerializableLogRecord slr =
+      new SerializableLogRecord(record, strongName);
     String json = JsonLogRecordClientUtil.serializableLogRecordAsJson(slr);
     requestProvider.getLoggingRequest().logMessage(json).fire(
         new Receiver<Boolean>() {
           @Override
public void onSuccess(Boolean response, Set<SyncResult> syncResults) {
             if (!response) {
- // A separate logger for wire activity, which does not get logged
-              // by the remote log handler, so we avoid infinite loops.
-              Logger wireLogger = Logger.getLogger("WireActivityLogger");
               wireLogger.severe("Remote Logging failed to parse JSON");
             }
           }
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/server/Logging.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/server/Logging.java Wed Sep 15 17:53:58 2010
@@ -17,6 +17,7 @@
 package com.google.gwt.requestfactory.server;

 import com.google.gwt.logging.server.JsonLogRecordServerUtil;
+import com.google.gwt.logging.server.StackTraceDeobfuscator;
 import com.google.gwt.logging.shared.SerializableLogRecord;

 import java.util.logging.LogRecord;
@@ -25,24 +26,40 @@
 /**
  * Server side object that handles log messages sent by
  * {...@link RequestFactoryLogHandler}.
+ *
+ * TODO(unnurg): Before the end of Sept 2010, combine this class intelligently
+ * with SimpleRemoteLogHandler so they share functionality and patterns.
  */
 public class Logging {
-  private static Logger logger = Logger.getLogger(Logging.class.getName());
-
+
+  private static StackTraceDeobfuscator deobfuscator =
+    new StackTraceDeobfuscator("");
+
   public static Boolean logMessage(String serializedLogRecordString) {
     SerializableLogRecord slr =
       JsonLogRecordServerUtil.serializableLogRecordFromJson(
           serializedLogRecordString);
+    slr = deobfuscator.deobfuscateLogRecord(slr);
     LogRecord lr = slr.getLogRecord();
     if (lr == null) {
       return false;
     }
+    Logger logger = Logger.getLogger(lr.getLoggerName());
     logger.log(lr);
     return true;
   }
+
+  /**
+   * This function is only for server side use which is why it's not in the
+   * LoggingRequest interface.
+   */
+  public static void setSymbolMapsDirectory(String dir) {
+    deobfuscator.setSymbolMapsDirectory(dir);
+  }

   private Long id = 0L;
-  private Integer version = 0;
+
+  private Integer version = 0;

   public Long getId() {
     return this.id;
@@ -51,11 +68,11 @@
   public Integer getVersion() {
     return this.version;
   }
-
+
   public void setId(Long id) {
     this.id = id;
   }
-
+
   public void setVersion(Integer version) {
     this.version = version;
   }
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java Wed Sep 15 15:23:04 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java Wed Sep 15 17:53:58 2010
@@ -127,5 +127,11 @@
     if (userInfoClass != null) {
       UserInformation.setUserInformationImplClass(userInfoClass);
     }
+
+    String symbolMapsDirectory =
+      getServletConfig().getInitParameter("symbolMapsDirectory");
+    if (symbolMapsDirectory != null) {
+      Logging.setSymbolMapsDirectory(symbolMapsDirectory);
+    }
   }
 }
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java Tue Sep 14 11:43:19 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java Wed Sep 15 17:53:58 2010
@@ -28,5 +28,5 @@
   // TODO(unnurg): Pass a SerializableLogRecord here rather than it's
   // serialized string.
   RequestObject<Boolean> logMessage(String serializedLogRecordString);
-
-}
+
+}
=======================================
--- /trunk/user/super/com/google/gwt/emul/java/util/logging/Level.java Fri Jun 18 10:49:21 2010 +++ /trunk/user/super/com/google/gwt/emul/java/util/logging/Level.java Wed Sep 15 17:53:58 2010
@@ -50,7 +50,7 @@
     impl.setName(name);
     impl.setValue(value);
   }
-
+
   public String getName() {
     return impl.getName();
   }

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to