From 84b9ab5039b730b45ac166039ae12eacd3f220da Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 11 Nov 2026 16:32:16 +0100 Subject: [PATCH 1/2] feat(tracing): [Ignore HTTP Status 4] Ignore incoming 404s by default Stop sending incoming server transactions that finish with HTTP 404 unless users replace or clear the ignored status configuration. Document the opt-out and cover native Spring and OpenTelemetry paths across supported Spring Boot generations. Fixes #4736 --- CHANGELOG.md | 38 ++++++++++++++ .../boot4/integrationtest/IntegrationTest.kt | 1 - .../integrationtest/PersonIntegrationTest.kt | 2 +- .../PersonIntegrationTest.kt | 12 +++++ .../PersonIntegrationTest.kt | 10 ++++ .../PersonWebfluxIntegrationTest.kt | 2 +- .../WebfluxIntegrationTest.kt | 1 - .../boot4/SentryAutoConfigurationTest.kt | 9 ++++ .../integrationtest/IntegrationTest.kt | 1 - .../integrationtest/PersonIntegrationTest.kt | 2 +- .../PersonIntegrationTest.kt | 12 +++++ .../PersonIntegrationTest.kt | 10 ++++ .../PersonWebfluxIntegrationTest.kt | 2 +- .../WebfluxIntegrationTest.kt | 1 - .../jakarta/SentryAutoConfigurationTest.kt | 9 ++++ sentry-spring-boot/README.md | 24 +++++++-- .../boot/integrationtest/IntegrationTest.kt | 1 - .../integrationtest/PersonIntegrationTest.kt | 2 +- .../PersonIntegrationTest.kt | 12 +++++ .../PersonIntegrationTest.kt | 10 ++++ .../PersonWebfluxIntegrationTest.kt | 2 +- .../WebfluxIntegrationTest.kt | 1 - .../boot/SentryAutoConfigurationTest.kt | 9 ++++ .../main/java/io/sentry/SentryOptions.java | 14 +++--- .../test/java/io/sentry/SentryClientTest.kt | 50 +++++++++---------- .../test/java/io/sentry/SentryOptionsTest.kt | 36 +++++++++++-- 26 files changed, 218 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1f5ccece3..f2a5f321d92 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. - 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() From bf2fb4ccf0e5dba1d84bfbe6c3c0508a9819459c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 11 Nov 2026 16:34:18 +0100 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a5f321d92..996b651eeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,7 +89,7 @@ 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. + 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))