From a0ea2037058355aa72655ba6562d00a0ab6e476b Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 3 Sep 2020 21:42:40 +0200 Subject: [PATCH 01/20] Add Spring Integration. Many of beans previously instantiated in Spring Boot integration move to Spring integration. Spring Boot integration enables more fine grained SentryOptions configuration, simple way to register custom event processors, passing git commit id as the event release version. --- buildSrc/src/main/java/Config.kt | 1 + .../samples/spring/SecurityConfiguration.java | 2 +- .../src/main/resources/application.properties | 3 + .../sentry-samples-spring/build.gradle.kts | 40 +++++ .../java/io/sentry/samples/spring/Person.java | 24 +++ .../samples/spring/PersonController.java | 27 ++++ .../samples/spring/SecurityConfiguration.java | 60 +++++++ .../samples/spring/SentryDemoApplication.java | 14 ++ .../src/main/resources/application.properties | 3 + .../src/main/resources/logback.xml | 16 ++ sentry-spring-boot-starter/build.gradle.kts | 1 + .../spring/boot/SentryAutoConfiguration.java | 17 +- .../boot/SentrySpringIntegrationTest.kt | 21 +++ sentry-spring/build.gradle.kts | 118 ++++++++++++++ .../java/io/sentry/spring/EnableSentry.java | 21 +++ .../spring/SentryCoreConfiguration.java | 71 +++++++++ .../spring/SentryExceptionResolver.java | 50 ++++++ .../sentry/spring}/SentryRequestFilter.java | 15 +- ...tryRequestHttpServletRequestProcessor.java | 7 +- .../sentry/spring}/SentrySecurityFilter.java | 7 +- ...SentryUserHttpServletRequestProcessor.java | 2 +- .../sentry/spring/SentryWebConfiguration.java | 26 +++ .../io/sentry/spring/EnableSentryTest.kt | 80 ++++++++++ ...yRequestHttpServletRequestProcessorTest.kt | 2 +- .../spring/SentrySpringIntegrationTest.kt | 148 ++++++++++++++++++ ...ntryUserHttpServletRequestProcessorTest.kt | 2 +- settings.gradle.kts | 2 + 27 files changed, 753 insertions(+), 27 deletions(-) create mode 100644 sentry-samples/sentry-samples-spring/build.gradle.kts create mode 100644 sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java create mode 100644 sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java create mode 100644 sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java create mode 100644 sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java create mode 100644 sentry-samples/sentry-samples-spring/src/main/resources/application.properties create mode 100644 sentry-samples/sentry-samples-spring/src/main/resources/logback.xml create mode 100644 sentry-spring/build.gradle.kts create mode 100644 sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java rename {sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot => sentry-spring/src/main/java/io/sentry/spring}/SentryRequestFilter.java (73%) rename {sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot => sentry-spring/src/main/java/io/sentry/spring}/SentryRequestHttpServletRequestProcessor.java (92%) rename {sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot => sentry-spring/src/main/java/io/sentry/spring}/SentrySecurityFilter.java (90%) rename {sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot => sentry-spring/src/main/java/io/sentry/spring}/SentryUserHttpServletRequestProcessor.java (97%) create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java create mode 100644 sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt rename {sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot => sentry-spring/src/test/kotlin/io/sentry/spring}/SentryRequestHttpServletRequestProcessorTest.kt (99%) create mode 100644 sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt rename {sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot => sentry-spring/src/test/kotlin/io/sentry/spring}/SentryUserHttpServletRequestProcessorTest.kt (98%) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 9246dbb3d..e2a1518f1 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -89,6 +89,7 @@ object Config { val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android" val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback" val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2" + val SENTRY_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring" val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot" val group = "io.sentry" val description = "SDK for sentry.io" diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java index 5d8173881..7c3c87acf 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java @@ -2,7 +2,7 @@ import io.sentry.core.IHub; import io.sentry.core.SentryOptions; -import io.sentry.spring.boot.SentrySecurityFilter; +import io.sentry.spring.SentrySecurityFilter; import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index 9f28892b0..0f1441584 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -1,3 +1,6 @@ # NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 sentry.send-default-pii=true +# Sentry Spring Boot integration allows more fine-grained SentryOptions configuration +sentry.max-breadcrumbs=10 +sentry.bypass-security=true diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts new file mode 100644 index 000000000..44825e82a --- /dev/null +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -0,0 +1,40 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id(Config.BuildPlugins.springBoot) version Config.springBootVersion + id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion + kotlin("jvm") + kotlin("plugin.spring") version Config.kotlinVersion +} + +group = "io.sentry.sample.spring-boot" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation(project(":sentry-spring")) + implementation(project(":sentry-logback")) + testImplementation("org.springframework.boot:spring-boot-starter-test") { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } +} + +tasks.withType { + useJUnitPlatform() +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = JavaVersion.VERSION_1_8.toString() + } +} diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java new file mode 100644 index 000000000..f4588a7f6 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java @@ -0,0 +1,24 @@ +package io.sentry.samples.spring; + +public class Person { + private final String firstName; + private final String lastName; + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + @Override + public String toString() { + return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}'; + } +} diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java new file mode 100644 index 000000000..9a2d97316 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java @@ -0,0 +1,27 @@ +package io.sentry.samples.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/person/") +public class PersonController { + private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); + + @GetMapping("{id}") + Person person(@PathVariable Long id) { + throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); + } + + @PostMapping + Person create(@RequestBody Person person) { + LOGGER.warn("Creating person: {}", person); + return person; + } +} diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java new file mode 100644 index 000000000..7c3c87acf --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java @@ -0,0 +1,60 @@ +package io.sentry.samples.spring; + +import io.sentry.core.IHub; +import io.sentry.core.SentryOptions; +import io.sentry.spring.SentrySecurityFilter; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; + +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + private final @NotNull IHub hub; + private final @NotNull SentryOptions options; + + public SecurityConfiguration(final @NotNull IHub hub, final @NotNull SentryOptions options) { + this.hub = hub; + this.options = options; + } + + // this API is meant to be consumed by non-browser clients thus the CSRF protection is not needed. + @Override + @SuppressWarnings("lgtm[java/spring-disabled-csrf-protection]") + protected void configure(final @NotNull HttpSecurity http) throws Exception { + // register SentrySecurityFilter to attach user information to SentryEvents + http.addFilterAfter(new SentrySecurityFilter(hub, options), AnonymousAuthenticationFilter.class) + .csrf() + .disable() + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .httpBasic(); + } + + @Bean + @Override + public @NotNull UserDetailsService userDetailsService() { + final PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); + + final UserDetails user = + User.builder() + .passwordEncoder(encoder::encode) + .username("user") + .password("password") + .roles("USER") + .build(); + + return new InMemoryUserDetailsManager(user); + } +} diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java new file mode 100644 index 000000000..4a17d70e3 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java @@ -0,0 +1,14 @@ +package io.sentry.samples.spring; + +import io.sentry.spring.EnableSentry; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableSentry +public class SentryDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SentryDemoApplication.class, args); + } +} diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring/src/main/resources/application.properties new file mode 100644 index 000000000..9f28892b0 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard +sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 +sentry.send-default-pii=true diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml new file mode 100644 index 000000000..fe050c21c --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + + + WARN + + + + + + + + diff --git a/sentry-spring-boot-starter/build.gradle.kts b/sentry-spring-boot-starter/build.gradle.kts index 100ea7aee..1282f1539 100644 --- a/sentry-spring-boot-starter/build.gradle.kts +++ b/sentry-spring-boot-starter/build.gradle.kts @@ -33,6 +33,7 @@ tasks.withType().configureEach { dependencies { api(project(":sentry-core")) + api(project(":sentry-spring")) implementation(Config.Libs.springBootStarter) implementation(Config.Libs.springWeb) implementation(Config.Libs.servletApi) diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index 4f768765d..5dd2feda8 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -10,6 +10,7 @@ import io.sentry.core.protocol.SdkVersion; import io.sentry.core.transport.ITransport; import io.sentry.core.transport.ITransportGate; +import io.sentry.spring.SentryWebConfiguration; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -17,10 +18,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.info.GitProperties; -import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; +import org.springframework.context.annotation.Import; @Configuration @ConditionalOnProperty(name = "sentry.dsn") @@ -75,18 +75,9 @@ static class HubConfiguration { /** Registers beans specific to Spring MVC. */ @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + @Import(SentryWebConfiguration.class) @Open - static class SentryWebMvcConfiguration { - - @Bean - public @NotNull FilterRegistrationBean sentryRequestFilter( - final @NotNull IHub sentryHub, final @NotNull SentryOptions sentryOptions) { - FilterRegistrationBean filterRegistrationBean = - new FilterRegistrationBean<>(new SentryRequestFilter(sentryHub, sentryOptions)); - filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); - return filterRegistrationBean; - } - } + static class SentryWebMvcConfiguration {} private static @NotNull SdkVersion createSdkVersion( final @NotNull SentryOptions sentryOptions) { diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt index e5d0cd1ee..514fc2986 100644 --- a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt +++ b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt @@ -7,6 +7,8 @@ import io.sentry.core.Sentry import io.sentry.core.SentryEvent import io.sentry.core.SentryOptions import io.sentry.core.transport.ITransport +import io.sentry.spring.SentrySecurityFilter +import java.lang.RuntimeException import org.assertj.core.api.Assertions.assertThat import org.awaitility.kotlin.await import org.junit.Test @@ -83,6 +85,20 @@ class SentrySpringIntegrationTest { }) } } + + @Test + fun `sends events for unhandled exceptions`() { + val restTemplate = TestRestTemplate().withBasicAuth("user", "password") + + restTemplate.getForEntity("http://localhost:$port/throws", String::class.java) + + await.untilAsserted { + verify(transport).send(check { event: SentryEvent -> + assertThat(event.throwable).isNotNull() + assertThat(event.throwable!!.message).isEqualTo("something went wrong") + }) + } + } } @SpringBootApplication @@ -95,6 +111,11 @@ class HelloController { fun hello() { Sentry.captureMessage("hello") } + + @GetMapping("/throws") + fun throws() { + throw RuntimeException("something went wrong") + } } @Configuration diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts new file mode 100644 index 000000000..49ccda888 --- /dev/null +++ b/sentry-spring/build.gradle.kts @@ -0,0 +1,118 @@ +import com.novoda.gradle.release.PublishExtension +import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.springframework.boot.gradle.plugin.SpringBootPlugin + +plugins { + `java-library` + kotlin("jvm") + jacoco + id(Config.QualityPlugins.errorProne) + id(Config.Deploy.novodaBintray) + id(Config.QualityPlugins.gradleVersions) + id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion + id(Config.BuildPlugins.springBoot) version Config.springBootVersion apply false +} + +apply(plugin = Config.BuildPlugins.springDependencyManagement) + +the().apply { + imports { + mavenBom(SpringBootPlugin.BOM_COORDINATES) + } +} + +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType().configureEach { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() +} + +dependencies { + api(project(":sentry-core")) + implementation(Config.Libs.springWeb) + implementation(Config.Libs.servletApi) + + compileOnly(Config.CompileOnly.nopen) + errorprone(Config.CompileOnly.nopenChecker) + errorprone(Config.CompileOnly.errorprone) + errorproneJavac(Config.CompileOnly.errorProneJavac8) + compileOnly(Config.CompileOnly.jetbrainsAnnotations) + + // tests + testImplementation(kotlin(Config.kotlinStdLib)) + testImplementation(Config.TestLibs.kotlinTestJunit) + testImplementation(Config.TestLibs.mockitoKotlin) + testImplementation(Config.TestLibs.springBootStarterTest) + testImplementation(Config.TestLibs.springBootStarterWeb) + testImplementation(Config.TestLibs.springBootStarterSecurity) + testImplementation(Config.TestLibs.awaitility) +} + +configure { + test { + java.srcDir("src/test/java") + } +} + +jacoco { + toolVersion = Config.QualityPlugins.jacocoVersion +} + +tasks.jacocoTestReport { + reports { + xml.isEnabled = true + html.isEnabled = false + } +} + +tasks { + jacocoTestCoverageVerification { + violationRules { + rule { limit { minimum = BigDecimal.valueOf(0.6) } } + } + } + check { + dependsOn(jacocoTestCoverageVerification) + dependsOn(jacocoTestReport) + } +} + +buildConfig { + useJavaOutput() + packageName("io.sentry.spring") + buildConfigField("String", "SENTRY_SPRING_SDK_NAME", "\"${Config.Sentry.SENTRY_SPRING_SDK_NAME}\"") + buildConfigField("String", "VERSION_NAME", "\"${project.version}\"") +} + +val generateBuildConfig by tasks +tasks.withType().configureEach { + dependsOn(generateBuildConfig) +} + +//TODO: move these blocks to parent gradle file, DRY +configure { + userOrg = Config.Sentry.userOrg + groupId = project.group.toString() + publishVersion = project.version.toString() + desc = Config.Sentry.description + website = Config.Sentry.website + repoName = Config.Sentry.repoName + setLicences(Config.Sentry.licence) + setLicenceUrls(Config.Sentry.licenceUrl) + issueTracker = Config.Sentry.issueTracker + repository = Config.Sentry.repository + sign = Config.Deploy.sign + artifactId = project.name + uploadName = "${project.group}:${project.name}" + devId = Config.Sentry.userOrg + devName = Config.Sentry.devName + devEmail = Config.Sentry.devEmail + scmConnection = Config.Sentry.scmConnection + scmDevConnection = Config.Sentry.scmDevConnection + scmUrl = Config.Sentry.scmUrl +} + diff --git a/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java b/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java new file mode 100644 index 000000000..32b1531f2 --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java @@ -0,0 +1,21 @@ +package io.sentry.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.context.annotation.Import; + +/** + * Enables Sentry error handling capabilities. + * + *

- creates bean of type {@link io.sentry.core.SentryOptions} using properties from Spring + * {@link org.springframework.core.env.Environment}. - registers {@link io.sentry.core.IHub} for + * sending Sentry events - registers {@link SentryRequestFilter} for attaching request information + * to Sentry events - registers {@link SentryExceptionResolver} to send Sentry event for any + * uncaught exception in Spring MVC flow. + */ +@Retention(RetentionPolicy.RUNTIME) +@Import({SentryCoreConfiguration.class, SentryWebConfiguration.class}) +@Target(ElementType.TYPE) +public @interface EnableSentry {} diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java new file mode 100644 index 000000000..4438ca5ce --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java @@ -0,0 +1,71 @@ +package io.sentry.spring; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.HubAdapter; +import io.sentry.core.IHub; +import io.sentry.core.Sentry; +import io.sentry.core.SentryOptions; +import io.sentry.core.protocol.SdkVersion; +import io.sentry.core.transport.ITransport; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** Registers beans required to use Sentry core features. */ +@Configuration +@Open +public class SentryCoreConfiguration { + + /** + * Creates {@link SentryOptions} using properties from Spring {@link Environment}. + * + * @param environment - the environment + * @param transport - optional Sentry transport - used primarily in testing scenarios + * @return SentryOptions + */ + @Bean + public @NotNull SentryOptions sentryOptions( + final @NotNull Environment environment, final @NotNull ObjectProvider transport) { + final SentryOptions options = new SentryOptions(); + options.setDsn(environment.getProperty("sentry.dsn")); + options.setSdkVersion(createSdkVersion(options)); + + final String sendDefaultPii = environment.getProperty("sentry.send-default-pii"); + if (sendDefaultPii != null) { + options.setSendDefaultPii(Boolean.parseBoolean(sendDefaultPii)); + } + final String enableUncaughtExceptionHandler = + environment.getProperty("sentry.enable-uncaught-exception-handler"); + if (enableUncaughtExceptionHandler != null) { + options.setEnableUncaughtExceptionHandler( + Boolean.parseBoolean(enableUncaughtExceptionHandler)); + } + transport.ifAvailable(options::setTransport); + return options; + } + + @Bean + public @NotNull IHub sentryHub(final @NotNull SentryOptions options) { + options.setSentryClientName(BuildConfig.SENTRY_SPRING_SDK_NAME); + options.setSdkVersion(createSdkVersion(options)); + Sentry.init(options); + return HubAdapter.getInstance(); + } + + private static @NotNull SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) { + SdkVersion sdkVersion = sentryOptions.getSdkVersion(); + + if (sdkVersion == null) { + sdkVersion = new SdkVersion(); + } + + sdkVersion.setName(BuildConfig.SENTRY_SPRING_SDK_NAME); + final String version = BuildConfig.VERSION_NAME; + sdkVersion.setVersion(version); + sdkVersion.addPackage("maven:sentry-spring", version); + + return sdkVersion; + } +} diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java new file mode 100644 index 000000000..e7bcda5e9 --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java @@ -0,0 +1,50 @@ +package io.sentry.spring; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.IHub; +import io.sentry.core.SentryOptions; +import io.sentry.core.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.core.Ordered; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; + +/** + * {@link HandlerExceptionResolver} implementation that will record any exception that a Spring + * {@link org.springframework.web.servlet.mvc.Controller} throws to Sentry. It then returns null, + * which will let the other (default or custom) exception resolvers handle the actual error. + */ +@Open +public class SentryExceptionResolver implements HandlerExceptionResolver, Ordered { + private final @NotNull IHub hub; + private final @NotNull SentryOptions options; + + public SentryExceptionResolver(final @NotNull IHub hub, final @NotNull SentryOptions options) { + this.hub = Objects.requireNonNull(hub, "hub is required"); + this.options = Objects.requireNonNull(options, "options are required"); + } + + @Override + public @Nullable ModelAndView resolveException( + final @NotNull HttpServletRequest request, + final @NotNull HttpServletResponse response, + final @Nullable Object handler, + final @NotNull Exception ex) { + + if (options.isEnableUncaughtExceptionHandler()) { + hub.captureException(ex); + } + + // null = run other HandlerExceptionResolvers to actually handle the exception + return null; + } + + @Override + public int getOrder() { + // ensure this resolver runs first so that all exceptions are reported + return Integer.MIN_VALUE; + } +} diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java similarity index 73% rename from sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryRequestFilter.java rename to sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java index fe0fb81aa..0a0be70b9 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryRequestFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java @@ -1,25 +1,27 @@ -package io.sentry.spring.boot; +package io.sentry.spring; import com.jakewharton.nopen.annotation.Open; import io.sentry.core.IHub; import io.sentry.core.SentryOptions; +import io.sentry.core.util.Objects; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jetbrains.annotations.NotNull; +import org.springframework.core.Ordered; import org.springframework.web.filter.OncePerRequestFilter; /** Pushes new {@link io.sentry.core.Scope} on each incoming HTTP request. */ @Open -public class SentryRequestFilter extends OncePerRequestFilter { +public class SentryRequestFilter extends OncePerRequestFilter implements Ordered { private final @NotNull IHub hub; private final @NotNull SentryOptions options; public SentryRequestFilter(final @NotNull IHub hub, final @NotNull SentryOptions options) { - this.hub = hub; - this.options = options; + this.hub = Objects.requireNonNull(hub, "hub is required"); + this.options = Objects.requireNonNull(options, "options are required"); } @Override @@ -36,4 +38,9 @@ protected void doFilterInternal( }); filterChain.doFilter(request, response); } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } } diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryRequestHttpServletRequestProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestHttpServletRequestProcessor.java similarity index 92% rename from sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryRequestHttpServletRequestProcessor.java rename to sentry-spring/src/main/java/io/sentry/spring/SentryRequestHttpServletRequestProcessor.java index 7b718377f..ffcb5e2d0 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryRequestHttpServletRequestProcessor.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestHttpServletRequestProcessor.java @@ -1,10 +1,11 @@ -package io.sentry.spring.boot; +package io.sentry.spring; import com.jakewharton.nopen.annotation.Open; import io.sentry.core.EventProcessor; import io.sentry.core.SentryEvent; import io.sentry.core.SentryOptions; import io.sentry.core.protocol.Request; +import io.sentry.core.util.Objects; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -26,8 +27,8 @@ public class SentryRequestHttpServletRequestProcessor implements EventProcessor public SentryRequestHttpServletRequestProcessor( final @NotNull HttpServletRequest request, final @NotNull SentryOptions options) { - this.request = request; - this.options = options; + this.request = Objects.requireNonNull(request, "request is required"); + this.options = Objects.requireNonNull(options, "options are required"); } @Override diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentrySecurityFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentrySecurityFilter.java similarity index 90% rename from sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentrySecurityFilter.java rename to sentry-spring/src/main/java/io/sentry/spring/SentrySecurityFilter.java index 546e8dd79..3d0d8d7ea 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentrySecurityFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentrySecurityFilter.java @@ -1,8 +1,9 @@ -package io.sentry.spring.boot; +package io.sentry.spring; import com.jakewharton.nopen.annotation.Open; import io.sentry.core.IHub; import io.sentry.core.SentryOptions; +import io.sentry.core.util.Objects; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -21,8 +22,8 @@ public class SentrySecurityFilter extends OncePerRequestFilter { private final @NotNull SentryOptions options; public SentrySecurityFilter(final @NotNull IHub hub, final @NotNull SentryOptions options) { - this.hub = hub; - this.options = options; + this.hub = Objects.requireNonNull(hub, "hub is required"); + this.options = Objects.requireNonNull(options, "options are required"); } @Override diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryUserHttpServletRequestProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SentryUserHttpServletRequestProcessor.java similarity index 97% rename from sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryUserHttpServletRequestProcessor.java rename to sentry-spring/src/main/java/io/sentry/spring/SentryUserHttpServletRequestProcessor.java index c9bfff93e..1e5fbf72b 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryUserHttpServletRequestProcessor.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryUserHttpServletRequestProcessor.java @@ -1,4 +1,4 @@ -package io.sentry.spring.boot; +package io.sentry.spring; import com.jakewharton.nopen.annotation.Open; import io.sentry.core.EventProcessor; diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java new file mode 100644 index 000000000..5f646903a --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java @@ -0,0 +1,26 @@ +package io.sentry.spring; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.IHub; +import io.sentry.core.SentryOptions; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** Registers Spring Web specific Sentry beans. */ +@Configuration +@Open +public class SentryWebConfiguration { + + @Bean + public @NotNull SentryRequestFilter sentryRequestFilter( + final @NotNull IHub sentryHub, final @NotNull SentryOptions sentryOptions) { + return new SentryRequestFilter(sentryHub, sentryOptions); + } + + @Bean + public @NotNull SentryExceptionResolver sentryExceptionResolver( + final @NotNull IHub sentryHub, final @NotNull SentryOptions options) { + return new SentryExceptionResolver(sentryHub, options); + } +} diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt new file mode 100644 index 000000000..46060ca8b --- /dev/null +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt @@ -0,0 +1,80 @@ +package io.sentry.spring + +import io.sentry.core.IHub +import io.sentry.core.SentryOptions +import kotlin.test.Test +import org.assertj.core.api.Assertions.assertThat +import org.springframework.boot.context.annotation.UserConfigurations +import org.springframework.boot.test.context.runner.ApplicationContextRunner + +class EnableSentryTest { + private val contextRunner = ApplicationContextRunner() + .withConfiguration(UserConfigurations.of(AppConfig::class.java)) + + @Test + fun `sets properties from environment on SentryOptions`() { + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.send-default-pii=true", + "sentry.enable-uncaught-exception-handler=true").run { + assertThat(it).hasSingleBean(SentryOptions::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.dsn).isEqualTo("http://key@localhost/proj") + assertThat(options.isSendDefaultPii).isTrue() + assertThat(options.isEnableUncaughtExceptionHandler).isTrue() + } + + contextRunner.withPropertyValues( + "sentry.dsn=", + "sentry.send-default-pii=false", + "sentry.enable-uncaught-exception-handler=false").run { + assertThat(it).hasSingleBean(SentryOptions::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.dsn).isEmpty() + assertThat(options.isSendDefaultPii).isFalse() + assertThat(options.isEnableUncaughtExceptionHandler).isFalse() + } + } + + @Test + fun `sets client name and SDK version`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it).hasSingleBean(SentryOptions::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.sentryClientName).isEqualTo("sentry.java.spring") + assertThat(options.sdkVersion).isNotNull + assertThat(options.sdkVersion!!.name).isEqualTo("sentry.java.spring") + assertThat(options.sdkVersion!!.version).isEqualTo(BuildConfig.VERSION_NAME) + assertThat(options.sdkVersion!!.packages).isNotNull + assertThat(options.sdkVersion!!.packages!!.map { pkg -> pkg.name }).contains("maven:sentry-spring") + } + } + + @Test + fun `creates Sentry Hub`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it).hasSingleBean(IHub::class.java) + } + } + + @Test + fun `creates SentryRequestFilter`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it).hasSingleBean(SentryRequestFilter::class.java) + } + } + + @Test + fun `creates SentryExceptionResolver`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it).hasSingleBean(SentryExceptionResolver::class.java) + } + } + + @EnableSentry + class AppConfig +} diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryRequestHttpServletRequestProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryRequestHttpServletRequestProcessorTest.kt similarity index 99% rename from sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryRequestHttpServletRequestProcessorTest.kt rename to sentry-spring/src/test/kotlin/io/sentry/spring/SentryRequestHttpServletRequestProcessorTest.kt index ba709d6c9..ca33e331d 100644 --- a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryRequestHttpServletRequestProcessorTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryRequestHttpServletRequestProcessorTest.kt @@ -1,4 +1,4 @@ -package io.sentry.spring.boot +package io.sentry.spring import io.sentry.core.SentryEvent import io.sentry.core.SentryOptions diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt new file mode 100644 index 000000000..aa3b18e02 --- /dev/null +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt @@ -0,0 +1,148 @@ +package io.sentry.spring + +import com.nhaarman.mockitokotlin2.check +import com.nhaarman.mockitokotlin2.verify +import io.sentry.core.IHub +import io.sentry.core.Sentry +import io.sentry.core.SentryEvent +import io.sentry.core.SentryOptions +import io.sentry.core.transport.ITransport +import java.lang.RuntimeException +import org.assertj.core.api.Assertions.assertThat +import org.awaitility.kotlin.await +import org.junit.Test +import org.junit.runner.RunWith +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.mock.mockito.MockBean +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.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +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 +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.factory.PasswordEncoderFactories +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.provisioning.InMemoryUserDetailsManager +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter +import org.springframework.test.context.junit4.SpringRunner +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RunWith(SpringRunner::class) +@SpringBootTest( + classes = [App::class], + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = ["sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true"] +) +class SentrySpringIntegrationTest { + + @MockBean + lateinit var transport: ITransport + + @LocalServerPort + lateinit var port: Integer + + @Test + fun `attaches request and user information to SentryEvents`() { + val restTemplate = TestRestTemplate().withBasicAuth("user", "password") + val headers = HttpHeaders() + headers["X-FORWARDED-FOR"] = listOf("169.128.0.1") + val entity = HttpEntity(headers) + + restTemplate.exchange("http://localhost:$port/hello", HttpMethod.GET, entity, Void::class.java) + + await.untilAsserted { + verify(transport).send(check { event: SentryEvent -> + assertThat(event.request).isNotNull() + assertThat(event.request.url).isEqualTo("http://localhost:$port/hello") + assertThat(event.user).isNotNull() + assertThat(event.user.username).isEqualTo("user") + assertThat(event.user.ipAddress).isEqualTo("169.128.0.1") + }) + } + } + + @Test + fun `attaches first ip address if multiple addresses exist in a header`() { + val restTemplate = TestRestTemplate().withBasicAuth("user", "password") + val headers = HttpHeaders() + headers["X-FORWARDED-FOR"] = listOf("169.128.0.1, 192.168.0.1") + val entity = HttpEntity(headers) + + restTemplate.exchange("http://localhost:$port/hello", HttpMethod.GET, entity, Void::class.java) + + await.untilAsserted { + verify(transport).send(check { event: SentryEvent -> + assertThat(event.user.ipAddress).isEqualTo("169.128.0.1") + }) + } + } + + @Test + fun `sends events for unhandled exceptions`() { + val restTemplate = TestRestTemplate().withBasicAuth("user", "password") + + restTemplate.getForEntity("http://localhost:$port/throws", String::class.java) + + await.untilAsserted { + verify(transport).send(check { event: SentryEvent -> + assertThat(event.throwable).isNotNull() + assertThat(event.throwable!!.message).isEqualTo("something went wrong") + }) + } + } +} + +@SpringBootApplication +@EnableSentry +open class App + +@RestController +class HelloController { + + @GetMapping("/hello") + fun hello() { + Sentry.captureMessage("hello") + } + + @GetMapping("/throws") + fun throws() { + throw RuntimeException("something went wrong") + } +} + +@Configuration +open class SecurityConfiguration( + private val hub: IHub, + private val options: SentryOptions +) : WebSecurityConfigurerAdapter() { + + override fun configure(http: HttpSecurity) { + http + .addFilterAfter(SentrySecurityFilter(hub, options), AnonymousAuthenticationFilter::class.java) + .csrf().disable() + .authorizeRequests().anyRequest().authenticated() + .and() + .httpBasic() + } + + @Bean + override fun userDetailsService(): UserDetailsService { + val encoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder() + val user: UserDetails = User + .builder() + .passwordEncoder { rawPassword -> encoder.encode(rawPassword) } + .username("user") + .password("password") + .roles("USER") + .build() + return InMemoryUserDetailsManager(user) + } +} diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryUserHttpServletRequestProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserHttpServletRequestProcessorTest.kt similarity index 98% rename from sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryUserHttpServletRequestProcessorTest.kt rename to sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserHttpServletRequestProcessorTest.kt index 9fa3fe46e..6a8e2598b 100644 --- a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryUserHttpServletRequestProcessorTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserHttpServletRequestProcessorTest.kt @@ -1,4 +1,4 @@ -package io.sentry.spring.boot +package io.sentry.spring import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever diff --git a/settings.gradle.kts b/settings.gradle.kts index 1a05fde78..efd3fce0f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,10 +7,12 @@ include("sentry-android", "sentry-core", "sentry-log4j2", "sentry-logback", + "sentry-spring", "sentry-spring-boot-starter", "sentry-android-timber", "sentry-samples:sentry-samples-android", "sentry-samples:sentry-samples-console", "sentry-samples:sentry-samples-log4j2", "sentry-samples:sentry-samples-logback", + "sentry-samples:sentry-samples-spring", "sentry-samples:sentry-samples-spring-boot") From 0bee84f4de85a4ed21bb5dd2af799287afa807b3 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 3 Sep 2020 21:48:15 +0200 Subject: [PATCH 02/20] Polish. --- sentry-samples/sentry-samples-spring/build.gradle.kts | 2 +- .../src/main/resources/application.properties | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index 44825e82a..84aa42b24 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -7,7 +7,7 @@ plugins { kotlin("plugin.spring") version Config.kotlinVersion } -group = "io.sentry.sample.spring-boot" +group = "io.sentry.sample.spring" version = "0.0.1-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring/src/main/resources/application.properties index 9f28892b0..b05b4202c 100644 --- a/sentry-samples/sentry-samples-spring/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring/src/main/resources/application.properties @@ -1,3 +1,6 @@ # NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 sentry.send-default-pii=true + +# Uncaught exception handler should be disabled if both Sentry Spring and Logback integrations are enabled +sentry.enable-uncaught-exception-handler=false From c3f1a7cf6335768374a4a31a4bc23fc9905f2b1b Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 3 Sep 2020 22:14:19 +0200 Subject: [PATCH 03/20] Add custom EventProcessor to Spring Boot sample. --- .../samples/spring/CustomEventProcessor.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java new file mode 100644 index 000000000..4a9bae01a --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java @@ -0,0 +1,18 @@ +package io.sentry.samples.spring; + +import io.sentry.core.EventProcessor; +import io.sentry.core.SentryEvent; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Component; + +/** + * Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are sent to Sentry. + */ +@Component +public class CustomEventProcessor implements EventProcessor { + @Override + public SentryEvent process(SentryEvent event, @Nullable Object hint) { + event.setTag("Java Version", System.getProperty("java.version")); + return event; + } +} From cd37677d2796e715ef518af1e6850b252bf7d047 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 3 Sep 2020 22:17:08 +0200 Subject: [PATCH 04/20] Clear breadcrumbs added during application startup. --- .../java/io/sentry/samples/spring/PersonController.java | 1 + .../sentry-samples-spring/src/main/resources/logback.xml | 6 +----- .../src/main/java/io/sentry/spring/SentryRequestFilter.java | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java index 9a2d97316..98aa10c0e 100644 --- a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java @@ -16,6 +16,7 @@ public class PersonController { @GetMapping("{id}") Person person(@PathVariable Long id) { + LOGGER.info("Loading person with id={}", id); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml index fe050c21c..21097b591 100644 --- a/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml @@ -3,11 +3,7 @@ - - - WARN - - + diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java index 0a0be70b9..bd69748ac 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java @@ -31,6 +31,8 @@ protected void doFilterInternal( final @NotNull FilterChain filterChain) throws ServletException, IOException { hub.pushScope(); + // clears breadcrumbs that may have been added during application startup through one of the logging integrations. + hub.clearBreadcrumbs(); hub.configureScope( scope -> { From 3fcb157ab8391cc3932a3afad93b88f56c351093 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 3 Sep 2020 23:09:15 +0200 Subject: [PATCH 05/20] Polish. --- .../samples/spring/{ => boot}/CustomEventProcessor.java | 2 +- .../java/io/sentry/samples/spring/{ => boot}/Person.java | 2 +- .../sentry/samples/spring/{ => boot}/PersonController.java | 3 ++- .../samples/spring/{ => boot}/SecurityConfiguration.java | 2 +- .../samples/spring/{ => boot}/SentryDemoApplication.java | 2 +- .../src/main/resources/application.properties | 3 +++ .../src/main/resources/logback.xml | 4 +--- .../sentry-samples-spring/src/main/resources/logback.xml | 4 +++- .../src/main/java/io/sentry/spring/SentryRequestFilter.java | 5 +++++ 9 files changed, 18 insertions(+), 9 deletions(-) rename sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/{ => boot}/CustomEventProcessor.java (93%) rename sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/{ => boot}/Person.java (92%) rename sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/{ => boot}/PersonController.java (90%) rename sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/{ => boot}/SecurityConfiguration.java (98%) rename sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/{ => boot}/SentryDemoApplication.java (88%) diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java similarity index 93% rename from sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java rename to sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java index 4a9bae01a..8165b7feb 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/CustomEventProcessor.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java @@ -1,4 +1,4 @@ -package io.sentry.samples.spring; +package io.sentry.samples.spring.boot; import io.sentry.core.EventProcessor; import io.sentry.core.SentryEvent; diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/Person.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/Person.java similarity index 92% rename from sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/Person.java rename to sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/Person.java index f4588a7f6..2a2177d46 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/Person.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/Person.java @@ -1,4 +1,4 @@ -package io.sentry.samples.spring; +package io.sentry.samples.spring.boot; public class Person { private final String firstName; diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/PersonController.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/PersonController.java similarity index 90% rename from sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/PersonController.java rename to sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/PersonController.java index 9a2d97316..7ee2e4604 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/PersonController.java @@ -1,4 +1,4 @@ -package io.sentry.samples.spring; +package io.sentry.samples.spring.boot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,6 +16,7 @@ public class PersonController { @GetMapping("{id}") Person person(@PathVariable Long id) { + LOGGER.info("Loading person with id={}", id); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java similarity index 98% rename from sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java rename to sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java index 7c3c87acf..417f0541d 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java @@ -1,4 +1,4 @@ -package io.sentry.samples.spring; +package io.sentry.samples.spring.boot; import io.sentry.core.IHub; import io.sentry.core.SentryOptions; diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java similarity index 88% rename from sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java rename to sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java index 43882fe6c..1044f1ad6 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java @@ -1,4 +1,4 @@ -package io.sentry.samples.spring; +package io.sentry.samples.spring.boot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index 0f1441584..b6253dfc1 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -4,3 +4,6 @@ sentry.send-default-pii=true # Sentry Spring Boot integration allows more fine-grained SentryOptions configuration sentry.max-breadcrumbs=10 sentry.bypass-security=true + +# Uncaught exception handler should be disabled if both Sentry Spring and Logback integrations are enabled +sentry.enable-uncaught-exception-handler=false diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml b/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml index fe050c21c..26c88f349 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml @@ -4,9 +4,7 @@ - - WARN - + WARN diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml index 21097b591..26c88f349 100644 --- a/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml @@ -3,7 +3,9 @@ - + + WARN + diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java index bd69748ac..4493247a8 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java @@ -33,6 +33,7 @@ protected void doFilterInternal( hub.pushScope(); // clears breadcrumbs that may have been added during application startup through one of the logging integrations. hub.clearBreadcrumbs(); + hub.addBreadcrumb(createRequestBreadcrumb(request)); hub.configureScope( scope -> { @@ -41,6 +42,10 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } + private @NotNull String createRequestBreadcrumb(final @NotNull HttpServletRequest request) { + return "Starting to serve request " + request.getMethod() + " " + request.getRequestURI(); + } + @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; From 3d03787ec8e288f0803fe786a22823a591505326 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 4 Sep 2020 10:59:24 +0200 Subject: [PATCH 06/20] Polish. --- .../io/sentry/samples/spring/boot/SentryDemoApplication.java | 1 - .../src/main/resources/application.properties | 3 +-- .../java/io/sentry/samples/spring/SentryDemoApplication.java | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java index 1044f1ad6..14e57e0cc 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java @@ -5,7 +5,6 @@ @SpringBootApplication public class SentryDemoApplication { - public static void main(String[] args) { SpringApplication.run(SentryDemoApplication.class, args); } diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index b6253dfc1..b09f07892 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -2,8 +2,7 @@ sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 sentry.send-default-pii=true # Sentry Spring Boot integration allows more fine-grained SentryOptions configuration -sentry.max-breadcrumbs=10 -sentry.bypass-security=true +sentry.max-breadcrumbs=150 # Uncaught exception handler should be disabled if both Sentry Spring and Logback integrations are enabled sentry.enable-uncaught-exception-handler=false diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java index 4a17d70e3..408d6d0c2 100644 --- a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java @@ -7,7 +7,6 @@ @SpringBootApplication @EnableSentry public class SentryDemoApplication { - public static void main(String[] args) { SpringApplication.run(SentryDemoApplication.class, args); } From ad53250045a045f22c1d742ed2efe37a166090c2 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 4 Sep 2020 12:52:47 +0200 Subject: [PATCH 07/20] Polish --- buildSrc/src/main/java/Config.kt | 6 +++--- .../sentry-samples-spring-boot/build.gradle.kts | 8 ++++---- .../sentry/samples/spring/boot/CustomEventProcessor.java | 3 ++- sentry-samples/sentry-samples-spring/build.gradle.kts | 8 ++++---- sentry-spring-boot-starter/build.gradle.kts | 6 +++--- sentry-spring/build.gradle.kts | 6 +++--- .../main/java/io/sentry/spring/SentryRequestFilter.java | 3 ++- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index e2a1518f1..d009fe993 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -46,6 +46,9 @@ object Config { val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version" val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion" + val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion" + val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion" + val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion" val springWeb = "org.springframework:spring-webmvc" val servletApi = "javax.servlet:javax.servlet-api" @@ -66,9 +69,6 @@ object Config { val robolectric = "org.robolectric:robolectric:4.3.1" val mockitoKotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" val awaitility = "org.awaitility:awaitility-kotlin:4.0.3" - val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion" - val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion" - val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion" } object QualityPlugins { diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 9676cdb87..e68ac4b4a 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -16,14 +16,14 @@ repositories { } dependencies { - implementation("org.springframework.boot:spring-boot-starter-security") - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter") + implementation(Config.Libs.springBootStarterSecurity) + implementation(Config.Libs.springBootStarterWeb) + implementation(Config.Libs.springBootStarter) implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation(project(":sentry-spring-boot-starter")) implementation(project(":sentry-logback")) - testImplementation("org.springframework.boot:spring-boot-starter-test") { + testImplementation(Config.Libs.springBootStarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } } diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java index 8165b7feb..c33a71bee 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java @@ -6,7 +6,8 @@ import org.springframework.stereotype.Component; /** - * Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are sent to Sentry. + * Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are + * sent to Sentry. */ @Component public class CustomEventProcessor implements EventProcessor { diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index 84aa42b24..48ced0bb1 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -16,14 +16,14 @@ repositories { } dependencies { - implementation("org.springframework.boot:spring-boot-starter-security") - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter") + implementation(Config.Libs.springBootStarterSecurity) + implementation(Config.Libs.springBootStarterWeb) + implementation(Config.Libs.springBootStarter) implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation(project(":sentry-spring")) implementation(project(":sentry-logback")) - testImplementation("org.springframework.boot:spring-boot-starter-test") { + testImplementation(Config.Libs.springBootStarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } } diff --git a/sentry-spring-boot-starter/build.gradle.kts b/sentry-spring-boot-starter/build.gradle.kts index 1282f1539..90bd30aef 100644 --- a/sentry-spring-boot-starter/build.gradle.kts +++ b/sentry-spring-boot-starter/build.gradle.kts @@ -51,9 +51,9 @@ dependencies { testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(Config.TestLibs.kotlinTestJunit) testImplementation(Config.TestLibs.mockitoKotlin) - testImplementation(Config.TestLibs.springBootStarterTest) - testImplementation(Config.TestLibs.springBootStarterWeb) - testImplementation(Config.TestLibs.springBootStarterSecurity) + testImplementation(Config.Libs.springBootStarterTest) + testImplementation(Config.Libs.springBootStarterWeb) + testImplementation(Config.Libs.springBootStarterSecurity) testImplementation(Config.TestLibs.awaitility) } diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts index 49ccda888..93c109fd6 100644 --- a/sentry-spring/build.gradle.kts +++ b/sentry-spring/build.gradle.kts @@ -46,9 +46,9 @@ dependencies { testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(Config.TestLibs.kotlinTestJunit) testImplementation(Config.TestLibs.mockitoKotlin) - testImplementation(Config.TestLibs.springBootStarterTest) - testImplementation(Config.TestLibs.springBootStarterWeb) - testImplementation(Config.TestLibs.springBootStarterSecurity) + testImplementation(Config.Libs.springBootStarterTest) + testImplementation(Config.Libs.springBootStarterWeb) + testImplementation(Config.Libs.springBootStarterSecurity) testImplementation(Config.TestLibs.awaitility) } diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java index 4493247a8..f03ae337e 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java @@ -31,7 +31,8 @@ protected void doFilterInternal( final @NotNull FilterChain filterChain) throws ServletException, IOException { hub.pushScope(); - // clears breadcrumbs that may have been added during application startup through one of the logging integrations. + // clears breadcrumbs that may have been added during application startup through one of the + // logging integrations. hub.clearBreadcrumbs(); hub.addBreadcrumb(createRequestBreadcrumb(request)); From b98827f4f632686bb78ec99d5f778bc0f6d84cea Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 08:42:39 +0200 Subject: [PATCH 08/20] Set Java version and vendor in custom event processor in sample app. Currently there is no option to set this on the runtime as Sentry does not recognize Java runtime. --- .../samples/spring/boot/CustomEventProcessor.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java index c33a71bee..61eedb2bd 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java @@ -11,9 +11,22 @@ */ @Component public class CustomEventProcessor implements EventProcessor { + private final String javaVersion; + private final String javaVendor; + + public CustomEventProcessor(String javaVersion, String javaVendor) { + this.javaVersion = javaVersion; + this.javaVendor = javaVendor; + } + + public CustomEventProcessor() { + this(System.getProperty("java.version"), System.getProperty("java.vendor")); + } + @Override public SentryEvent process(SentryEvent event, @Nullable Object hint) { - event.setTag("Java Version", System.getProperty("java.version")); + event.setTag("Java-Version", javaVersion); + event.setTag("Java-Vendor", javaVendor); return event; } } From 8a63bf9554ce712f774ef3b028e8ed8c2692e6fa Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 08:46:08 +0200 Subject: [PATCH 09/20] Autoconfigure multiple event processors and integrations if exist. --- .../io/sentry/spring/boot/SentryAutoConfiguration.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index 5dd2feda8..5136d6d57 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -22,6 +22,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import java.util.List; + @Configuration @ConditionalOnProperty(name = "sentry.dsn") @Open @@ -39,15 +41,15 @@ static class HubConfiguration { final @NotNull ObjectProvider beforeSendCallback, final @NotNull ObjectProvider beforeBreadcrumbCallback, - final @NotNull ObjectProvider eventProcessors, - final @NotNull ObjectProvider integrations, + final @NotNull List eventProcessors, + final @NotNull List integrations, final @NotNull ObjectProvider transportGate, final @NotNull ObjectProvider transport) { return options -> { beforeSendCallback.ifAvailable(options::setBeforeSend); beforeBreadcrumbCallback.ifAvailable(options::setBeforeBreadcrumb); - eventProcessors.stream().forEach(options::addEventProcessor); - integrations.stream().forEach(options::addIntegration); + eventProcessors.forEach(options::addEventProcessor); + integrations.forEach(options::addIntegration); transportGate.ifAvailable(options::setTransportGate); transport.ifAvailable(options::setTransport); }; From 507a08dd163846e4312cda9dbc0711f0c38993e5 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 09:13:38 +0200 Subject: [PATCH 10/20] Add convenient method to create HTTP breadcrumb. --- .../src/main/java/io/sentry/core/Breadcrumb.java | 16 ++++++++++++++++ .../test/java/io/sentry/core/BreadcrumbTest.kt | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java b/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java index cfe7c8f35..c79e476df 100644 --- a/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java +++ b/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java @@ -43,6 +43,22 @@ public final class Breadcrumb implements Cloneable, IUnknownPropertiesConsumer { this.timestamp = timestamp; } + /** + * Creates HTTP breadcrumb. + * + * @param url - the request URL + * @param method - the request method + * @return the breadcrumb + */ + public static @NotNull Breadcrumb http(final @NotNull String url, final @NotNull String method) { + final Breadcrumb breadcrumb = new Breadcrumb(); + breadcrumb.setType("http"); + breadcrumb.setCategory("http"); + breadcrumb.setData("url", url); + breadcrumb.setData("method", method.toUpperCase()); + return breadcrumb; + } + /** Breadcrumb ctor */ public Breadcrumb() { this(DateUtils.getCurrentDateTimeOrNull()); diff --git a/sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt b/sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt index d38ef8966..f7a2a9258 100644 --- a/sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt @@ -98,4 +98,13 @@ class BreadcrumbTest { val breadcrumb = Breadcrumb("this is a test") assertEquals("this is a test", breadcrumb.message) } + + @Test + fun `creates HTTP breadcrumb`() { + val breadcrumb = Breadcrumb.http("http://example.com", "POST") + assertEquals("http://example.com", breadcrumb.data["url"]) + assertEquals("POST", breadcrumb.data["method"]) + assertEquals("http", breadcrumb.type) + assertEquals("http", breadcrumb.category) + } } From 7a65de27ef8a88b11cc90efe00c970cc59a31481 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 09:14:20 +0200 Subject: [PATCH 11/20] Add convenient method to create HTTP breadcrumb. --- .../boot/SentrySpringIntegrationTest.kt | 6 +++++- .../spring/SentryExceptionResolver.java | 19 ++++++++++++------- .../spring/SentrySpringIntegrationTest.kt | 6 +++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt index 514fc2986..a8b28ab66 100644 --- a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt +++ b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt @@ -6,6 +6,7 @@ import io.sentry.core.IHub import io.sentry.core.Sentry import io.sentry.core.SentryEvent import io.sentry.core.SentryOptions +import io.sentry.core.exception.ExceptionMechanismException import io.sentry.core.transport.ITransport import io.sentry.spring.SentrySecurityFilter import java.lang.RuntimeException @@ -95,7 +96,10 @@ class SentrySpringIntegrationTest { await.untilAsserted { verify(transport).send(check { event: SentryEvent -> assertThat(event.throwable).isNotNull() - assertThat(event.throwable!!.message).isEqualTo("something went wrong") + assertThat(event.throwable).isInstanceOf(ExceptionMechanismException::class.java) + val ex = event.throwable as ExceptionMechanismException + assertThat(ex.throwable.message).isEqualTo("something went wrong") + assertThat(ex.exceptionMechanism.isHandled).isFalse() }) } } diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java index e7bcda5e9..eba7bfbf4 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java @@ -2,7 +2,10 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.core.IHub; -import io.sentry.core.SentryOptions; +import io.sentry.core.SentryEvent; +import io.sentry.core.SentryLevel; +import io.sentry.core.exception.ExceptionMechanismException; +import io.sentry.core.protocol.Mechanism; import io.sentry.core.util.Objects; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -20,11 +23,9 @@ @Open public class SentryExceptionResolver implements HandlerExceptionResolver, Ordered { private final @NotNull IHub hub; - private final @NotNull SentryOptions options; - public SentryExceptionResolver(final @NotNull IHub hub, final @NotNull SentryOptions options) { + public SentryExceptionResolver(final @NotNull IHub hub) { this.hub = Objects.requireNonNull(hub, "hub is required"); - this.options = Objects.requireNonNull(options, "options are required"); } @Override @@ -34,9 +35,13 @@ public SentryExceptionResolver(final @NotNull IHub hub, final @NotNull SentryOpt final @Nullable Object handler, final @NotNull Exception ex) { - if (options.isEnableUncaughtExceptionHandler()) { - hub.captureException(ex); - } + final Mechanism mechanism = new Mechanism(); + mechanism.setHandled(false); + final Throwable throwable = + new ExceptionMechanismException(mechanism, ex, Thread.currentThread()); + final SentryEvent event = new SentryEvent(throwable); + event.setLevel(SentryLevel.FATAL); + hub.captureEvent(event); // null = run other HandlerExceptionResolvers to actually handle the exception return null; diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt index aa3b18e02..e600672bd 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt @@ -6,6 +6,7 @@ import io.sentry.core.IHub import io.sentry.core.Sentry import io.sentry.core.SentryEvent import io.sentry.core.SentryOptions +import io.sentry.core.exception.ExceptionMechanismException import io.sentry.core.transport.ITransport import java.lang.RuntimeException import org.assertj.core.api.Assertions.assertThat @@ -94,7 +95,10 @@ class SentrySpringIntegrationTest { await.untilAsserted { verify(transport).send(check { event: SentryEvent -> assertThat(event.throwable).isNotNull() - assertThat(event.throwable!!.message).isEqualTo("something went wrong") + assertThat(event.throwable).isInstanceOf(ExceptionMechanismException::class.java) + val ex = event.throwable as ExceptionMechanismException + assertThat(ex.throwable.message).isEqualTo("something went wrong") + assertThat(ex.exceptionMechanism.isHandled).isFalse() }) } } From cc01bb9c711b954af2c9acdc9b9a71e29b606e42 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 09:15:22 +0200 Subject: [PATCH 12/20] Polish. --- .../io/sentry/spring/boot/SentryAutoConfiguration.java | 3 +-- .../main/java/io/sentry/spring/SentryRequestFilter.java | 8 +++----- .../java/io/sentry/spring/SentryWebConfiguration.java | 5 ++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index 5136d6d57..67eacc224 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -11,6 +11,7 @@ import io.sentry.core.transport.ITransport; import io.sentry.core.transport.ITransportGate; import io.sentry.spring.SentryWebConfiguration; +import java.util.List; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -22,8 +23,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.util.List; - @Configuration @ConditionalOnProperty(name = "sentry.dsn") @Open diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java index f03ae337e..84ab28213 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java @@ -1,6 +1,7 @@ package io.sentry.spring; import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.Breadcrumb; import io.sentry.core.IHub; import io.sentry.core.SentryOptions; import io.sentry.core.util.Objects; @@ -31,10 +32,11 @@ protected void doFilterInternal( final @NotNull FilterChain filterChain) throws ServletException, IOException { hub.pushScope(); + // TODO: check if it's there in Sentry 1.x // clears breadcrumbs that may have been added during application startup through one of the // logging integrations. hub.clearBreadcrumbs(); - hub.addBreadcrumb(createRequestBreadcrumb(request)); + hub.addBreadcrumb(Breadcrumb.http(request.getRequestURI(), request.getMethod())); hub.configureScope( scope -> { @@ -43,10 +45,6 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } - private @NotNull String createRequestBreadcrumb(final @NotNull HttpServletRequest request) { - return "Starting to serve request " + request.getMethod() + " " + request.getRequestURI(); - } - @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java index 5f646903a..47e8731c5 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java @@ -19,8 +19,7 @@ public class SentryWebConfiguration { } @Bean - public @NotNull SentryExceptionResolver sentryExceptionResolver( - final @NotNull IHub sentryHub, final @NotNull SentryOptions options) { - return new SentryExceptionResolver(sentryHub, options); + public @NotNull SentryExceptionResolver sentryExceptionResolver(final @NotNull IHub sentryHub) { + return new SentryExceptionResolver(sentryHub); } } From 33c2d0346e9856794929e5692d03b226e5508246 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 10:22:02 +0200 Subject: [PATCH 13/20] Set DSN using annotation attribute on `@EnableSentry`. --- .../src/main/resources/application.properties | 3 +- .../samples/spring/SentryDemoApplication.java | 6 +- .../src/main/resources/application.properties | 6 -- .../java/io/sentry/spring/EnableSentry.java | 25 ++++-- .../spring/SentryCoreConfiguration.java | 71 --------------- .../io/sentry/spring/SentryHubRegistrar.java | 73 ++++++++++++++++ .../spring/SentryInitBeanPostProcessor.java | 21 +++++ .../io/sentry/spring/EnableSentryTest.kt | 86 +++++++++---------- .../spring/SentrySpringIntegrationTest.kt | 23 +++-- 9 files changed, 177 insertions(+), 137 deletions(-) delete mode 100644 sentry-samples/sentry-samples-spring/src/main/resources/application.properties delete mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index b09f07892..e60faee44 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -4,5 +4,4 @@ sentry.send-default-pii=true # Sentry Spring Boot integration allows more fine-grained SentryOptions configuration sentry.max-breadcrumbs=150 -# Uncaught exception handler should be disabled if both Sentry Spring and Logback integrations are enabled -sentry.enable-uncaught-exception-handler=false +# TODO: add deduplication diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java index 408d6d0c2..cf87d72aa 100644 --- a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java @@ -5,7 +5,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -@EnableSentry +// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry +// project/dashboard +@EnableSentry( + dsn = "https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954", + sendDefaultPii = true) public class SentryDemoApplication { public static void main(String[] args) { SpringApplication.run(SentryDemoApplication.class, args); diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring/src/main/resources/application.properties deleted file mode 100644 index b05b4202c..000000000 --- a/sentry-samples/sentry-samples-spring/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -# NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard -sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 -sentry.send-default-pii=true - -# Uncaught exception handler should be disabled if both Sentry Spring and Logback integrations are enabled -sentry.enable-uncaught-exception-handler=false diff --git a/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java b/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java index 32b1531f2..2ab00f6ee 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java +++ b/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java @@ -9,13 +9,24 @@ /** * Enables Sentry error handling capabilities. * - *

- creates bean of type {@link io.sentry.core.SentryOptions} using properties from Spring - * {@link org.springframework.core.env.Environment}. - registers {@link io.sentry.core.IHub} for - * sending Sentry events - registers {@link SentryRequestFilter} for attaching request information - * to Sentry events - registers {@link SentryExceptionResolver} to send Sentry event for any - * uncaught exception in Spring MVC flow. + *

    + *
  • creates bean of type {@link io.sentry.core.SentryOptions} + *
  • registers {@link io.sentry.core.IHub} for sending Sentry events + *
  • registers {@link SentryRequestFilter} for attaching request information to Sentry events + *
  • registers {@link SentryExceptionResolver} to send Sentry event for any uncaught exception + * in Spring MVC flow. + *
*/ @Retention(RetentionPolicy.RUNTIME) -@Import({SentryCoreConfiguration.class, SentryWebConfiguration.class}) +@Import({SentryHubRegistrar.class, SentryInitBeanPostProcessor.class, SentryWebConfiguration.class}) @Target(ElementType.TYPE) -public @interface EnableSentry {} +public @interface EnableSentry { + /** + * The DSN tells the SDK where to send the events to. If this value is not provided, the SDK will + * just not send any events. + */ + String dsn() default ""; + + /** Whether to send personal identifiable information along with events. */ + boolean sendDefaultPii() default false; +} diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java deleted file mode 100644 index 4438ca5ce..000000000 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryCoreConfiguration.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.sentry.spring; - -import com.jakewharton.nopen.annotation.Open; -import io.sentry.core.HubAdapter; -import io.sentry.core.IHub; -import io.sentry.core.Sentry; -import io.sentry.core.SentryOptions; -import io.sentry.core.protocol.SdkVersion; -import io.sentry.core.transport.ITransport; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -/** Registers beans required to use Sentry core features. */ -@Configuration -@Open -public class SentryCoreConfiguration { - - /** - * Creates {@link SentryOptions} using properties from Spring {@link Environment}. - * - * @param environment - the environment - * @param transport - optional Sentry transport - used primarily in testing scenarios - * @return SentryOptions - */ - @Bean - public @NotNull SentryOptions sentryOptions( - final @NotNull Environment environment, final @NotNull ObjectProvider transport) { - final SentryOptions options = new SentryOptions(); - options.setDsn(environment.getProperty("sentry.dsn")); - options.setSdkVersion(createSdkVersion(options)); - - final String sendDefaultPii = environment.getProperty("sentry.send-default-pii"); - if (sendDefaultPii != null) { - options.setSendDefaultPii(Boolean.parseBoolean(sendDefaultPii)); - } - final String enableUncaughtExceptionHandler = - environment.getProperty("sentry.enable-uncaught-exception-handler"); - if (enableUncaughtExceptionHandler != null) { - options.setEnableUncaughtExceptionHandler( - Boolean.parseBoolean(enableUncaughtExceptionHandler)); - } - transport.ifAvailable(options::setTransport); - return options; - } - - @Bean - public @NotNull IHub sentryHub(final @NotNull SentryOptions options) { - options.setSentryClientName(BuildConfig.SENTRY_SPRING_SDK_NAME); - options.setSdkVersion(createSdkVersion(options)); - Sentry.init(options); - return HubAdapter.getInstance(); - } - - private static @NotNull SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) { - SdkVersion sdkVersion = sentryOptions.getSdkVersion(); - - if (sdkVersion == null) { - sdkVersion = new SdkVersion(); - } - - sdkVersion.setName(BuildConfig.SENTRY_SPRING_SDK_NAME); - final String version = BuildConfig.VERSION_NAME; - sdkVersion.setVersion(version); - sdkVersion.addPackage("maven:sentry-spring", version); - - return sdkVersion; - } -} diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java b/sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java new file mode 100644 index 000000000..3b85dae5c --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java @@ -0,0 +1,73 @@ +package io.sentry.spring; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.HubAdapter; +import io.sentry.core.SentryOptions; +import io.sentry.core.protocol.SdkVersion; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; + +/** Registers beans required to use Sentry core features. */ +@Configuration +@Open +public class SentryHubRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions( + AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + final AnnotationAttributes annotationAttributes = + AnnotationAttributes.fromMap( + importingClassMetadata.getAnnotationAttributes(EnableSentry.class.getName())); + if (annotationAttributes != null && annotationAttributes.containsKey("dsn")) { + registerSentryOptions(registry, annotationAttributes); + registerSentryHubBean(registry); + } + } + + private void registerSentryOptions( + BeanDefinitionRegistry registry, AnnotationAttributes annotationAttributes) { + final BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(SentryOptions.class); + + if (registry.containsBeanDefinition("mockTransport")) { + builder.addPropertyReference("transport", "mockTransport"); + } + builder.addPropertyValue("dsn", annotationAttributes.getString("dsn")); + builder.addPropertyValue("sentryClientName", BuildConfig.SENTRY_SPRING_SDK_NAME); + builder.addPropertyValue("sdkVersion", createSdkVersion()); + if (annotationAttributes.containsKey("sendDefaultPii")) { + builder.addPropertyValue("sendDefaultPii", annotationAttributes.getBoolean("sendDefaultPii")); + } + + registry.registerBeanDefinition("sentryOptions", builder.getBeanDefinition()); + } + + private void registerSentryHubBean(BeanDefinitionRegistry registry) { + final BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(HubAdapter.class); + builder.setInitMethodName("getInstance"); + + registry.registerBeanDefinition("sentryHub", builder.getBeanDefinition()); + } + + private static @NotNull SdkVersion createSdkVersion() { + final SentryOptions defaultOptions = new SentryOptions(); + SdkVersion sdkVersion = defaultOptions.getSdkVersion(); + + if (sdkVersion == null) { + sdkVersion = new SdkVersion(); + } + + sdkVersion.setName(BuildConfig.SENTRY_SPRING_SDK_NAME); + final String version = BuildConfig.VERSION_NAME; + sdkVersion.setVersion(version); + sdkVersion.addPackage("maven:sentry-spring", version); + + return sdkVersion; + } +} diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java new file mode 100644 index 000000000..ddc350a3e --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java @@ -0,0 +1,21 @@ +package io.sentry.spring; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.Sentry; +import io.sentry.core.SentryOptions; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** Initializes Sentry after all beans are registered. */ +@Open +public class SentryInitBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessAfterInitialization( + final @NotNull Object bean, @NotNull final String beanName) throws BeansException { + if (bean instanceof SentryOptions) { + Sentry.init((SentryOptions) bean); + } + return bean; + } +} diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt index 46060ca8b..1255f96a9 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt @@ -13,68 +13,66 @@ class EnableSentryTest { @Test fun `sets properties from environment on SentryOptions`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.send-default-pii=true", - "sentry.enable-uncaught-exception-handler=true").run { - assertThat(it).hasSingleBean(SentryOptions::class.java) - val options = it.getBean(SentryOptions::class.java) - assertThat(options.dsn).isEqualTo("http://key@localhost/proj") - assertThat(options.isSendDefaultPii).isTrue() - assertThat(options.isEnableUncaughtExceptionHandler).isTrue() - } - - contextRunner.withPropertyValues( - "sentry.dsn=", - "sentry.send-default-pii=false", - "sentry.enable-uncaught-exception-handler=false").run { - assertThat(it).hasSingleBean(SentryOptions::class.java) - val options = it.getBean(SentryOptions::class.java) - assertThat(options.dsn).isEmpty() - assertThat(options.isSendDefaultPii).isFalse() - assertThat(options.isEnableUncaughtExceptionHandler).isFalse() - } - } + ApplicationContextRunner() + .withConfiguration(UserConfigurations.of(AppConfigWithDefaultSendPii::class.java)) + .run { + assertThat(it).hasSingleBean(SentryOptions::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.dsn).isEqualTo("http://key@localhost/proj") + assertThat(options.isSendDefaultPii).isTrue() + } - @Test - fun `sets client name and SDK version`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + ApplicationContextRunner() + .withConfiguration(UserConfigurations.of(AppConfigWithEmptyDsn::class.java)) .run { assertThat(it).hasSingleBean(SentryOptions::class.java) val options = it.getBean(SentryOptions::class.java) - assertThat(options.sentryClientName).isEqualTo("sentry.java.spring") - assertThat(options.sdkVersion).isNotNull - assertThat(options.sdkVersion!!.name).isEqualTo("sentry.java.spring") - assertThat(options.sdkVersion!!.version).isEqualTo(BuildConfig.VERSION_NAME) - assertThat(options.sdkVersion!!.packages).isNotNull - assertThat(options.sdkVersion!!.packages!!.map { pkg -> pkg.name }).contains("maven:sentry-spring") + assertThat(options.dsn).isEmpty() + assertThat(options.isSendDefaultPii).isFalse() } } + @Test + fun `sets client name and SDK version`() { + contextRunner.run { + assertThat(it).hasSingleBean(SentryOptions::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.sentryClientName).isEqualTo("sentry.java.spring") + assertThat(options.sdkVersion).isNotNull + assertThat(options.sdkVersion!!.name).isEqualTo("sentry.java.spring") + assertThat(options.sdkVersion!!.version).isEqualTo(BuildConfig.VERSION_NAME) + assertThat(options.sdkVersion!!.packages).isNotNull + assertThat(options.sdkVersion!!.packages!!.map { pkg -> pkg.name }).contains("maven:sentry-spring") + } + } + @Test fun `creates Sentry Hub`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it).hasSingleBean(IHub::class.java) - } + contextRunner.run { + assertThat(it).hasSingleBean(IHub::class.java) + } } @Test fun `creates SentryRequestFilter`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it).hasSingleBean(SentryRequestFilter::class.java) - } + contextRunner.run { + assertThat(it).hasSingleBean(SentryRequestFilter::class.java) + } } @Test fun `creates SentryExceptionResolver`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it).hasSingleBean(SentryExceptionResolver::class.java) - } + contextRunner.run { + assertThat(it).hasSingleBean(SentryExceptionResolver::class.java) + } } - @EnableSentry + @EnableSentry(dsn = "http://key@localhost/proj") class AppConfig + + @EnableSentry(dsn = "") + class AppConfigWithEmptyDsn + + @EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true) + class AppConfigWithDefaultSendPii } diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt index e600672bd..ea943e722 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt @@ -1,6 +1,8 @@ package io.sentry.spring import com.nhaarman.mockitokotlin2.check +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.reset import com.nhaarman.mockitokotlin2.verify import io.sentry.core.IHub import io.sentry.core.Sentry @@ -11,11 +13,12 @@ import io.sentry.core.transport.ITransport import java.lang.RuntimeException import org.assertj.core.api.Assertions.assertThat import org.awaitility.kotlin.await +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.boot.web.server.LocalServerPort import org.springframework.context.annotation.Bean @@ -39,17 +42,21 @@ import org.springframework.web.bind.annotation.RestController @RunWith(SpringRunner::class) @SpringBootTest( classes = [App::class], - webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, - properties = ["sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true"] + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT ) class SentrySpringIntegrationTest { - @MockBean + @Autowired lateinit var transport: ITransport @LocalServerPort lateinit var port: Integer + @Before + fun `reset mocks`() { + reset(transport) + } + @Test fun `attaches request and user information to SentryEvents`() { val restTemplate = TestRestTemplate().withBasicAuth("user", "password") @@ -105,8 +112,12 @@ class SentrySpringIntegrationTest { } @SpringBootApplication -@EnableSentry -open class App +@EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true) +open class App { + + @Bean + open fun mockTransport() = mock() +} @RestController class HelloController { From ea83f4c77be2231086b4465e8fc19c3284fd6693 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 11:09:34 +0200 Subject: [PATCH 14/20] Deduplicate events based on the throwable. --- ...DuplicateEventDetectionEventProcessor.java | 75 +++++++++++++++++++ .../java/io/sentry/core/SentryOptions.java | 1 + ...plicateEventDetectionEventProcessorTest.kt | 70 +++++++++++++++++ .../src/main/resources/application.properties | 2 - 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 sentry-core/src/main/java/io/sentry/core/DuplicateEventDetectionEventProcessor.java create mode 100644 sentry-core/src/test/java/io/sentry/core/DuplicateEventDetectionEventProcessorTest.kt diff --git a/sentry-core/src/main/java/io/sentry/core/DuplicateEventDetectionEventProcessor.java b/sentry-core/src/main/java/io/sentry/core/DuplicateEventDetectionEventProcessor.java new file mode 100644 index 000000000..e62824128 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/DuplicateEventDetectionEventProcessor.java @@ -0,0 +1,75 @@ +package io.sentry.core; + +import io.sentry.core.exception.ExceptionMechanismException; +import io.sentry.core.util.Objects; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Deduplicates events containing throwable that has been already processed. */ +public final class DuplicateEventDetectionEventProcessor implements EventProcessor { + private final WeakHashMap capturedObjects = new WeakHashMap<>(); + private final SentryOptions options; + + public DuplicateEventDetectionEventProcessor(final @NotNull SentryOptions options) { + this.options = Objects.requireNonNull(options, "options are required"); + } + + @Override + public SentryEvent process(final @NotNull SentryEvent event, final @Nullable Object hint) { + final Throwable throwable = event.getThrowable(); + if (throwable != null) { + if (throwable instanceof ExceptionMechanismException) { + final ExceptionMechanismException ex = (ExceptionMechanismException) throwable; + if (capturedObjects.containsKey(ex.getThrowable())) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Duplicate Exception detected. Event %s will be discarded.", + event.getEventId()); + return null; + } else { + capturedObjects.put(ex.getThrowable(), null); + } + } else { + if (capturedObjects.containsKey(throwable) + || containsAnyKey(capturedObjects, allCauses(throwable))) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Duplicate Exception detected. Event %s will be discarded.", + event.getEventId()); + return null; + } else { + capturedObjects.put(throwable, null); + } + } + } + return event; + } + + private static boolean containsAnyKey( + final @NotNull Map map, final @NotNull List list) { + for (T entry : list) { + if (map.containsKey(entry)) { + return true; + } + } + return false; + } + + private static @NotNull List allCauses(final @NotNull Throwable throwable) { + final List causes = new ArrayList<>(); + Throwable ex = throwable; + while (ex.getCause() != null) { + causes.add(ex.getCause()); + ex = ex.getCause(); + } + return causes; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java b/sentry-core/src/main/java/io/sentry/core/SentryOptions.java index 7c10b1702..21cbb10b5 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryOptions.java @@ -1047,6 +1047,7 @@ public SentryOptions() { integrations.add(new ShutdownHookIntegration()); eventProcessors.add(new MainEventProcessor(this)); + eventProcessors.add(new DuplicateEventDetectionEventProcessor(this)); setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME); setSdkVersion(createSdkVersion()); diff --git a/sentry-core/src/test/java/io/sentry/core/DuplicateEventDetectionEventProcessorTest.kt b/sentry-core/src/test/java/io/sentry/core/DuplicateEventDetectionEventProcessorTest.kt new file mode 100644 index 000000000..3867fd5f5 --- /dev/null +++ b/sentry-core/src/test/java/io/sentry/core/DuplicateEventDetectionEventProcessorTest.kt @@ -0,0 +1,70 @@ +package io.sentry.core + +import io.sentry.core.exception.ExceptionMechanismException +import io.sentry.core.protocol.Mechanism +import java.lang.RuntimeException +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class DuplicateEventDetectionEventProcessorTest { + + val processor = DuplicateEventDetectionEventProcessor(SentryOptions()) + + @Test + fun `does not drop event if no previous event with same exception was processed`() { + processor.process(SentryEvent(), null) + + val result = processor.process(SentryEvent(RuntimeException()), null) + + assertNotNull(result) + } + + @Test + fun `drops event with the same exception`() { + val event = SentryEvent(RuntimeException()) + processor.process(event, null) + + val result = processor.process(event, null) + assertNull(result) + } + + @Test + fun `drops event with mechanism exception having an exception that has already been processed`() { + val event = SentryEvent(RuntimeException()) + processor.process(event, null) + + val result = processor.process(SentryEvent(ExceptionMechanismException(Mechanism(), event.throwable, null)), null) + assertNull(result) + } + + @Test + fun `drops event with exception that has already been processed with event with mechanism exception`() { + val sentryEvent = SentryEvent(ExceptionMechanismException(Mechanism(), RuntimeException(), null)) + processor.process(sentryEvent, null) + + val result = processor.process(SentryEvent((sentryEvent.throwable as ExceptionMechanismException).throwable), null) + + assertNull(result) + } + + @Test + fun `drops event with the cause equal to exception in already processed event`() { + val event = SentryEvent(RuntimeException()) + processor.process(event, null) + + val result = processor.process(SentryEvent(RuntimeException(event.throwable)), null) + + assertNull(result) + } + + @Test + fun `drops event with any of the causes has been already processed`() { + val event = SentryEvent(RuntimeException()) + processor.process(event, null) + + val result = processor.process(SentryEvent(RuntimeException(RuntimeException(event.throwable))), null) + + assertNull(result) + } +} diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index e60faee44..84b7ca93c 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -3,5 +3,3 @@ sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 sentry.send-default-pii=true # Sentry Spring Boot integration allows more fine-grained SentryOptions configuration sentry.max-breadcrumbs=150 - -# TODO: add deduplication From 7f317f25d20f3285f6a40e0536ffb26222eb4152 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 11:57:25 +0200 Subject: [PATCH 15/20] Fix linter issue. --- sentry-core/src/main/java/io/sentry/core/Breadcrumb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java b/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java index c79e476df..d990e4daf 100644 --- a/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java +++ b/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java @@ -55,7 +55,7 @@ public final class Breadcrumb implements Cloneable, IUnknownPropertiesConsumer { breadcrumb.setType("http"); breadcrumb.setCategory("http"); breadcrumb.setData("url", url); - breadcrumb.setData("method", method.toUpperCase()); + breadcrumb.setData("method", method.toUpperCase(Locale.getDefault())); return breadcrumb; } From 24c44652957960feea595afb02d7431b826d6da1 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 7 Sep 2020 12:06:20 +0200 Subject: [PATCH 16/20] Set Java runtime on events in sample event processor. --- .../sentry/samples/spring/boot/CustomEventProcessor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java index 61eedb2bd..40d17a1c6 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java @@ -2,6 +2,7 @@ import io.sentry.core.EventProcessor; import io.sentry.core.SentryEvent; +import io.sentry.core.protocol.SentryRuntime; import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Component; @@ -25,8 +26,10 @@ public CustomEventProcessor() { @Override public SentryEvent process(SentryEvent event, @Nullable Object hint) { - event.setTag("Java-Version", javaVersion); - event.setTag("Java-Vendor", javaVendor); + final SentryRuntime runtime = new SentryRuntime(); + runtime.setVersion(javaVersion); + runtime.setName(javaVendor); + event.getContexts().setRuntime(runtime); return event; } } From b31f2d24cc33b291a48f65f8730431e1fe66cc76 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 8 Sep 2020 10:01:24 +0200 Subject: [PATCH 17/20] Polish. --- sentry-samples/sentry-samples-spring/build.gradle.kts | 3 ++- sentry-spring/build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index 48ced0bb1..dce35dcd2 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -20,7 +21,7 @@ dependencies { implementation(Config.Libs.springBootStarterWeb) implementation(Config.Libs.springBootStarter) implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(project(":sentry-spring")) implementation(project(":sentry-logback")) testImplementation(Config.Libs.springBootStarterTest) { diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts index 2f72cc37d..7ec722c93 100644 --- a/sentry-spring/build.gradle.kts +++ b/sentry-spring/build.gradle.kts @@ -72,7 +72,7 @@ tasks.jacocoTestReport { tasks { jacocoTestCoverageVerification { violationRules { - rule { limit { minimum = BigDecimal.valueOf(0.6) } } + rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } } } check { From 250b299ac70d20baae288736eba4277419dd7921 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 8 Sep 2020 10:18:06 +0200 Subject: [PATCH 18/20] Run tests on random port. --- .../kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt | 2 +- .../test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt index a8b28ab66..eb1fc3340 100644 --- a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt +++ b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt @@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.RestController @RunWith(SpringRunner::class) @SpringBootTest( classes = [App::class], - webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true"] ) class SentrySpringIntegrationTest { diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt index ea943e722..f5aabbda2 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt @@ -42,7 +42,7 @@ import org.springframework.web.bind.annotation.RestController @RunWith(SpringRunner::class) @SpringBootTest( classes = [App::class], - webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT ) class SentrySpringIntegrationTest { From 16724872d1d1d7d04396f1ab95faada905096805 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 8 Sep 2020 10:18:24 +0200 Subject: [PATCH 19/20] Do not clear breadcrumbs on http request. --- .../src/main/java/io/sentry/spring/SentryRequestFilter.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java index 84ab28213..d9f22adc2 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java @@ -32,10 +32,6 @@ protected void doFilterInternal( final @NotNull FilterChain filterChain) throws ServletException, IOException { hub.pushScope(); - // TODO: check if it's there in Sentry 1.x - // clears breadcrumbs that may have been added during application startup through one of the - // logging integrations. - hub.clearBreadcrumbs(); hub.addBreadcrumb(Breadcrumb.http(request.getRequestURI(), request.getMethod())); hub.configureScope( From 97bf1a4c987e74b87184bdd74b4a59e69a5973ab Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 8 Sep 2020 10:45:29 +0200 Subject: [PATCH 20/20] Polish. --- sentry-samples/sentry-samples-spring-boot/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index e68ac4b4a..e342bf150 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -20,7 +21,7 @@ dependencies { implementation(Config.Libs.springBootStarterWeb) implementation(Config.Libs.springBootStarter) implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(project(":sentry-spring-boot-starter")) implementation(project(":sentry-logback")) testImplementation(Config.Libs.springBootStarterTest) {