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(); }