Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable SentryExceptionResolver Order for getsentry/sentry-java#681 #1008

Merged
merged 19 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ local.properties
.cxx
**/sentry-native-local
target/
.classpath
.project
.settings/
bin/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Fix broken NDK integration on 3.1.2 (release failed on packaging a .so file)
* Increase max cached events to 30 (#1029)
* Normalize DSN URI (#1030)
* Enhancement: Make SentryExceptionResolver Order configurable to not send handled web exceptions
* Enhancement: Resolve HTTP Proxy parameters from the external configuration (#1028)

# 3.1.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.sentry.Sentry;
import io.sentry.SentryOptions;
import io.sentry.protocol.SdkVersion;
import io.sentry.spring.SentryExceptionResolver;
import io.sentry.spring.SentryUserProvider;
import io.sentry.spring.SentryUserProviderEventProcessor;
import io.sentry.spring.SentryWebConfiguration;
Expand Down Expand Up @@ -92,7 +93,14 @@ static class HubConfiguration {
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import(SentryWebConfiguration.class)
@Open
static class SentryWebMvcConfiguration {}
static class SentryWebMvcConfiguration {
@Bean
@ConditionalOnMissingBean
public @NotNull SentryExceptionResolver sentryExceptionResolver(
final @NotNull IHub sentryHub, final @NotNull SentryProperties options) {
return new SentryExceptionResolver(sentryHub, options.getExceptionResolverOrder());
}
}

private static @NotNull SdkVersion createSdkVersion(
final @NotNull SentryOptions sentryOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class SentryProperties extends SentryOptions {
/** Weather to use Git commit id as a release. */
private boolean useGitCommitIdAsRelease = true;

/** Report all or only uncaught web exceptions. */
private int exceptionResolverOrder = Integer.MIN_VALUE;

/** Logging framework integration properties. */
private @NotNull Logging logging = new Logging();

Expand All @@ -26,6 +29,26 @@ public void setUseGitCommitIdAsRelease(boolean useGitCommitIdAsRelease) {
this.useGitCommitIdAsRelease = useGitCommitIdAsRelease;
}

/**
* Returns the order used for Spring SentryExceptionResolver, which determines whether all web
* exceptions are reported, or only uncaught exceptions.
*
* @return order to use for Spring SentryExceptionResolver
*/
public int getExceptionResolverOrder() {
return exceptionResolverOrder;
}

/**
* Sets the order to use for Spring SentryExceptionResolver, which determines whether all web
* exceptions are reported, or only uncaught exceptions.
*
* @param exceptionResolverOrder order to use for Spring SentryExceptionResolver
*/
public void setExceptionResolverOrder(int exceptionResolverOrder) {
this.exceptionResolverOrder = exceptionResolverOrder;
}

public Logging getLogging() {
return logging;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ class SentryAutoConfigurationTest {
"sentry.attach-threads=true",
"sentry.attach-stacktrace=true",
"sentry.server-name=host-001",
"sentry.exception-resolver-order=100",
"sentry.proxy.host=example.proxy.com",
"sentry.proxy.port=8090",
"sentry.proxy.user=proxy-user",
"sentry.proxy.pass=proxy-pass"
).run {
val options = it.getBean(SentryOptions::class.java)
val options = it.getBean(SentryProperties::class.java)
assertThat(options.readTimeoutMillis).isEqualTo(10)
assertThat(options.shutdownTimeout).isEqualTo(20)
assertThat(options.flushTimeoutMillis).isEqualTo(30)
Expand All @@ -114,6 +115,7 @@ class SentryAutoConfigurationTest {
assertThat(options.isAttachThreads).isEqualTo(true)
assertThat(options.isAttachStacktrace).isEqualTo(true)
assertThat(options.serverName).isEqualTo("host-001")
assertThat(options.exceptionResolverOrder).isEqualTo(100)
assertThat(options.proxy).isNotNull()
assertThat(options.proxy!!.host).isEqualTo("example.proxy.com")
assertThat(options.proxy!!.port).isEqualTo("8090")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;

/**
* Enables Sentry error handling capabilities.
Expand Down Expand Up @@ -37,4 +38,11 @@
* @return true if send default PII or false otherwise.
*/
boolean sendDefaultPii() default false;

/**
* Determines whether all web exceptions are reported or only uncaught exceptions.
*
* @return the order to use for {@link SentryExceptionResolver}
*/
int exceptionResolverOrder() default Ordered.HIGHEST_PRECEDENCE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
@Open
public class SentryExceptionResolver implements HandlerExceptionResolver, Ordered {
private final @NotNull IHub hub;
private final int order;

public SentryExceptionResolver(final @NotNull IHub hub) {
public SentryExceptionResolver(final @NotNull IHub hub, final int order) {
this.hub = Objects.requireNonNull(hub, "hub is required");
this.order = order;
}

@Override
Expand All @@ -49,7 +51,6 @@ public SentryExceptionResolver(final @NotNull IHub hub) {

@Override
public int getOrder() {
// ensure this resolver runs first so that all exceptions are reported
return Integer.MIN_VALUE;
return order;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public void registerBeanDefinitions(
if (annotationAttributes != null && annotationAttributes.containsKey("dsn")) {
registerSentryOptions(registry, annotationAttributes);
registerSentryHubBean(registry);
registerSentryExceptionResolver(registry, annotationAttributes);
}
}

Expand Down Expand Up @@ -56,6 +57,18 @@ private void registerSentryHubBean(BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("sentryHub", builder.getBeanDefinition());
}

private void registerSentryExceptionResolver(
final @NotNull BeanDefinitionRegistry registry,
final @NotNull AnnotationAttributes annotationAttributes) {
final BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(SentryExceptionResolver.class);
builder.addConstructorArgReference("sentryHub");
int order = annotationAttributes.getNumber("exceptionResolverOrder");
builder.addConstructorArgValue(order);

registry.registerBeanDefinition("sentryExceptionResolver", builder.getBeanDefinition());
}

private static @NotNull SdkVersion createSdkVersion() {
final SentryOptions defaultOptions = new SentryOptions();
SdkVersion sdkVersion = defaultOptions.getSdkVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,4 @@ public class SentryWebConfiguration {
final @NotNull IHub sentryHub, final @NotNull SentryOptions sentryOptions) {
return new SentrySpringRequestListener(sentryHub, sentryOptions);
}

@Bean
public @NotNull SentryExceptionResolver sentryExceptionResolver(final @NotNull IHub sentryHub) {
return new SentryExceptionResolver(sentryHub);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,21 @@ class EnableSentryTest {
fun `creates SentryExceptionResolver`() {
contextRunner.run {
assertThat(it).hasSingleBean(SentryExceptionResolver::class.java)
assertThat(it).getBean(SentryExceptionResolver::class.java)
.hasFieldOrPropertyWithValue("order", Integer.MIN_VALUE)
}
}

@Test
fun `creates SentryExceptionResolver with order set in the @EnableSentry annotation`() {
ApplicationContextRunner().withConfiguration(UserConfigurations.of(AppConfigWithExceptionResolverOrderIntegerMaxValue::class.java))
.run {
assertThat(it).hasSingleBean(SentryExceptionResolver::class.java)
assertThat(it).getBean(SentryExceptionResolver::class.java)
.hasFieldOrPropertyWithValue("order", Integer.MAX_VALUE)
}
}

@EnableSentry(dsn = "http://key@localhost/proj")
class AppConfig

Expand All @@ -84,4 +96,7 @@ class EnableSentryTest {

@EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true)
class AppConfigWithDefaultSendPii

@EnableSentry(dsn = "http://key@localhost/proj", exceptionResolverOrder = Integer.MAX_VALUE)
class AppConfigWithExceptionResolverOrderIntegerMaxValue
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package io.sentry.spring
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import io.sentry.Sentry
import io.sentry.test.checkEvent
import io.sentry.transport.ITransport
import java.lang.RuntimeException
import java.time.Duration
import org.assertj.core.api.Assertions.assertThat
import org.awaitility.kotlin.await
import org.junit.Before
Expand All @@ -19,9 +21,11 @@ import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.web.server.LocalServerPort
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.ResponseEntity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.userdetails.User
Expand All @@ -31,6 +35,8 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

Expand Down Expand Up @@ -103,10 +109,21 @@ class SentrySpringIntegrationTest {
})
}
}

@Test
fun `does not send events for handled exceptions`() {
val restTemplate = TestRestTemplate().withBasicAuth("user", "password")

restTemplate.getForEntity("http://localhost:$port/throws-handled", String::class.java)

await.during(Duration.ofSeconds(2)).untilAsserted {
verifyZeroInteractions(transport)
}
}
}

@SpringBootApplication
@EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true)
@EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true, exceptionResolverOrder = Ordered.LOWEST_PRECEDENCE)
open class App {

@Bean
Expand All @@ -125,6 +142,20 @@ class HelloController {
fun throws() {
throw RuntimeException("something went wrong")
}

@GetMapping("/throws-handled")
fun throwsHandled() {
throw CustomException("handled exception")
}
}

class CustomException(message: String) : RuntimeException(message)

@ControllerAdvice
class ExceptionHandlers {

@ExceptionHandler(CustomException::class)
fun handle(e: CustomException) = ResponseEntity.badRequest().build<Void>()
}

@Configuration
Expand Down