* 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:
parent
b5f7f14b2f
commit
b29bf58253
6 changed files with 134 additions and 39 deletions
|
@ -17,9 +17,12 @@
|
||||||
package helma.framework;
|
package helma.framework;
|
||||||
|
|
||||||
import helma.objectmodel.db.Transactor;
|
import helma.objectmodel.db.Transactor;
|
||||||
|
import helma.scripting.ScriptingException;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -373,9 +376,43 @@ public class ResponseBean implements Serializable {
|
||||||
* @return the error message
|
* @return the error message
|
||||||
*/
|
*/
|
||||||
public String getError() {
|
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 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
|
* Get the current message for the response, if set
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,6 +19,7 @@ package helma.framework;
|
||||||
import helma.framework.core.Skin;
|
import helma.framework.core.Skin;
|
||||||
import helma.framework.core.Application;
|
import helma.framework.core.Application;
|
||||||
import helma.util.*;
|
import helma.util.*;
|
||||||
|
import helma.scripting.ScriptingException;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -95,8 +96,8 @@ public final class ResponseTrans extends Writer implements Serializable {
|
||||||
// field for generic message to be displayed
|
// field for generic message to be displayed
|
||||||
private transient String message;
|
private transient String message;
|
||||||
|
|
||||||
// field for error message
|
// field for error
|
||||||
private transient String error;
|
private transient Throwable error;
|
||||||
|
|
||||||
// the res.data map of form and cookie data
|
// the res.data map of form and cookie data
|
||||||
private transient Map values = new SystemMap();
|
private transient Map values = new SystemMap();
|
||||||
|
@ -190,7 +191,8 @@ public final class ResponseTrans extends Writer implements Serializable {
|
||||||
buffers = null;
|
buffers = null;
|
||||||
response = null;
|
response = null;
|
||||||
cacheable = true;
|
cacheable = true;
|
||||||
redir = forward = message = error = null;
|
redir = forward = message = null;
|
||||||
|
error = null;
|
||||||
etag = realm = charset = null;
|
etag = realm = charset = null;
|
||||||
contentType = "text/html";
|
contentType = "text/html";
|
||||||
values.clear();
|
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
|
* Write a vanilla error report. Callers should make sure the ResponeTrans is
|
||||||
* new or has been reset.
|
* new or has been reset.
|
||||||
*
|
*
|
||||||
* @param appName the application name
|
* @param throwable the error
|
||||||
* @param message the error message
|
|
||||||
*/
|
*/
|
||||||
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()) {
|
if (reqtrans.isXmlRpc()) {
|
||||||
writeXmlRpcError(new RuntimeException(message));
|
writeXmlRpcError(new RuntimeException(throwable));
|
||||||
} else {
|
} else {
|
||||||
status = 500;
|
status = 500;
|
||||||
if (!"true".equalsIgnoreCase(app.getProperty("suppressErrorPage"))) {
|
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("Error in application ");
|
||||||
write(appName);
|
write(app.getName());
|
||||||
write("</h3>");
|
write("</h2><p>");
|
||||||
write(message);
|
encode(errorMessage);
|
||||||
write("</body></html>");
|
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.
|
* Get the error message to display to the user, if any.
|
||||||
* @return the error message
|
* @return the error message
|
||||||
*/
|
*/
|
||||||
public String getError() {
|
public Throwable getError() {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1018,10 +1058,25 @@ public final class ResponseTrans extends Writer implements Serializable {
|
||||||
* Set a message to display to the user.
|
* Set a message to display to the user.
|
||||||
* @param error the error message
|
* @param error the error message
|
||||||
*/
|
*/
|
||||||
public void setError(String error) {
|
public void setError(Throwable error) {
|
||||||
this.error = 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.
|
* Get debug messages to append to the response, if any.
|
||||||
* @return the response's debug buffer
|
* @return the response's debug buffer
|
||||||
|
|
|
@ -701,7 +701,7 @@ public final class Application implements Runnable {
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
errorCount += 1;
|
errorCount += 1;
|
||||||
res = new ResponseTrans(this, req);
|
res = new ResponseTrans(this, req);
|
||||||
res.reportError(name, x.getMessage());
|
res.reportError(x);
|
||||||
} finally {
|
} finally {
|
||||||
if (primaryRequest) {
|
if (primaryRequest) {
|
||||||
activeRequests.remove(req);
|
activeRequests.remove(req);
|
||||||
|
|
|
@ -149,7 +149,7 @@ public final class RequestEvaluator implements Runnable {
|
||||||
|
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
String error = null;
|
Throwable error = null;
|
||||||
String functionName = function instanceof String ?
|
String functionName = function instanceof String ?
|
||||||
(String) function : null;
|
(String) function : null;
|
||||||
|
|
||||||
|
@ -543,7 +543,7 @@ public final class RequestEvaluator implements Runnable {
|
||||||
Thread.sleep((long) (base + (Math.random() * base * 2)));
|
Thread.sleep((long) (base + (Math.random() * base * 2)));
|
||||||
} catch (InterruptedException interrupt) {
|
} catch (InterruptedException interrupt) {
|
||||||
// we got interrrupted, create minimal error message
|
// we got interrrupted, create minimal error message
|
||||||
res.reportError(app.getName(), error);
|
res.reportError(interrupt);
|
||||||
done = true;
|
done = true;
|
||||||
// and release resources and thread
|
// and release resources and thread
|
||||||
rtx = null;
|
rtx = null;
|
||||||
|
@ -555,11 +555,8 @@ public final class RequestEvaluator implements Runnable {
|
||||||
}
|
}
|
||||||
abortTransaction();
|
abortTransaction();
|
||||||
|
|
||||||
if (error == null)
|
|
||||||
error = "Application too busy, please try again later";
|
|
||||||
|
|
||||||
// error in error action. use traditional minimal error message
|
// 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;
|
done = true;
|
||||||
}
|
}
|
||||||
} catch (Throwable x) {
|
} catch (Throwable x) {
|
||||||
|
@ -587,15 +584,7 @@ public final class RequestEvaluator implements Runnable {
|
||||||
|
|
||||||
// set done to false so that the error will be processed
|
// set done to false so that the error will be processed
|
||||||
done = false;
|
done = false;
|
||||||
error = x.getMessage();
|
error = x;
|
||||||
|
|
||||||
if ((error == null) || (error.length() == 0)) {
|
|
||||||
error = x.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error == null) {
|
|
||||||
error = "Unspecified error";
|
|
||||||
}
|
|
||||||
|
|
||||||
app.logError(txname + ": " + error, x);
|
app.logError(txname + ": " + error, x);
|
||||||
|
|
||||||
|
@ -610,7 +599,7 @@ public final class RequestEvaluator implements Runnable {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// error in error action. use traditional minimal error message
|
// error in error action. use traditional minimal error message
|
||||||
res.reportError(app.getName(), error);
|
res.reportError(error);
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -755,7 +744,7 @@ public final class RequestEvaluator implements Runnable {
|
||||||
|
|
||||||
if (reqtype != NONE && stopTransactor()) {
|
if (reqtype != NONE && stopTransactor()) {
|
||||||
res.reset();
|
res.reset();
|
||||||
res.reportError(app.getName(), "Request timed out");
|
res.reportError("Request timed out");
|
||||||
}
|
}
|
||||||
|
|
||||||
session.commit(this);
|
session.commit(this);
|
||||||
|
|
|
@ -688,7 +688,7 @@ public final class Skin {
|
||||||
if ("message".equals(propName))
|
if ("message".equals(propName))
|
||||||
value = cx.reval.getResponse().getMessage();
|
value = cx.reval.getResponse().getMessage();
|
||||||
else if ("error".equals(propName))
|
else if ("error".equals(propName))
|
||||||
value = cx.reval.getResponse().getError();
|
value = cx.reval.getResponse().getErrorMessage();
|
||||||
if (value != null)
|
if (value != null)
|
||||||
return filter(value, cx);
|
return filter(value, cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,7 @@ package helma.scripting;
|
||||||
|
|
||||||
import org.mozilla.javascript.RhinoException;
|
import org.mozilla.javascript.RhinoException;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.*;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for wrapped exceptions thrown by invocation of the scripting engine.
|
* 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) {
|
public void printStackTrace(PrintStream s) {
|
||||||
synchronized (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) {
|
public void printStackTrace(PrintWriter s) {
|
||||||
synchronized (s) {
|
synchronized (s) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue