Serialize java.lang.Throwable along with stack tra

2019-03-02 02:11发布

I'm writing GWT application where I need to send instance of java.lang.Throwable (with chain of it's causes and all stack traces respectively) using GWT RPC which uses standard Java serialization mechanism (as far as I'm concerned).

The problem is that when I pass following sample exception from client:

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

on the server I get the following:

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

where stacktrace X is simply stack trace leading to place where this exception was deserialized on the server i.e. with no regards to original stack traces A, B or C. So stacktrace information is lost along with causes chain.

After reading superb article 7 Tips for Exception Handling in GWT it was found out that

the stack trace within an exception is transient, and so is lost from client to server (so if you need it on the server side, send it as a separate parameter)

After a bit of googling, I came to conclusion that topic of fully serializing/deserializing instances of java.lang.Throwable using standard Java serialization technique is not so popular. Actually I couldn't find neither libraries nor blogs with detailed description on how to achieve this.

Has anybody stucked upon and solved such problem before? Are there any suggested solutions to this problem?

Thanks in advance!

3条回答
甜甜的少女心
2楼-- · 2019-03-02 02:53

Even if is working I don't think it is wise to do so. In serialization we need to keep tap of what are attached to the object and make sure that when de-serialized it got all the right version of the classes it need otherwise failed. So, depend of the running environment, the exception stacktrace are different, platform, jvm version, additional libraries different.... So, think of the stacktrace as a snapshot in time for that environment and can not be reintroduce unless restored it the later time to the same environment. But in your requirement, it is intent to send from client to a server so, this will never work! The best thing to do is just to captured as string and save it as such:

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

And if you can not use StringWriter then try this:

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();
   }
}
查看更多
乱世女痞
3楼-- · 2019-03-02 03:07

Ok, found an elegant and simple solution to my problem: in GWT 2.5.1 there is a class designed specifically for those needs called com.google.gwt.core.client.impl.SerializableThrowable with following JavaDoc:

The emulated Throwable class does not serialize Throwables recursively and does not serialize the stack trace. This class is an alternative, and can be used by writing a custom serializer for the class which contains a Throwable. See LogRecord_CustomFieldSerializer as an example.

So, code snippet solving my problem is the following:

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

If implemented in this way, it prints correct stack trace info along with correct causes.

NOTE In GWT 2.6.0 class com.google.gwt.core.client.impl.SerializableThrowable is deprecated in favor of com.google.gwt.core.shared.SerializableThrowable which differs from the first one only slightly and should work similarly.

查看更多
等我变得足够好
4楼-- · 2019-03-02 03:16

Try RemoteLoggingService to send the logs from client to server side. Here is a sample code:

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 (inherit logging and set properties):

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

client side code:

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);
}
查看更多
登录 后发表回答