* Make error handling more flexible, adding res.exception, res.scriptStack, res.javaStack

to get further information on unhandled exceptions during page execution.
* Make default error page include script and java stack traces when debug is set to true.
This commit is contained in:
hns 2008-01-29 12:16:06 +00:00
parent b5f7f14b2f
commit b29bf58253
6 changed files with 134 additions and 39 deletions

View file

@ -17,9 +17,12 @@
package helma.framework;
import helma.objectmodel.db.Transactor;
import helma.scripting.ScriptingException;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Map;
@ -373,9 +376,43 @@ public class ResponseBean implements Serializable {
* @return the error message
*/
public String getError() {
return res.getErrorMessage();
}
/**
* Get the uncaught exception for the response, if any
* @return the uncaught exception
*/
public Throwable getException() {
return res.getError();
}
/**
* Return the Javascript stack trace of an uncought exception.
* @return the script stack trace of any uncaught exception or null.
*/
public String getScriptStack() {
Throwable t = res.getError();
if (t instanceof ScriptingException)
return ((ScriptingException) t).getScriptStackTrace();
return null;
}
/**
* Get the Java stack trace of an uncaught exception.
* @return the java stack trace of an uncaught exception or null.
*/
public String getJavaStack() {
Throwable t = res.getError();
if (t == null)
return null;
else if (t instanceof ScriptingException)
return ((ScriptingException) t).getJavaStackTrace();
StringWriter w = new StringWriter();
t.printStackTrace(new PrintWriter(w));
return w.toString();
}
/**
* Get the current message for the response, if set
*

View file

@ -19,6 +19,7 @@ package helma.framework;
import helma.framework.core.Skin;
import helma.framework.core.Application;
import helma.util.*;
import helma.scripting.ScriptingException;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@ -95,8 +96,8 @@ public final class ResponseTrans extends Writer implements Serializable {
// field for generic message to be displayed
private transient String message;
// field for error message
private transient String error;
// field for error
private transient Throwable error;
// the res.data map of form and cookie data
private transient Map values = new SystemMap();
@ -190,7 +191,8 @@ public final class ResponseTrans extends Writer implements Serializable {
buffers = null;
response = null;
cacheable = true;
redir = forward = message = error = null;
redir = forward = message = null;
error = null;
etag = realm = charset = null;
contentType = "text/html";
values.clear();
@ -546,21 +548,59 @@ public final class ResponseTrans extends Writer implements Serializable {
* Write a vanilla error report. Callers should make sure the ResponeTrans is
* new or has been reset.
*
* @param appName the application name
* @param message the error message
* @param throwable the error
*/
public void reportError(String appName, String message) {
public void reportError(Throwable throwable) {
if (throwable == null) {
// just to be safe
reportError("Unspecified error");
return;
}
if (reqtrans.isXmlRpc()) {
writeXmlRpcError(new RuntimeException(message));
writeXmlRpcError(new RuntimeException(throwable));
} else {
status = 500;
if (!"true".equalsIgnoreCase(app.getProperty("suppressErrorPage"))) {
write("<html><body><h3>");
write("<html><body>");
write("<h2>Error in application " + app.getName() + "</h2><p>");
encode(getErrorMessage(throwable));
writeln("</p>");
if (app.debug()) {
if (throwable instanceof ScriptingException) {
ScriptingException scriptx = (ScriptingException) throwable;
writeln("<h4>Script Stack</h4>");
writeln("<pre>" + scriptx.getScriptStackTrace() + "</pre>");
writeln("<h4>Java Stack</h4>");
writeln("<pre>" + scriptx.getJavaStackTrace() + "</pre>");
} else {
writeln("<h4>Java Stack</h4>");
writeln("<pre>");
throwable.printStackTrace(new PrintWriter(this));
writeln("</pre>");
}
}
writeln("</body></html>");
}
}
}
/**
* Write a vanilla error report. Callers should make sure the ResponeTrans is
* new or has been reset.
* @param errorMessage the error message
*/
public void reportError(String errorMessage) {
if (reqtrans.isXmlRpc()) {
writeXmlRpcError(new RuntimeException(errorMessage));
} else {
status = 500;
if (!"true".equalsIgnoreCase(app.getProperty("suppressErrorPage"))) {
write("<html><body><h2>");
write("Error in application ");
write(appName);
write("</h3>");
write(message);
write("</body></html>");
write(app.getName());
write("</h2><p>");
encode(errorMessage);
writeln("</p></body></html>");
}
}
}
@ -1010,7 +1050,7 @@ public final class ResponseTrans extends Writer implements Serializable {
* Get the error message to display to the user, if any.
* @return the error message
*/
public String getError() {
public Throwable getError() {
return error;
}
@ -1018,10 +1058,25 @@ public final class ResponseTrans extends Writer implements Serializable {
* Set a message to display to the user.
* @param error the error message
*/
public void setError(String error) {
public void setError(Throwable error) {
this.error = error;
}
public String getErrorMessage() {
if (error == null)
return null;
return getErrorMessage(error);
}
private static String getErrorMessage(Throwable t) {
String msg = t.getMessage();
if (msg == null || msg.length() == 0)
msg = t.toString();
if (msg == null || msg.length() == 0)
return "Unspecified Error: " + t.getClass().getName();
return msg;
}
/**
* Get debug messages to append to the response, if any.
* @return the response's debug buffer

View file

@ -701,7 +701,7 @@ public final class Application implements Runnable {
} catch (Exception x) {
errorCount += 1;
res = new ResponseTrans(this, req);
res.reportError(name, x.getMessage());
res.reportError(x);
} finally {
if (primaryRequest) {
activeRequests.remove(req);

View file

@ -149,7 +149,7 @@ public final class RequestEvaluator implements Runnable {
int tries = 0;
boolean done = false;
String error = null;
Throwable error = null;
String functionName = function instanceof String ?
(String) function : null;
@ -543,7 +543,7 @@ public final class RequestEvaluator implements Runnable {
Thread.sleep((long) (base + (Math.random() * base * 2)));
} catch (InterruptedException interrupt) {
// we got interrrupted, create minimal error message
res.reportError(app.getName(), error);
res.reportError(interrupt);
done = true;
// and release resources and thread
rtx = null;
@ -555,11 +555,8 @@ public final class RequestEvaluator implements Runnable {
}
abortTransaction();
if (error == null)
error = "Application too busy, please try again later";
// error in error action. use traditional minimal error message
res.reportError(app.getName(), error);
res.reportError("Application too busy, please try again later");
done = true;
}
} catch (Throwable x) {
@ -587,15 +584,7 @@ public final class RequestEvaluator implements Runnable {
// set done to false so that the error will be processed
done = false;
error = x.getMessage();
if ((error == null) || (error.length() == 0)) {
error = x.toString();
}
if (error == null) {
error = "Unspecified error";
}
error = x;
app.logError(txname + ": " + error, x);
@ -610,7 +599,7 @@ public final class RequestEvaluator implements Runnable {
}
} else {
// error in error action. use traditional minimal error message
res.reportError(app.getName(), error);
res.reportError(error);
done = true;
}
} finally {
@ -755,7 +744,7 @@ public final class RequestEvaluator implements Runnable {
if (reqtype != NONE && stopTransactor()) {
res.reset();
res.reportError(app.getName(), "Request timed out");
res.reportError("Request timed out");
}
session.commit(this);

View file

@ -688,7 +688,7 @@ public final class Skin {
if ("message".equals(propName))
value = cx.reval.getResponse().getMessage();
else if ("error".equals(propName))
value = cx.reval.getResponse().getError();
value = cx.reval.getResponse().getErrorMessage();
if (value != null)
return filter(value, cx);
}

View file

@ -18,10 +18,7 @@ package helma.scripting;
import org.mozilla.javascript.RhinoException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.FilenameFilter;
import java.io.File;
import java.io.*;
/**
* The base class for wrapped exceptions thrown by invocation of the scripting engine.
@ -60,9 +57,26 @@ public class ScriptingException extends Exception {
}
}
/**
* Get the script stack, or null if none is available
* @return the script stack trace
*/
public String getScriptStackTrace() {
return scriptStack;
}
/**
* Get the java stack trace.
* @return the java stack trace
*/
public String getJavaStackTrace() {
StringWriter w = new StringWriter();
getCause().printStackTrace(new PrintWriter(w));
return w.toString();
}
/*
* Adaption from Throwable.printStackTrace() to only print Script file stack elements.
* Adaption from Throwable.printStackTrace() to also print Script file stack elements.
*/
public void printStackTrace(PrintStream s) {
synchronized (s) {
@ -77,7 +91,7 @@ public class ScriptingException extends Exception {
/*
* Adaption from Throwable.printStackTrace() to only print Script file stack elements.
* Adaption from Throwable.printStackTrace() to also print Script file stack elements.
*/
public void printStackTrace(PrintWriter s) {
synchronized (s) {