The problem
In any production Wicket application, there are countless exceptions that are thrown which lead to error pages. Some are very difficult to debug, or even reproduce, due to lack of information. You normally wouldn’t be handling these kind of exceptions yourself, so wouldn’t it be nice to somehow report all these? Some common exceptions every Wicket developer is probably sick of are:
org.apache.wicket.MarkupContainer - Unable to find component with id 'XXX' in [Page class = YYY, id = 0, render count = 1]
IllegalArgumentException - Cannot update component that does not have setOutputMarkupId property set to true. Component: blah
Tried of struggling to reproduce bugs reported by users? Wouldn’t it be nice to see the past N actions taken by the user before getting the error page? We can accomplish all of this by implementing Wicket request logging and using our own custom AbstractRequestLogger.
The solution
Creating a custom AbstractRequestLogger
The first step is to modify our Wicket’s WebApplication init() method to enable request logging, and to set our own custom request logger that will use an EvictingQueue to store the past N request URL’s in session.
@Override
public void init() {
super.init();
getRequestLoggerSettings().setRequestLoggerEnabled(true);
getRequestLoggerSettings().setRequestsWindowSize(5); //set # of requests to store
}
@Override
protected IRequestLogger newRequestLogger() {
return new CustomRequestLogger();
}
Here we have set the requestsWindowSize to 5. This means that our error log will also contain the previous 5 requests by the user.
You can see the full implementation of CustomRequestLogger here.
Now, to make sure that we capture all exceptions thrown by Wicket, we also need to set a custom IRequestCycleListener in our init() method:
getRequestCycleListeners().add(new IRequestCycleListener() {
@Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) {
//do something with ex
if(ex instanceof ListenerInvocationNotAllowedException || ex instanceof ComponentNotFoundException) {
//if this is an ajax request, just return an empty response to avoid sending user to error page
return EmptyAjaxRequestHandler.getInstance();
}
return null;
}
});
In the onException() method, you can now do whatever you need to do with the exception.
A good approach would be to save the error and stack trace to a database table, as well as send an email to developers so it can be quickly addressed. They will receive a nicely formatted email with environment information, the logged in user id, URL, stack trace, and the requests leading up to the exception.
They can then happily go about fixing it with a smile on their face, well maybe 馃檪
You can checkout a full working example of this here!