【问题标题】:Serialize java.lang.Throwable along with stack trace, causes chain and related stack traces序列化 java.lang.Throwable 以及堆栈跟踪,导致链和相关堆栈跟踪
【发布时间】:2014-03-29 18:19:24
【问题描述】:

我正在编写 GWT 应用程序,我需要发送 java.lang.Throwable 的实例(以及它的 原因 和所有堆栈跟踪)使用 GWT RPC,它使用标准 Java 序列化机制(就我而言)。

问题是当我通过以下示例异常来自客户端

java.lang.RuntimeException (message=null, stacktrace A) caused by
java.io.IOException (message="Problems with io", stacktrace B) caused by
java.lang.IllegalStateException (message="Some error text", stacktrace C), cause=null

在服务器上我得到以下信息:

java.lang.RuntimeException (message="java.io.IOException: Problems with io", stacktrace X) cause=this

其中stacktrace X 只是堆栈跟踪,导致此异常在服务器上被反序列化,即不考虑原始堆栈跟踪 A、B 或 C。因此 堆栈跟踪信息丢失以及导致连锁。

看了极品文章7 Tips for Exception Handling in GWT才发现

异常中的堆栈跟踪是暂时的,因此从客户端到服务器会丢失(因此,如果您在服务器端需要它,请将其作为单独的参数发送)

经过一番谷歌搜索后,我得出结论,使用标准 Java 序列化技术完全序列化/反序列化 java.lang.Throwable 实例的主题是不那么受欢迎。实际上,我既找不到库也找不到详细描述如何实现这一点的博客。

以前有没有人坚持并解决过这样的问题?这个问题有什么建议的解决方案吗?

提前致谢!

【问题讨论】:

  • GWT 编译为 JavaScript。部署应用程序后,您将不会遇到 Java 中所期望的相同异常。我错过了什么吗?

标签: java exception gwt serialization stack-trace


【解决方案1】:

尝试 RemoteLoggingService 将日志从客户端发送到服务器端。这是一个示例代码:

web.xml:

<servlet>
    <servlet-name>remoteLogServlet</servlet-name>
    <servlet-class>com.x.y.z.server.servlet.GwtRemoteLogging</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>remoteLogServlet</servlet-name>
    <url-pattern>/context_path/remote_logging</url-pattern>
</servlet-mapping>

GwtRemoteLogging.java:

import java.util.logging.Level;
import java.util.logging.LogRecord;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import com.google.gwt.logging.server.StackTraceDeobfuscator;
import com.google.gwt.logging.shared.RemoteLoggingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import org.apache.log4j.Logger;

/**
 * The Class GwtRemoteLogging.
 */
@SuppressWarnings("serial")
public class GwtRemoteLogging extends RemoteServiceServlet implements RemoteLoggingService {

    /** The Constant logger. */
    private StackTraceDeobfuscator deobfuscator = null;
    private final static Logger logger = Logger.getLogger("logger_name");

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

    /**
     * Logs a Log Record which has been serialized using GWT RPC on the server.
     * 
     * @return either an error message, or null if logging is successful.
     */
    public final String logOnServer(LogRecord lr) {
        try {
            if (lr.getLevel().equals(Level.SEVERE)) {
                logger.error(lr.getMessage(),lr.getThrown());
            } else if (lr.getLevel().equals(Level.INFO)) {
                logger.info(lr.getMessage(),lr.getThrown());
            } else if (lr.getLevel().equals(Level.WARNING)) {
                logger.warn(lr.getMessage(),lr.getThrown());
            } else if (lr.getLevel().equals(Level.FINE)) {
                logger.debug(lr.getMessage(),lr.getThrown());
            } else {
                logger.trace(lr.getMessage(),lr.getThrown());
            }
        } catch (Exception e) {
            logger.error("Remote logging failed", e);
            return "Remote logging failed, check stack trace for details.";
        }
        return null;
    }

    /**
     * By default, this service does not do any deobfuscation. In order to do server side
     * deobfuscation, you must copy the symbolMaps files to a directory visible to the server and
     * set the directory using this method.
     * 
     * @param symbolMapsDir
     */
    public void setSymbolMapsDirectory(String symbolMapsDir) {
        if (deobfuscator == null) {
            deobfuscator = new StackTraceDeobfuscator(symbolMapsDir);
        } else {
            deobfuscator.setSymbolMapsDirectory(symbolMapsDir);
        }
    }
}

gwt.xml(继承日志记录并设置属性):

<inherits name="com.google.gwt.logging.Logging"/>
<set-property name="gwt.logging.logLevel" value="FINE"/>          
<set-property name="gwt.logging.enabled" value="TRUE"/>    

<!-- This handler sends log messages to the server, where they will be logged using the server side logging mechanism. -->     
<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />  
<!-- Logs by calling method GWT.log. These messages can only be seen in Development Mode in the DevMode window. -->
<set-property name="gwt.logging.developmentModeHandler" value="ENABLED" />  
<!-- These messages can only be seen in Development Mode in the DevMode window. -->
<set-property name="gwt.logging.systemHandler" value="ENABLED" />
<!-- Logs to the popup which resides in the upper left hand corner of application when this handler is enabled. -->
<set-property name="gwt.logging.popupHandler" value="DISABLED" />
<!-- Logs to the javascript console, which is used by Firebug Lite (for IE), Safari and Chrome. -->
<set-property name="gwt.logging.consoleHandler" value="DISABLED"/> 
<!-- Logs to the firebug console. -->
<set-property name="gwt.logging.firebugHandler" value="DISABLED" /> 

客户端代码:

public void log(String message,Throwable e) {
    Logger logger = java.util.logging.Logger.getLogger("class_name");
    logger.fine(name + ":" + message);
    logger.info(name + ":" + message);
    logger.log(Level.SEVERE, message, e);
    logger.warning(message);
}

【讨论】:

  • 感谢您的回答!我考虑过使用 GWT 的 内置日志记录机制,但我需要更大的灵活性,所以我决定使用自己的服务。
【解决方案2】:

好的,找到了一个优雅而简单的解决方案来解决我的问题:在 GWT 2.5.1 中有一个专门针对这些需求而设计的类,称为 com.google.gwt.core.client.impl.SerializableThrowable,并带有以下 JavaDoc:

模拟的 Throwable 类不会递归序列化 Throwable,也不会序列化堆栈跟踪。这个类是一个替代方案,可以通过为包含 Throwable 的类编写自定义序列化程序来使用。以 LogRecord_CustomFieldSerializer 为例。

所以,解决我的问题的代码 sn-p 如下:

// client-side
LogServiceAsync logService = GWT.create(LogService.class);

GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {

    @Override
    public void onUncaughtException(final Throwable ex) {
        // wrapping throwable in SerializableThrowable to preserve 
        // causes and stack traces upon serialization
        SerializableThrowable serializableEx = new SerializableThrowable(ex);
        // sending instance of SerializableThrowable to server
        logService.log(serializableEx, callbackCodeDoesntMatter);
    }
}

// server-side
public class LogServiceServlet extends RemoteServiceServlet implements LogService {

    @Override
    public void log(final SerializableThrowable ex) {
        // getting original instance Throwable with preserved
        // causes and stack traces
        Throwable originalThrowable = ex.getThrowable();
        originalThrowable.printStackTrace();
    }
}

如果以这种方式实现,它会打印正确的堆栈跟踪信息以及正确的原因。

注意GWT 2.6.0 类中,com.google.gwt.core.client.impl.SerializableThrowable 已被弃用,取而代之的是 com.google.gwt.core.shared.SerializableThrowable,它与第一个略有不同,并且应该类似地工作。

【讨论】:

    【解决方案3】:

    即使有效,我也不认为这样做是明智的。在序列化过程中,我们需要了解附加到对象的内容,并确保在反序列化时它获得了所需的所有正确版本的类,否则会失败。因此,取决于运行环境,异常堆栈跟踪不同,平台、jvm 版本、附加库不同....因此,将堆栈跟踪视为该环境及时的快照,除非稍后恢复,否则无法重新引入时间到相同的环境。但是在您的要求中,它打算从客户端发送到服务器,所以这永远不会起作用!最好的办法就是将其捕获为字符串并将其保存为:

    public static String getStackTrace(Throwable t) {
       if (t == null) {
          return "Exception not available.";
       } else {
          StringWriter stackTraceHolder = new StringWriter();
          t.printStackTrace(new PrintWriter(stackTraceHolder));
           return stackTraceHolder.toString();
       }
    }
    

    如果你不能使用 StringWriter 那么试试这个:

    public static String getStackTrace(Throwable t) {
       if (t == null) {
          return "Exception not available.";
       } else {
          StringBuilder sb = new StringBuilder();
          for (StackTraceElement element : t.getStackTrace()) {
            sb.append(element.toString());
            sb.append("\n");
          }
          return sb.toString();
       }
    }
    

    【讨论】:

    • 感谢您的回答!我曾想过将堆栈跟踪打印到字符串并发送字符串,但在 GWT 中会出现 2 个问题:1. GWT 客户端代码无法使用 PrintWriter, StringWriter 2. 堆栈跟踪代码是在客户端进行混淆并且需要在服务器端进行去混淆,因此在序列化期间保留标准的StackTraceElement类更为可取。关于过时的堆栈跟踪:GWT 中的服务器和客户端代码应该同时更改,所以这应该不是问题。
    • 见上面修改后的解决方案!
    • 太好了 - 非常感谢您的提示!将这个字符串反序列化回 StackTraceElement 实例的问题仍然存在......
    • 不确定您面临的反序列化问题,但 StackTraceElement 是一个对象,并且 t.getStackTrace() 返回它的数组,您可以替换 for (Object element : t.getStackTrace()) { 并查看它是否适合您。跨度>
    • 问题是我需要类似以下的东西:(客户端)可抛出实例(带有原因和堆栈跟踪) ->(客户端)以某种方式序列化 -> 发送通过网络 ->(服务器端)以某种方式反序列化 ->(服务器端)Throwable 实例(带有原因和堆栈跟踪)。所以在序列化为字符串之后,我需要将它从字符串反序列化回 Throwable,所以如果可能的话,最好使用原生序列化...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-25
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-11
    相关资源
    最近更新 更多