diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1f5ccece3..996b651eeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,44 @@ `logsEnabled` argument. Pass `false` to keep Sentry Logs disabled. - Disable tracing for HTTP `OPTIONS` requests by default. Set `traceOptionsRequests` to `true` to preserve the previous behavior ([#6254](https://github.com/getsentry/sentry-java/pull/6254)) +- Incoming HTTP server transactions that finish with status 404 are no longer sent to Sentry by + default. This includes matched endpoints that intentionally return 404. Outgoing HTTP requests + that receive a 404 are unaffected. + + Restore the previous behavior and trace every incoming HTTP status with an empty configuration: + + Java: + + ```java + import java.util.Collections; + + options.setIgnoredServerHttpStatusCodesForTracing(Collections.emptyList()); + ``` + + `sentry.properties`: + + ```properties + ignored-server-http-status-codes-for-tracing= + ``` + + Spring Boot `application.properties`: + + ```properties + sentry.ignored-server-http-status-codes-for-tracing= + ``` + + Spring Boot `application.yml`: + + ```yaml + sentry: + ignored-server-http-status-codes-for-tracing: [] + ``` + + The final response status is only known after sampling and request handling. The transaction may + already have propagated a sampled trace to downstream services, which can still report their + segments. Local child spans are discarded with the ignored transaction. Continuous profile + chunks are reported independently. External OTLP export does not pass through `SentryClient` and + is not filtered by this option ([#6567](https://github.com/getsentry/sentry-java/pull/6567)) - Remove the Apache HttpClient transport and stop publishing `sentry-apache-http-client-5` ([#6214](https://github.com/getsentry/sentry-java/pull/6214)) - Remove the deprecated `Instrumenter` enum, option, span context property, and `startChild` overloads ([#6252](https://github.com/getsentry/sentry-java/pull/6252)) - Remove deprecated OpenTelemetry integration classes and the legacy `SentrySpanStorage` ([#6243](https://github.com/getsentry/sentry-java/pull/6243)) diff --git a/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/IntegrationTest.kt b/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/IntegrationTest.kt index 3ca549b3b9e..f61ad6c3826 100644 --- a/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/IntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/IntegrationTest.kt @@ -29,7 +29,6 @@ import org.springframework.test.context.junit4.SpringRunner properties = [ "sentry.traces-sample-rate=1.0", - "sentry.ignored-server-http-status-codes-for-tracing=404", "sentry.clients.ok-http-enabled=true", "sentry.enable-cache-tracing=true", "sentry.enable-queue-tracing=true", diff --git a/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/PersonIntegrationTest.kt b/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/PersonIntegrationTest.kt index ee18a3b7233..f6045b89b7e 100644 --- a/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTest/kotlin/io/sentry/spring/boot4/integrationtest/PersonIntegrationTest.kt @@ -103,7 +103,7 @@ class PersonIntegrationTest : IntegrationTest() { } @Test - fun `configured not found response does not send a transaction`() { + fun `default not found response does not send a transaction`() { val path = "/missing-ignored-status" val response = restTemplate.getForEntity(path, String::class.java) diff --git a/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/PersonIntegrationTest.kt b/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/PersonIntegrationTest.kt index 09d7aecda14..7d81b9389ff 100644 --- a/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/PersonIntegrationTest.kt @@ -20,6 +20,18 @@ import org.springframework.http.HttpHeaders class PersonIntegrationTest : IntegrationTest() { + @Test + fun `default not found response does not send a transaction`() { + val path = "/missing-default-ignored-status" + + val response = restTemplate.getForEntity(path, String::class.java) + + assertThat(response.statusCode.value()).isEqualTo(404) + server.assertNoEnvelope(millis = 1_000) { + it.transaction()?.request?.url?.endsWith(path) == true + } + } + @Test fun `failed request reports errors, spans with their flags, and logs`() { val response = restTemplate.getForEntity("/person/11", String::class.java) diff --git a/sentry-spring-boot-4/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot4/otelnoagentintegrationtest/PersonIntegrationTest.kt b/sentry-spring-boot-4/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot4/otelnoagentintegrationtest/PersonIntegrationTest.kt index 6e0359c459b..4274747cee7 100644 --- a/sentry-spring-boot-4/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot4/otelnoagentintegrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot4/otelnoagentintegrationtest/PersonIntegrationTest.kt @@ -21,6 +21,16 @@ import org.springframework.http.HttpHeaders class PersonIntegrationTest : IntegrationTest() { + @Test + fun `default not found response does not send a transaction`() { + val path = "/missing-default-ignored-status" + + val response = restTemplate.getForEntity(path, String::class.java) + + assertThat(response.statusCode.value()).isEqualTo(404) + server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true } + } + @Test fun `failed request reports errors, nested spans with their flags, and logs`() { val response = restTemplate.getForEntity("/person/11", String::class.java) diff --git a/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt b/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt index 7af3dbcfdf4..8a283f7d046 100644 --- a/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt @@ -67,7 +67,7 @@ class PersonWebfluxIntegrationTest : WebfluxIntegrationTest() { } @Test - fun `configured not found response does not send a transaction`() { + fun `default not found response does not send a transaction`() { val path = "/missing-ignored-status" val response = restTemplate.getForEntity(path, String::class.java) diff --git a/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/WebfluxIntegrationTest.kt b/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/WebfluxIntegrationTest.kt index 2a9bb4d6710..360dcbb1617 100644 --- a/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/WebfluxIntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTestWebflux/kotlin/io/sentry/spring/boot4/webfluxintegrationtest/WebfluxIntegrationTest.kt @@ -26,7 +26,6 @@ import org.springframework.test.context.junit4.SpringRunner properties = [ "sentry.traces-sample-rate=1.0", - "sentry.ignored-server-http-status-codes-for-tracing=404", "sentry.enable-cache-tracing=true", "sentry.logs.enabled=true", "sentry.reactive.thread-local-accessor-enabled=true", diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt index 08a8a54c9d2..699919ff44e 100644 --- a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt @@ -341,6 +341,15 @@ class SentryAutoConfigurationTest { } } + @Test + fun `ignored server HTTP status codes default to 404`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run { + val ranges = it.getBean(SentryProperties::class.java).ignoredServerHttpStatusCodesForTracing + assertThat(ranges).hasSize(1) + assertThat(ranges!!.single().isInRange(404)).isTrue() + } + } + @Test fun `empty ignored server HTTP status codes disable the configuration`() { contextRunner diff --git a/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/IntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/IntegrationTest.kt index cb67a5321eb..d539470ae93 100644 --- a/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/IntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/IntegrationTest.kt @@ -29,7 +29,6 @@ import org.springframework.test.context.junit4.SpringRunner properties = [ "sentry.traces-sample-rate=1.0", - "sentry.ignored-server-http-status-codes-for-tracing=404", "sentry.clients.ok-http-enabled=true", "sentry.enable-cache-tracing=true", "sentry.enable-queue-tracing=true", diff --git a/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/PersonIntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/PersonIntegrationTest.kt index 919034786d6..40638a251c5 100644 --- a/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTest/kotlin/io/sentry/spring/boot/jakarta/integrationtest/PersonIntegrationTest.kt @@ -103,7 +103,7 @@ class PersonIntegrationTest : IntegrationTest() { } @Test - fun `configured not found response does not send a transaction`() { + fun `default not found response does not send a transaction`() { val path = "/missing-ignored-status" val response = restTemplate.getForEntity(path, String::class.java) diff --git a/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/PersonIntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/PersonIntegrationTest.kt index 62b2012163a..f9526f4e6ff 100644 --- a/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/PersonIntegrationTest.kt @@ -20,6 +20,18 @@ import org.springframework.http.HttpHeaders class PersonIntegrationTest : IntegrationTest() { + @Test + fun `default not found response does not send a transaction`() { + val path = "/missing-default-ignored-status" + + val response = restTemplate.getForEntity(path, String::class.java) + + assertThat(response.statusCode.value()).isEqualTo(404) + server.assertNoEnvelope(millis = 1_000) { + it.transaction()?.request?.url?.endsWith(path) == true + } + } + @Test fun `failed request reports errors, spans with their flags, and logs`() { val response = restTemplate.getForEntity("/person/11", String::class.java) diff --git a/sentry-spring-boot-jakarta/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/jakarta/otelnoagentintegrationtest/PersonIntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/jakarta/otelnoagentintegrationtest/PersonIntegrationTest.kt index e2ca6eb7b56..07e068ff7a6 100644 --- a/sentry-spring-boot-jakarta/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/jakarta/otelnoagentintegrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/jakarta/otelnoagentintegrationtest/PersonIntegrationTest.kt @@ -21,6 +21,16 @@ import org.springframework.http.HttpHeaders class PersonIntegrationTest : IntegrationTest() { + @Test + fun `default not found response does not send a transaction`() { + val path = "/missing-default-ignored-status" + + val response = restTemplate.getForEntity(path, String::class.java) + + assertThat(response.statusCode.value()).isEqualTo(404) + server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true } + } + @Test fun `failed request reports errors, nested spans with their flags, and logs`() { val response = restTemplate.getForEntity("/person/11", String::class.java) diff --git a/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt index f437453e3b7..dcc87e293af 100644 --- a/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt @@ -67,7 +67,7 @@ class PersonWebfluxIntegrationTest : WebfluxIntegrationTest() { } @Test - fun `configured not found response does not send a transaction`() { + fun `default not found response does not send a transaction`() { val path = "/missing-ignored-status" val response = restTemplate.getForEntity(path, String::class.java) diff --git a/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/WebfluxIntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/WebfluxIntegrationTest.kt index 8f0ff2d0de3..4d314719e35 100644 --- a/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/WebfluxIntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/jakarta/webfluxintegrationtest/WebfluxIntegrationTest.kt @@ -26,7 +26,6 @@ import org.springframework.test.context.junit4.SpringRunner properties = [ "sentry.traces-sample-rate=1.0", - "sentry.ignored-server-http-status-codes-for-tracing=404", "sentry.enable-cache-tracing=true", "sentry.logs.enabled=true", "sentry.reactive.thread-local-accessor-enabled=true", diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt index f574ced344c..08b94cd3d61 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt @@ -349,6 +349,15 @@ class SentryAutoConfigurationTest { } } + @Test + fun `ignored server HTTP status codes default to 404`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run { + val ranges = it.getBean(SentryProperties::class.java).ignoredServerHttpStatusCodesForTracing + assertThat(ranges).hasSize(1) + assertThat(ranges!!.single().isInRange(404)).isTrue() + } + } + @Test fun `empty ignored server HTTP status codes disable the configuration`() { contextRunner diff --git a/sentry-spring-boot/README.md b/sentry-spring-boot/README.md index beb1aa3d228..1ee657af6df 100644 --- a/sentry-spring-boot/README.md +++ b/sentry-spring-boot/README.md @@ -7,13 +7,27 @@ For more information check out [Sentry Spring Boot documentation](https://docs.s ## Ignoring server transactions by HTTP status -Set `sentry.ignored-server-http-status-codes-for-tracing` to a comma-separated list of individual -status codes and inclusive ranges: +Incoming server transactions that finish with HTTP status 404 are not sent to Sentry by default. +Set `sentry.ignored-server-http-status-codes-for-tracing` to replace the default with a +comma-separated list of individual status codes and inclusive ranges: ```properties sentry.ignored-server-http-status-codes-for-tracing=404,500-599 ``` -Matching incoming server transactions are not sent to Sentry. The filter reads the root -transaction's HTTP response status; child span statuses do not affect it. The option is disabled -when it is unset or empty. +Use an empty value to trace every incoming HTTP status: + +```properties +sentry.ignored-server-http-status-codes-for-tracing= +``` + +For YAML configuration, use an empty list: + +```yaml +sentry: + ignored-server-http-status-codes-for-tracing: [] +``` + +The filter reads the root transaction's HTTP response status; child span statuses do not affect it. +Filtering happens after the response status is known, so the request may already have propagated a +sampled trace, and local child spans are discarded with the transaction. diff --git a/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/IntegrationTest.kt b/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/IntegrationTest.kt index 31e49fdbb64..a83f50a73f3 100644 --- a/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/IntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/IntegrationTest.kt @@ -29,7 +29,6 @@ import org.springframework.test.context.junit4.SpringRunner properties = [ "sentry.traces-sample-rate=1.0", - "sentry.ignored-server-http-status-codes-for-tracing=404", "sentry.clients.ok-http-enabled=true", "sentry.enable-cache-tracing=true", "sentry.enable-queue-tracing=true", diff --git a/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/PersonIntegrationTest.kt b/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/PersonIntegrationTest.kt index fa17b781b01..84009cc9151 100644 --- a/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTest/kotlin/io/sentry/spring/boot/integrationtest/PersonIntegrationTest.kt @@ -103,7 +103,7 @@ class PersonIntegrationTest : IntegrationTest() { } @Test - fun `configured not found response does not send a transaction`() { + fun `default not found response does not send a transaction`() { val path = "/missing-ignored-status" val response = restTemplate.getForEntity(path, String::class.java) diff --git a/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/PersonIntegrationTest.kt b/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/PersonIntegrationTest.kt index 53a52f77412..e979aa0e04e 100644 --- a/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/PersonIntegrationTest.kt @@ -20,6 +20,18 @@ import org.springframework.http.HttpHeaders class PersonIntegrationTest : IntegrationTest() { + @Test + fun `default not found response does not send a transaction`() { + val path = "/missing-default-ignored-status" + + val response = restTemplate.getForEntity(path, String::class.java) + + assertThat(response.statusCode.value()).isEqualTo(404) + server.assertNoEnvelope(millis = 1_000) { + it.transaction()?.request?.url?.endsWith(path) == true + } + } + @Test fun `failed request reports errors, spans with their flags, and logs`() { val response = restTemplate.getForEntity("/person/11", String::class.java) diff --git a/sentry-spring-boot/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/otelnoagentintegrationtest/PersonIntegrationTest.kt b/sentry-spring-boot/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/otelnoagentintegrationtest/PersonIntegrationTest.kt index 6e9b1c1038b..c9a5ffb72bc 100644 --- a/sentry-spring-boot/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/otelnoagentintegrationtest/PersonIntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTestOtelNoAgent/kotlin/io/sentry/spring/boot/otelnoagentintegrationtest/PersonIntegrationTest.kt @@ -21,6 +21,16 @@ import org.springframework.http.HttpHeaders class PersonIntegrationTest : IntegrationTest() { + @Test + fun `default not found response does not send a transaction`() { + val path = "/missing-default-ignored-status" + + val response = restTemplate.getForEntity(path, String::class.java) + + assertThat(response.statusCode.value()).isEqualTo(404) + server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true } + } + @Test fun `failed request reports errors, nested spans with their flags, and logs`() { val response = restTemplate.getForEntity("/person/11", String::class.java) diff --git a/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt b/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt index 35c00694d6f..57584cba90a 100644 --- a/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/PersonWebfluxIntegrationTest.kt @@ -67,7 +67,7 @@ class PersonWebfluxIntegrationTest : WebfluxIntegrationTest() { } @Test - fun `configured not found response does not send a transaction`() { + fun `default not found response does not send a transaction`() { val path = "/missing-ignored-status" val response = restTemplate.getForEntity(path, String::class.java) diff --git a/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/WebfluxIntegrationTest.kt b/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/WebfluxIntegrationTest.kt index b2ee1979640..f222e37b4ef 100644 --- a/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/WebfluxIntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTestWebflux/kotlin/io/sentry/spring/boot/webfluxintegrationtest/WebfluxIntegrationTest.kt @@ -26,7 +26,6 @@ import org.springframework.test.context.junit4.SpringRunner properties = [ "sentry.traces-sample-rate=1.0", - "sentry.ignored-server-http-status-codes-for-tracing=404", "sentry.logs.enabled=true", "sentry.profile-session-sample-rate=1.0", "sentry.profile-lifecycle=TRACE", diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt index 7f86feb1dfe..50fda4611e8 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt @@ -347,6 +347,15 @@ class SentryAutoConfigurationTest { } } + @Test + fun `ignored server HTTP status codes default to 404`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run { + val ranges = it.getBean(SentryProperties::class.java).ignoredServerHttpStatusCodesForTracing + assertThat(ranges).hasSize(1) + assertThat(ranges!!.single().isInRange(404)).isTrue() + } + } + @Test fun `empty ignored server HTTP status codes disable the configuration`() { contextRunner diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 04c1434892f..69a8fc0d902 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -598,7 +598,8 @@ public class SentryOptions implements RateLimiterConfig { */ private @Nullable List ignoredTransactions = null; - private @Nullable List ignoredServerHttpStatusCodesForTracing = null; + private @Nullable List ignoredServerHttpStatusCodesForTracing = + new ArrayList<>(Collections.singletonList(new HttpStatusCodeRange(404))); @ApiStatus.Experimental private @NotNull IBackpressureMonitor backpressureMonitor = NoOpBackpressureMonitor.getInstance(); @@ -3151,9 +3152,10 @@ public void setIgnoredTransactions(final @Nullable List ignoredTransacti /** * Returns the HTTP status codes and inclusive ranges for which incoming server transactions are - * not sent to Sentry. An empty list disables this filter. + * not sent to Sentry. Defaults to a single range containing 404. An empty list disables this + * filter. * - * @return the configured HTTP status code ranges, or {@code null} if unset + * @return the configured HTTP status code ranges, or {@code null} if explicitly set to null */ public @Nullable List getIgnoredServerHttpStatusCodesForTracing() { return ignoredServerHttpStatusCodesForTracing; @@ -3163,11 +3165,11 @@ public void setIgnoredTransactions(final @Nullable List ignoredTransacti * Sets the HTTP status codes and inclusive ranges for which incoming server transactions are not * sent to Sentry. For example, configure {@code new HttpStatusCodeRange(404)} for one status or * {@code new HttpStatusCodeRange(500, 599)} for an inclusive range. External configuration - * accepts comma-separated codes and ranges such as {@code 404,410,500-599}. An empty list - * disables this filter. + * accepts comma-separated codes and ranges such as {@code 404,410,500-599}. An empty list or + * {@code null} disables this filter. * * @param ignoredServerHttpStatusCodesForTracing the HTTP status code ranges, or {@code null} to - * leave the option unset + * disable the filter */ public void setIgnoredServerHttpStatusCodesForTracing( final @Nullable List ignoredServerHttpStatusCodesForTracing) { diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index e11920d5e0d..ccebf83cdaf 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -1385,10 +1385,7 @@ class SentryClientTest { } @Test - fun `server transaction with ignored HTTP status is dropped before callbacks`() { - fixture.sentryOptions.setIgnoredServerHttpStatusCodesForTracing( - listOf(HttpStatusCodeRange(404)) - ) + fun `server transaction with default ignored HTTP status is dropped before callbacks`() { val processor = mock() val beforeSend = mock() val replayController = mock() @@ -1425,18 +1422,29 @@ class SentryClientTest { } @Test - fun `server transaction with ignored HTTP status range accepts OTel long value`() { + fun `default HTTP status filter retains server 200`() { + fixture + .getSut() + .captureTransaction(createServerTransaction(200), fixture.sentryTracer.traceContext()) + + verify(fixture.transport).send(any(), anyOrNull()) + } + + @Test + fun `custom HTTP status range replaces default and accepts OTel long value`() { fixture.sentryOptions.setIgnoredServerHttpStatusCodesForTracing( listOf(HttpStatusCodeRange(500, 599)) ) + val sut = fixture.getSut() - val eventId = - fixture - .getSut() - .captureTransaction(createServerTransaction(503L), fixture.sentryTracer.traceContext()) + val ignoredEventId = + sut.captureTransaction(createServerTransaction(503L), fixture.sentryTracer.traceContext()) + val retainedEventId = + sut.captureTransaction(createServerTransaction(404), fixture.sentryTracer.traceContext()) - assertThat(eventId).isEqualTo(SentryId.EMPTY_ID) - verify(fixture.transport, never()).send(any(), anyOrNull()) + assertThat(ignoredEventId).isEqualTo(SentryId.EMPTY_ID) + assertThat(retainedEventId).isNotEqualTo(SentryId.EMPTY_ID) + verify(fixture.transport).send(any(), anyOrNull()) } @Test @@ -1451,10 +1459,7 @@ class SentryClientTest { } @Test - fun `HTTP status filter only applies to server transactions`() { - fixture.sentryOptions.setIgnoredServerHttpStatusCodesForTracing( - listOf(HttpStatusCodeRange(404)) - ) + fun `default HTTP status filter only applies to server transactions`() { val clientTransaction = createServerTransaction(404) clientTransaction.contexts.trace!!.data[SpanDataConvention.SENTRY_KIND_KEY] = "client" val missingKindTransaction = createServerTransaction(404) @@ -1468,10 +1473,7 @@ class SentryClientTest { } @Test - fun `HTTP status filter ignores missing and invalid root status values`() { - fixture.sentryOptions.setIgnoredServerHttpStatusCodesForTracing( - listOf(HttpStatusCodeRange(404)) - ) + fun `default HTTP status filter ignores missing and invalid root status values`() { val invalidStatusValues = listOf("404", 404.0, 404.5f, 99, 600, Long.MAX_VALUE) val sut = fixture.getSut() @@ -1489,10 +1491,7 @@ class SentryClientTest { } @Test - fun `HTTP status filter does not read child span attributes`() { - fixture.sentryOptions.setIgnoredServerHttpStatusCodesForTracing( - listOf(HttpStatusCodeRange(404)) - ) + fun `default HTTP status filter does not read child span attributes`() { val tracer = SentryTracer( TransactionContext("child-status-transaction", "op", TracesSamplingDecision(true)), @@ -1511,10 +1510,7 @@ class SentryClientTest { } @Test - fun `profiling trace deletion failure for ignored transaction is logged`() { - fixture.sentryOptions.setIgnoredServerHttpStatusCodesForTracing( - listOf(HttpStatusCodeRange(404)) - ) + fun `profiling trace deletion failure for default ignored transaction is logged`() { val traceFile = mock() whenever(traceFile.delete()).thenReturn(false) whenever(traceFile.path).thenReturn("undeletable.trace") diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 1a9dfac4502..a3b6417598d 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -1088,14 +1088,31 @@ class SentryOptionsTest { } @Test - fun `ignored server HTTP status codes for tracing are unset by default`() { - assertNull(SentryOptions().ignoredServerHttpStatusCodesForTracing) + fun `ignored server HTTP status codes for tracing default to 404`() { + val ranges = SentryOptions().ignoredServerHttpStatusCodesForTracing + + assertThat(ranges).hasSize(1) + assertThat(ranges!!.single().isInRange(404)).isTrue() + } + + @Test + fun `default ignored server HTTP status code lists are independent`() { + val first = SentryOptions().ignoredServerHttpStatusCodesForTracing!! + val second = SentryOptions().ignoredServerHttpStatusCodesForTracing!! + + first.clear() + + assertThat(second).hasSize(1) + assertThat(second.single().isInRange(404)).isTrue() } @Test - fun `ignored server HTTP status code setter preserves empty and custom ranges`() { + fun `ignored server HTTP status code setter preserves null, empty and custom ranges`() { val options = SentryOptions() + options.ignoredServerHttpStatusCodesForTracing = null + assertThat(options.ignoredServerHttpStatusCodesForTracing).isNull() + options.ignoredServerHttpStatusCodesForTracing = emptyList() assertThat(options.ignoredServerHttpStatusCodesForTracing).isEmpty() @@ -1107,11 +1124,10 @@ class SentryOptionsTest { } @Test - fun `merging empty ignored server HTTP status codes replaces the previous value`() { + fun `merging empty ignored server HTTP status codes clears the default`() { val externalOptions = ExternalOptions() externalOptions.ignoredServerHttpStatusCodesForTracing = emptyList() val options = SentryOptions() - options.ignoredServerHttpStatusCodesForTracing = listOf(HttpStatusCodeRange(404)) options.merge(externalOptions) @@ -1129,6 +1145,16 @@ class SentryOptionsTest { assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(410)).isTrue() } + @Test + fun `merging options when ignored server HTTP status codes are not set preserves default`() { + val options = SentryOptions() + + options.merge(ExternalOptions()) + + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(1) + assertThat(options.ignoredServerHttpStatusCodesForTracing!!.single().isInRange(404)).isTrue() + } + @Test fun `merging options when ignoredErrors is not set preserves the previous value`() { val externalOptions = ExternalOptions()