Add server side deobfuscation of stack traces to RF Remote log handler
Review at http://gwt-code-reviews.appspot.com/867802
Review by: jat@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8795 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
index d7f348c..932bfe6 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
@@ -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" />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
index 9d3fc32..81f4c07 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
@@ -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);
diff --git a/samples/dynatablerf/war/WEB-INF/web.xml b/samples/dynatablerf/war/WEB-INF/web.xml
index be13961..b3c0fdd 100644
--- a/samples/dynatablerf/war/WEB-INF/web.xml
+++ b/samples/dynatablerf/war/WEB-INF/web.xml
@@ -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>
diff --git a/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java b/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java
index b210533..fdaa43f 100644
--- a/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java
+++ b/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java
@@ -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()));
}
diff --git a/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java b/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java
index 8053c2d..356a34f 100644
--- a/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java
+++ b/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java
@@ -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);
}
}
diff --git a/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java b/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
index 2173ddf..8f1d1c9 100644
--- a/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
+++ b/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
@@ -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;
diff --git a/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java b/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java
new file mode 100644
index 0000000..753525c
--- /dev/null
+++ b/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java
@@ -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;
+ }
+}
diff --git a/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java b/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
index 92816f0..529af2d 100644
--- a/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
+++ b/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
@@ -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() {
@@ -85,6 +93,10 @@
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;
+ }
}
diff --git a/user/src/com/google/gwt/logging/shared/SerializableThrowable.java b/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
index ebbe381..7c06259 100644
--- a/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
+++ b/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
@@ -29,17 +29,6 @@
private String message;
private StackTraceElement[] stackTrace;
- /**
- * Create a new SerializableThrowable from a Throwable.
- */
- public SerializableThrowable(Throwable t) {
- message = t.getMessage();
- if (t.getCause() != null) {
- cause = new SerializableThrowable(t.getCause());
- }
- stackTrace = t.getStackTrace();
- }
-
public SerializableThrowable(String message, SerializableThrowable cause,
StackTraceElement[] stackTrace) {
this.message = message;
@@ -47,6 +36,17 @@
this.stackTrace = stackTrace;
}
+ /**
+ * Create a new SerializableThrowable from a Throwable.
+ */
+ public SerializableThrowable(Throwable t) {
+ message = t.getMessage();
+ if (t.getCause() != null && t.getCause() != t) {
+ cause = new SerializableThrowable(t.getCause());
+ }
+ stackTrace = t.getStackTrace();
+ }
+
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;
+ }
}
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
index 7ae3819..2f964e5 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
@@ -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");
}
}
diff --git a/user/src/com/google/gwt/requestfactory/server/Logging.java b/user/src/com/google/gwt/requestfactory/server/Logging.java
index fff5ebd..59d012d 100644
--- a/user/src/com/google/gwt/requestfactory/server/Logging.java
+++ b/user/src/com/google/gwt/requestfactory/server/Logging.java
@@ -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;
}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index 185c36e..f3592f6 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -127,5 +127,11 @@
if (userInfoClass != null) {
UserInformation.setUserInformationImplClass(userInfoClass);
}
+
+ String symbolMapsDirectory =
+ getServletConfig().getInitParameter("symbolMapsDirectory");
+ if (symbolMapsDirectory != null) {
+ Logging.setSymbolMapsDirectory(symbolMapsDirectory);
+ }
}
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
index cf07757..6ddd7c3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
@@ -28,5 +28,5 @@
// TODO(unnurg): Pass a SerializableLogRecord here rather than it's
// serialized string.
RequestObject<Boolean> logMessage(String serializedLogRecordString);
-
+
}
diff --git a/user/super/com/google/gwt/emul/java/util/logging/Level.java b/user/super/com/google/gwt/emul/java/util/logging/Level.java
index 5129d33..305d8e6 100644
--- a/user/super/com/google/gwt/emul/java/util/logging/Level.java
+++ b/user/super/com/google/gwt/emul/java/util/logging/Level.java
@@ -50,7 +50,7 @@
impl.setName(name);
impl.setValue(value);
}
-
+
public String getName() {
return impl.getName();
}