为什么Tomcat无法显示实际的堆栈跟踪?

5
使用 GWT,我已经将我的服务器部署到 Tomcat 上。这个工作正常,但当 GWT 抛出异常时,一个弹出窗口会显示客户端异常的堆栈跟踪。
在开发模式下,这个功能正常。但在 Tomcat 中,我得到了下面的堆栈跟踪。
为什么会出现这种情况?如何解决?
Unknown.Le(StackTraceCreator.java:168)
Unknown.Jd(StackTraceCreator.java:421)
Unknown.NT(Exception_FieldSerializer.java:16)
Unknown.g1(SerializerBase.java:55)
Unknown.b1(SerializerBase.java:112)
Unknown.D$(AbstractSerializationStreamReader.java:119)
Unknown.uAc(CustomException_FieldSerializer.java:39)
Unknown.uBc(ServerSideException_FieldSerializer.java:12)
Unknown.f1(SerializerBase.java:46)
Unknown._0(SerializerBase.java:92)
Unknown.D$(AbstractSerializationStreamReader.java:119)
Unknown.B_(RequestCallbackAdapter.java:216)
Unknown._o(Request.java:287)
使用@Christian Kuetbach的答案后,我现在得到的是:

Unknown.com_google_gwt_core_client_impl_StackTraceCreator$CollectorEmulated_$fillInStackTrace__Lcom_google_gwt_core_client_impl_StackTraceCreator$CollectorEmulated_2Ljava_lang_Throwable_2V(StackTraceCreator.java:168) Unknown.java_lang_Throwable_Throwable__Ljava_lang_String_2Ljava_lang_Throwable_2V(StackTraceCreator.java:421) Unknown.com_google_gwt_user_client_rpc_StatusCodeException_StatusCodeException__ILjava_lang_String_2V(StatusCodeException.java:35) Unknown.com_google_gwt_user_client_rpc_impl_RequestCallbackAdapter_$onResponseReceived__Lcom_google_gwt_user_client_rpc_impl_RequestCallbackAdapter_2Lcom_google_gwt_http_client_Request_2Lcom_google_gwt_http_client_Response_2V(RequestCallbackAdapter.java:209) Unknown.com_google_gwt_http_client_Request_$fireOnResponseReceived__Lcom_google_gwt_http_client_Request_2Lcom_google_gwt_http_client_RequestCallback_2V(Request.java:287) Unknown.com_google_gwt_http_client_RequestBuilder $1_onReadyStateChange__Lcom_google_gwt_xhr_client_XMLHttpRequest_2V(RequestBuilder.java:395)Unknown.anonymous(XMLHttpRequest.java:287)

请帮忙!

3个回答

5

在GWT文档中找到所有设置反混淆日志所需的信息有些困难,因此这里提供简短版本:

在您的模块文件(.gwt.xml)中添加以下内容:

<inherits name="com.google.gwt.logging.Logging"/>
<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
<set-property name="compiler.stackMode" value="emulated" />
<set-configuration-property name="compiler.emulatedStack.recordLineNumbers" 
     value="true" />

在客户端使用类似以下的东西:
import java.util.logging.Logger;

private static Logger rootLogger = Logger.getLogger("");
...
rootLogger.log(Level.SEVERE, "My message", e);

在客户端不需要创建RemoteLoggingServiceAsync实例 - 它会被日志记录器自动使用,因为我们指定了<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />

在服务器端,配置RemoteLoggingServiceImpl。您将需要告诉它在哪里找到符号映射 (symbolMaps),这些符号映射将在使用 GWT 编译器参数 -extra /path/to/myExtraDir 进行编译时生成。我个人使用的方法是覆盖RemoteLoggingServiceImpl,以允许从 web.xml 的<init-param>中指定目录[*]

package mypackage.server;

public class ConfigurableRemoteLoggingServiceImpl extends RemoteLoggingServiceImpl {

  @Override
  public void init(final ServletConfig config) throws ServletException {
    super.init(config);

    final String symbolMapsDirectory = 
        config.getInitParameter("symbolMapsDirectory");
    setSymbolMapsDirectory(symbolMapsDirectory);
  }
}

web.xml 中进行注册,例如:
<servlet>
    <servlet-name>remoteLogging</servlet-name>
    <servlet-class>mypackage.server.ConfigurableRemoteLoggingServiceImpl</servlet-class>

  <init-param>
    <param-name>symbolMapsDirectory</param-name>
    <param-value>/path/to/myExtraDir/mymodulename/symbolMaps</param-value>
  </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>remoteLogging</servlet-name>
    <url-pattern>/mymodulename/remote_logging</url-pattern>
</servlet-mapping>

请将/path/to/myExtraDirmymodulenamemypackage替换为您自己的值,并不要忘记使用-extra参数调用GWT编译器(请注意,您不必使用-style PRETTY或DETAILED,它也可以与OBF一起工作)。保留所有生成的symbolMaps:没有它们,反混淆将无法正常工作。由于每个新版本会自动获得唯一名称,因此在构建时可以将它们全部收集到一个安全的中央位置。

[*] 我真的非常想知道为什么RemoteLoggingServiceImpl本身没有实现这个!


“/path/to/myExtraDir” 应该如何编写?是相对路径、绝对路径还是其他什么? - Adel Boutros
路径被评估为简单的 new File("/path/to/myExtraDir"),请参见 StackTraceDeobfuscator.setSymbolMapsDirectory(String symbolMapsDirectory),因此您可以使用任何与通常的 java.io.File(String) 构造函数兼容的内容(例如,如果在 Unix 上以正斜杠开头,则是绝对路径,...)。 - Chris Lercher

5

为什么会出现这种情况?

正如Christian Kuetbach所说,这是在DevMode下运行(代码在Java中执行)和生产模式下运行(代码已编译为JavaScript并进行了优化,包括重命名类和方法)之间的区别。

如何解决这个问题?

你不需要解决它。一般来说,向用户显示堆栈跟踪不是一个好主意。更好的方法是通过将其发送到服务器来记录异常(例如使用java.util.logging进行日志记录,以及使用SimpleRemoteLogHandler将日志发送到服务器,在那里使用java.util.logging进行日志记录)。

虽然有解混淆堆栈跟踪的方法,但是RemoteLoggingServiceImpl servlet可以自动配置以执行此操作。
有关详细信息,请参见http://code.google.com/p/google-web-toolkit/wiki/WebModeExceptions

如果您不能或不想使用远程日志记录,则可以“手动”解密堆栈跟踪:查看WEB-INF/deploy(默认位置,可以通过向GWT编译器传递-deploy来更改)中与排列(与浏览器加载的*.cache.*文件具有相同名称)对应的文件,它将告诉您Le方法来自哪个Java方法。
但是您已经拥有源文件名和行号,所以您实际上不需要它,对吧?

有时候,异常可能是由于客户端的错误操作而导致的。因此,我不想记录每个异常,我希望让客户端决定是否想要发送、保存或无动作处理它。 - Adel Boutros
StackTraceCreator并不是源文件名,也不是行号。 - Adel Boutros
@Adel:"笨拙"的客户端错误应该理想地产生清晰的验证消息。其他情况并不真正是客户端错误,而是软件中的漏洞 - 应该得到修复。在这种情况下,可以显示一个不透明的消息,例如"发生了错误。您是否要发送错误报告?" - Chris Lercher
@ChrisLercher “发生了错误。您要发送错误报告吗?” 这是我已经做的。问题在于发送的错误包含了难以理解的堆栈跟踪,这使得发送无用,因为开发人员将无法理解日志。 - Adel Boutros
@Adel:你可以按照Thomas的建议在服务器端对其进行反混淆。 - Chris Lercher
显示剩余2条评论

1

这个功能按预期工作,因为优化将删除方法和类名。

您可以编译为PRETTY或DETAILED,以获得更易读的堆栈跟踪。

还有模拟堆栈跟踪的可能性。

<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"/> 

这是不适合在生产环境中使用的不好的想法,因为它会增加您的JavaScript文件的大小。

更新: 我看到了Exception_FieldSerializer异常。

您是否尝试序列化某些不可序列化的东西?

没有默认构造函数的类不可序列化,而不在客户端或共享包中的类也是如此。如果您试图序列化异常,则可能是问题所在。


方法名称和类名? - Adel Boutros
请查看我的编辑。我已经尝试了使用提到的属性pretty和Detailed,现在我得到了新的堆栈跟踪。 - Adel Boutros
所有扩展异常的类都是可序列化的,我发送的所有异常也都是可序列化的。 - Adel Boutros
当然可以,但在GWT项目中有共享/客户端/服务器包。我不确定来自客户端或共享包之外的类是否会被序列化,即使它们是可序列化的。 - Christian Kuetbach
实际上我的异常类(CustomException)位于共享包中。 - Adel Boutros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接