Ok so I did this in the end. I've basically used the ideas above but thought there was enough extra to post my own answer.
It turns out you really shouldn't do this as other people suggested and I've added a bit at the bottom to say why!
Here's my filter:
public class FailOnErrorFilter implements Filter
{
@Override
public void init(FilterConfig config) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
try {
filterChain.doFilter(request, response);
}
catch (Exception exception) {
System.exit(1);
}
}
@Override
public void destroy()
{
}
}
To get this working you have to modify the web.xml:
<filter>
<filter-name>failingFilter</filter-name>
<filter-class>fullyQualified.FailOnErrorFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>failingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The top one defines the filter + the second one says use it everywhere.
ExtDirectSpring
After doing the above I realised that ExtDirectSpring was a further culprit. It's default behaviour is to swallow all exceptions in server side methods.
I was worried I was going to have to patch the library but thankfully someone else had already complained about this and it got fixed in 1.3.6. Initially I tried upgrading to the latest version but it broke a load of code! What a great library. Anyway 1.3.6 added in the ability to switch off the suppression of errors by doing the following:
//this is to switch off blanket switching of exceptions in extdirect spring
@Component
public class NoExceptionHandling implements RouterExceptionHandler
{
@Override
public Object handleException(MethodInfo methodInfo, BaseResponse baseResponse, Exception e, HttpServletRequest httpServletRequest)
{
throw new RuntimeException(e);
}
}
As the name suggests extdirectspring uses spring and so doesn't make its dependencies obvious to calling code, however if you go digging (its on github). You'll see in RouterController it calls the following method in the catch
private Object handleException(MethodInfo methodInfo, BaseResponse response, Exception e, HttpServletRequest request) {
return configurationService.getRouterExceptionHandler().handleException(methodInfo, response, e, request);
}
Where router controller does this:
@Autowired(required = false)
private RouterExceptionHandler routerExceptionHandler;
public RouterExceptionHandler getRouterExceptionHandler() {
return routerExceptionHandler;
}
It sets up a default one if you dont provide one.
Update - why you shouldn't do this
It turns out you really shouldn't call System.exit
in a tomcat application. Not only does it bring down your application it also causes the server to exit. This brings down any other applications running aswell!
It's also not appropriate for a number of other reasons:
- if the first in a series of tests throws an exception then all subsequent tests will fail
- Its time consuming restarting the server and you have to be the person who breaks it to see the exception
- if you are running a manual test deployment on a seperate machine then you have to restart the server if something caused a problem somewhere.
Likewise:
- In production it will take everyone's application down and most users wont be in a position to restart the server.
What I'm doing instead
The errors were already being written the the tomcat logs + the database.
- In debug we now also going to redirect to an error page with the stacktrace
- In production we are going to just redirect to a 'something went wrong' page. We're also going to set up an email service that notifies us of exceptions.
- For UI/selenium tests - it'll work the same as debug
- For headless Js tests the server rejects subsequent requests until the next test resets the error state of the server
Just to make things more complicated unsurprisingly the original webapp is too flaky to not mask errors so I've kept the old error suppression in place for that as we're not actively developing/fixing it at the moment.