Skip to content

Commit a5e903a

Browse files
author
Tobias Mende
committed
Insert BugsnagMvcExceptionHandler just before the DefaultHandlerExceptionResolver
1 parent 81abe4c commit a5e903a

File tree

7 files changed

+91
-16
lines changed

7 files changed

+91
-16
lines changed

bugsnag-spring/src/main/java/com/bugsnag/BugsnagMvcExceptionHandler.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@
22

33
import com.bugsnag.HandledState.SeverityReasonType;
44

5-
import org.springframework.core.Ordered;
6-
import org.springframework.core.annotation.Order;
75
import org.springframework.web.servlet.HandlerExceptionResolver;
86
import org.springframework.web.servlet.ModelAndView;
97

108
import java.util.Collections;
11-
129
import javax.servlet.http.HttpServletRequest;
1310
import javax.servlet.http.HttpServletResponse;
1411

1512
/**
1613
* Reports uncaught exceptions thrown from handler mapping or execution to Bugsnag
1714
* and then passes the exception to the next handler in the chain.
18-
*
15+
* <p>
1916
* Set to highest precedence so that it should be called before other exception
2017
* resolvers.
2118
*/
22-
@Order(Ordered.HIGHEST_PRECEDENCE)
2319
class BugsnagMvcExceptionHandler implements HandlerExceptionResolver {
2420

2521
private final Bugsnag bugsnag;
@@ -48,3 +44,4 @@ public ModelAndView resolveException(HttpServletRequest request,
4844
return null;
4945
}
5046
}
47+
Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
11
package com.bugsnag;
22

33
import org.springframework.beans.factory.annotation.Autowired;
4-
import org.springframework.context.annotation.Bean;
54
import org.springframework.context.annotation.Conditional;
65
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.web.servlet.HandlerExceptionResolver;
7+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
78

9+
import java.util.List;
810
import javax.annotation.PostConstruct;
911

1012
/**
1113
* If spring-webmvc is loaded, add configuration for reporting unhandled exceptions.
1214
*/
1315
@Configuration
1416
@Conditional(SpringWebMvcLoadedCondition.class)
15-
class MvcConfiguration {
17+
class MvcConfiguration extends WebMvcConfigurationSupport {
1618

1719
@Autowired
1820
private Bugsnag bugsnag;
1921

20-
/**
21-
* Register an exception resolver to send unhandled reports to Bugsnag
22-
* for uncaught exceptions thrown from request handlers.
23-
*/
24-
@Bean
25-
BugsnagMvcExceptionHandler bugsnagHandlerExceptionResolver() {
26-
return new BugsnagMvcExceptionHandler(bugsnag);
27-
}
28-
2922
/**
3023
* Add a callback to assign specified severities for some Spring exceptions.
3124
*/
3225
@PostConstruct
3326
void addExceptionClassCallback() {
3427
bugsnag.addCallback(new ExceptionClassCallback());
3528
}
29+
30+
/**
31+
* Normally, the exceptionResolvers contain the following resolvers in this order:
32+
* - {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}
33+
* - {@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}
34+
* - {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}
35+
* <p>
36+
* The first two handlers handle exceptions in an application-specific manner.
37+
* (either by @{@link org.springframework.web.bind.annotation.ExceptionHandler}
38+
* or @{@link org.springframework.web.bind.annotation.ResponseStatus})
39+
* <p>
40+
* Therefore, exceptions that are handled by these handlers should not be handled by Bugsnag.
41+
* Only unhandled exceptions shall be sent to Bugsnag.
42+
*/
43+
@Override
44+
protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
45+
final int position = exceptionResolvers.isEmpty() ? 0 : exceptionResolvers.size() - 1;
46+
exceptionResolvers.add(position, new BugsnagMvcExceptionHandler(bugsnag));
47+
}
3648
}

bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ public void springVersionSetCorrectly() {
171171
assertEquals(SpringBootVersion.getVersion(), runtimeVersions.get("springBoot"));
172172
}
173173

174+
@Test
175+
public void noBugsnagNotifyOnResponseStatusException() {
176+
callResponseStatusExceptionEndpoint();
177+
178+
verifyNoReport();
179+
}
180+
181+
@Test
182+
public void noBugsnagNotifyOnExceptionHandledByExceptionHandlerException() {
183+
callResponseStatusExceptionEndpoint();
184+
185+
verifyNoReport();
186+
}
187+
174188
@Test
175189
public void unhandledTypeMismatchExceptionSeverityInfo() {
176190
callUnhandledTypeMismatchExceptionEndpoint();
@@ -242,6 +256,16 @@ private void callUnhandledTypeMismatchExceptionEndpoint() {
242256
"/throw-type-mismatch-exception", String.class);
243257
}
244258

259+
private void callResponseStatusExceptionEndpoint() {
260+
this.restTemplate.getForEntity(
261+
"/throw-response-status-exception", String.class);
262+
}
263+
264+
private void callCustomExceptionEndpoint() {
265+
this.restTemplate.getForEntity(
266+
"/throw-custom-exception", String.class);
267+
}
268+
245269
private void callHandledTypeMismatchExceptionUserSeverityEndpoint() {
246270
this.restTemplate.getForEntity(
247271
"/handled-type-mismatch-exception-user-severity", String.class);

bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestController.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ public void throwTypeMismatchException() {
3333
throw new TypeMismatchException("Test", String.class);
3434
}
3535

36+
/**
37+
* Throw an exception with @ResponseStatus
38+
*/
39+
@RequestMapping("/throw-response-status-exception")
40+
public void throwResponseStatusException() {
41+
throw new TestResponseStatusException();
42+
}
43+
44+
/**
45+
* Throw an exception that is handled by @ExceptionHandler
46+
*/
47+
@RequestMapping("/throw-custom-exception")
48+
public void throwCustomException() {
49+
throw new TestCustomException();
50+
}
51+
3652
/**
3753
* Report a handled exception where the severity reason is exceptionClass
3854
*/
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.bugsnag.testapp.springboot;
2+
3+
public class TestCustomException extends RuntimeException {
4+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.bugsnag.testapp.springboot;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.web.bind.annotation.ControllerAdvice;
5+
import org.springframework.web.bind.annotation.ExceptionHandler;
6+
7+
@ControllerAdvice
8+
public class TestExceptionHandler {
9+
@ExceptionHandler(TestCustomException.class)
10+
public ResponseEntity handleTestCustomException(TestCustomException ignored) {
11+
return ResponseEntity.ok(TestCustomException.class.getSimpleName());
12+
}
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.bugsnag.testapp.springboot;
2+
3+
import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT;
4+
5+
import org.springframework.web.bind.annotation.ResponseStatus;
6+
7+
@ResponseStatus(I_AM_A_TEAPOT)
8+
public class TestResponseStatusException extends RuntimeException {
9+
}

0 commit comments

Comments
 (0)