序列化java.lang.Throwable和堆栈跟踪,导致链和相关的堆栈跟踪



我正在编写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无关。因此,堆栈跟踪信息连同原因链一起丢失

在阅读了精彩的文章《GWT中异常处理的7个技巧》之后,我们发现

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

经过一点谷歌搜索,我得出结论,完全序列化/反序列化java.lang.Throwable使用标准java序列化技术的实例的主题不那么流行。事实上,无论是图书馆还是博客,我都找不到关于如何实现这一目标的详细描述。

以前有人发现并解决过这样的问题吗?这个问题有什么建议的解决办法吗?

提前感谢!

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

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

因此,解决我的问题的代码片段如下:

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

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

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

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

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

尝试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>

GwtMotorLogging.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);
}

最新更新