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