From 3c0b86da34729ea57b3fe7b064f82a419513e41c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 11 Nov 2026 13:30:40 +0100 Subject: [PATCH] feat(core): [Ignore HTTP Status 2] Add tracing status configuration Add an unset option for ignored incoming server HTTP status codes and ranges. Parse external configuration without clearing existing values when all entries are invalid, and support Spring Boot binding across all supported versions. This PR only adds configuration surfaces; filtering remains unchanged. Refs #4735 --- .../boot4/SentryAutoConfigurationTest.kt | 18 +++++ .../jakarta/SentryAutoConfigurationTest.kt | 18 +++++ .../boot/SentryAutoConfigurationTest.kt | 18 +++++ sentry/api/sentry.api | 5 ++ .../main/java/io/sentry/ExternalOptions.java | 37 ++++++++++ .../java/io/sentry/HttpStatusCodeRange.java | 58 +++++++++++++++ .../main/java/io/sentry/SentryOptions.java | 34 +++++++++ .../java/io/sentry/ExternalOptionsTest.kt | 72 +++++++++++++++++++ .../java/io/sentry/HttpStatusCodeRangeTest.kt | 43 +++++++++++ .../test/java/io/sentry/SentryOptionsTest.kt | 48 +++++++++++++ 10 files changed, 351 insertions(+) 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 7360e732da7..08a8a54c9d2 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 @@ -263,6 +263,7 @@ class SentryAutoConfigurationTest { "sentry.ignored-checkins=slug1,slugB", "sentry.ignored-errors=Some error,Another .*", "sentry.ignored-transactions=transactionName1,transactionNameB", + "sentry.ignored-server-http-status-codes-for-tracing=404,500-599", "sentry.enable-backpressure-handling=false", "sentry.enable-database-transaction-tracing=true", "sentry.enable-cache-tracing=true", @@ -316,6 +317,10 @@ class SentryAutoConfigurationTest { assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB") assertThat(options.ignoredErrors).containsOnly("Some error", "Another .*") assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB") + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(2) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(404)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(500)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(599)).isTrue() assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true) assertThat(options.isEnableCacheTracing).isEqualTo(true) @@ -336,6 +341,19 @@ class SentryAutoConfigurationTest { } } + @Test + fun `empty ignored server HTTP status codes disable the configuration`() { + contextRunner + .withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.ignored-server-http-status-codes-for-tracing=", + ) + .run { + assertThat(it.getBean(SentryProperties::class.java).ignoredServerHttpStatusCodesForTracing) + .isEmpty() + } + } + @Test fun `data collection properties are applied to SentryOptions`() { contextRunner 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 fc2dd676ec3..f574ced344c 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 @@ -266,6 +266,7 @@ class SentryAutoConfigurationTest { "sentry.ignored-checkins=slug1,slugB", "sentry.ignored-errors=Some error,Another .*", "sentry.ignored-transactions=transactionName1,transactionNameB", + "sentry.ignored-server-http-status-codes-for-tracing=404,500-599", "sentry.enable-backpressure-handling=false", "sentry.enable-database-transaction-tracing=true", "sentry.enable-spotlight=true", @@ -321,6 +322,10 @@ class SentryAutoConfigurationTest { assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB") assertThat(options.ignoredErrors).containsOnly("Some error", "Another .*") assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB") + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(2) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(404)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(500)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(599)).isTrue() assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true) assertThat(options.isForceInit).isEqualTo(true) @@ -344,6 +349,19 @@ class SentryAutoConfigurationTest { } } + @Test + fun `empty ignored server HTTP status codes disable the configuration`() { + contextRunner + .withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.ignored-server-http-status-codes-for-tracing=", + ) + .run { + assertThat(it.getBean(SentryProperties::class.java).ignoredServerHttpStatusCodesForTracing) + .isEmpty() + } + } + @Test fun `data collection properties are applied to SentryOptions`() { contextRunner 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 28b190de232..7f86feb1dfe 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 @@ -264,6 +264,7 @@ class SentryAutoConfigurationTest { "sentry.ignored-checkins=slug1,slugB", "sentry.ignored-errors=Some error,Another .*", "sentry.ignored-transactions=transactionName1,transactionNameB", + "sentry.ignored-server-http-status-codes-for-tracing=404,500-599", "sentry.enable-backpressure-handling=false", "sentry.enable-database-transaction-tracing=true", "sentry.enable-spotlight=true", @@ -319,6 +320,10 @@ class SentryAutoConfigurationTest { assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB") assertThat(options.ignoredErrors).containsOnly("Some error", "Another .*") assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB") + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(2) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(404)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(500)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(599)).isTrue() assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true) assertThat(options.isForceInit).isEqualTo(true) @@ -342,6 +347,19 @@ class SentryAutoConfigurationTest { } } + @Test + fun `empty ignored server HTTP status codes disable the configuration`() { + contextRunner + .withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.ignored-server-http-status-codes-for-tracing=", + ) + .run { + assertThat(it.getBean(SentryProperties::class.java).ignoredServerHttpStatusCodesForTracing) + .isEmpty() + } + } + @Test fun `data collection properties are applied to SentryOptions`() { contextRunner diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 49e5242da7f..7d719198328 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -567,6 +567,7 @@ public final class io/sentry/ExternalOptions { public fun getIgnoredCheckIns ()Ljava/util/List; public fun getIgnoredErrors ()Ljava/util/List; public fun getIgnoredExceptionsForType ()Ljava/util/Set; + public fun getIgnoredServerHttpStatusCodesForTracing ()Ljava/util/List; public fun getIgnoredTransactions ()Ljava/util/List; public fun getInAppExcludes ()Ljava/util/List; public fun getInAppIncludes ()Ljava/util/List; @@ -623,6 +624,7 @@ public final class io/sentry/ExternalOptions { public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V public fun setIgnoredErrors (Ljava/util/List;)V + public fun setIgnoredServerHttpStatusCodesForTracing (Ljava/util/List;)V public fun setIgnoredTransactions (Ljava/util/List;)V public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V public fun setOrgId (Ljava/lang/String;)V @@ -710,6 +712,7 @@ public final class io/sentry/HttpStatusCodeRange { public fun (I)V public fun (II)V public fun isInRange (I)Z + public static fun valueOf (Ljava/lang/String;)Lio/sentry/HttpStatusCodeRange; } public abstract interface class io/sentry/IAppStartExtender { @@ -3556,6 +3559,7 @@ public class io/sentry/SentryOptions : io/sentry/transport/RateLimiterConfig { public fun getIgnoredErrorFilters ()Ljava/util/List; public fun getIgnoredErrors ()Ljava/util/List; public fun getIgnoredExceptionsForType ()Ljava/util/Set; + public fun getIgnoredServerHttpStatusCodesForTracing ()Ljava/util/List; public fun getIgnoredSpanOriginFilters ()Ljava/util/List; public fun getIgnoredSpanOrigins ()Ljava/util/List; public fun getIgnoredTransactionFilters ()Ljava/util/List; @@ -3729,6 +3733,7 @@ public class io/sentry/SentryOptions : io/sentry/transport/RateLimiterConfig { public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V public fun setIgnoredErrors (Ljava/util/List;)V + public fun setIgnoredServerHttpStatusCodesForTracing (Ljava/util/List;)V public fun setIgnoredSpanOrigins (Ljava/util/List;)V public fun setIgnoredTransactions (Ljava/util/List;)V public fun setInitPriority (Lio/sentry/InitPriority;)V diff --git a/sentry/src/main/java/io/sentry/ExternalOptions.java b/sentry/src/main/java/io/sentry/ExternalOptions.java index efeb005f7b5..89c2a0d5fd0 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -50,6 +50,7 @@ public final class ExternalOptions { private @Nullable List ignoredCheckIns; private @Nullable List ignoredTransactions; + private @Nullable List ignoredServerHttpStatusCodesForTracing; private @Nullable Boolean sendModules; private @Nullable Boolean sendDefaultPii; @@ -160,6 +161,8 @@ public final class ExternalOptions { options.setIgnoredCheckIns(propertiesProvider.getListOrNull("ignored-checkins")); options.setIgnoredTransactions(propertiesProvider.getListOrNull("ignored-transactions")); + options.setIgnoredServerHttpStatusCodesForTracing( + parseIgnoredServerHttpStatusCodesForTracing(propertiesProvider, logger)); options.setEnableBackpressureHandling( propertiesProvider.getBooleanProperty("enable-backpressure-handling")); @@ -243,6 +246,31 @@ public final class ExternalOptions { return options; } + private static @Nullable List parseIgnoredServerHttpStatusCodesForTracing( + final @NotNull PropertiesProvider propertiesProvider, final @NotNull ILogger logger) { + final String value = + propertiesProvider.getProperty("ignored-server-http-status-codes-for-tracing"); + if (value == null) { + return null; + } + if (value.trim().isEmpty()) { + return new ArrayList<>(); + } + + final List ranges = new ArrayList<>(); + for (final String token : value.split(",", -1)) { + try { + ranges.add(HttpStatusCodeRange.valueOf(token)); + } catch (IllegalArgumentException e) { + logger.log( + SentryLevel.WARNING, + "Ignoring invalid HTTP status code range in ignored-server-http-status-codes-for-tracing: %s", + token); + } + } + return ranges.isEmpty() ? null : ranges; + } + private static @Nullable DataCollection parseDataCollection( final @NotNull PropertiesProvider propertiesProvider) { final DataCollection dataCollection = new DataCollection(); @@ -622,6 +650,15 @@ public void setIgnoredTransactions(final @Nullable List ignoredTransacti return ignoredTransactions; } + public void setIgnoredServerHttpStatusCodesForTracing( + final @Nullable List ignoredServerHttpStatusCodesForTracing) { + this.ignoredServerHttpStatusCodesForTracing = ignoredServerHttpStatusCodesForTracing; + } + + public @Nullable List getIgnoredServerHttpStatusCodesForTracing() { + return ignoredServerHttpStatusCodesForTracing; + } + public void setEnableBackpressureHandling(final @Nullable Boolean enableBackpressureHandling) { this.enableBackpressureHandling = enableBackpressureHandling; } diff --git a/sentry/src/main/java/io/sentry/HttpStatusCodeRange.java b/sentry/src/main/java/io/sentry/HttpStatusCodeRange.java index 690ab115c5c..be56ab2154e 100644 --- a/sentry/src/main/java/io/sentry/HttpStatusCodeRange.java +++ b/sentry/src/main/java/io/sentry/HttpStatusCodeRange.java @@ -1,5 +1,8 @@ package io.sentry; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + /** * The Http status code range. Example for a range: 400 to 499, 500 to 599, 400 to 599 The range is * inclusive so the min and max is considered part of the range. @@ -10,6 +13,9 @@ public final class HttpStatusCodeRange { public static final int DEFAULT_MIN = 500; public static final int DEFAULT_MAX = 599; + private static final int MIN_HTTP_STATUS_CODE = 100; + private static final int MAX_HTTP_STATUS_CODE = 599; + private final int min; private final int max; @@ -23,6 +29,58 @@ public HttpStatusCodeRange(final int statusCode) { this.max = statusCode; } + /** + * Parses a single HTTP status code or an inclusive range such as {@code 404} or {@code 500-599}. + * + * @param value the status code or range + * @return the parsed range + * @throws IllegalArgumentException if the value is malformed, outside 100-599, or reversed + */ + @ApiStatus.Internal + public static @NotNull HttpStatusCodeRange valueOf(final @NotNull String value) { + final String trimmedValue = value.trim(); + final int separatorIndex = trimmedValue.indexOf('-'); + if (separatorIndex < 0) { + return new HttpStatusCodeRange(parseStatusCode(trimmedValue)); + } + if (separatorIndex == 0 + || separatorIndex == trimmedValue.length() - 1 + || separatorIndex != trimmedValue.lastIndexOf('-')) { + throw invalidRange(value); + } + + final int min = parseStatusCode(trimmedValue.substring(0, separatorIndex).trim()); + final int max = parseStatusCode(trimmedValue.substring(separatorIndex + 1).trim()); + if (min > max) { + throw invalidRange(value); + } + return new HttpStatusCodeRange(min, max); + } + + private static int parseStatusCode(final @NotNull String value) { + for (int i = 0; i < value.length(); i++) { + if (!Character.isDigit(value.charAt(i))) { + throw invalidRange(value); + } + } + + final int statusCode; + try { + statusCode = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw invalidRange(value); + } + if (statusCode < MIN_HTTP_STATUS_CODE || statusCode > MAX_HTTP_STATUS_CODE) { + throw invalidRange(value); + } + return statusCode; + } + + private static @NotNull IllegalArgumentException invalidRange(final @NotNull String value) { + return new IllegalArgumentException( + "HTTP status code range must contain values from 100 to 599: " + value); + } + public boolean isInRange(final int statusCode) { return statusCode >= min && statusCode <= max; } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 71dd4588e33..04c1434892f 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -598,6 +598,8 @@ public class SentryOptions implements RateLimiterConfig { */ private @Nullable List ignoredTransactions = null; + private @Nullable List ignoredServerHttpStatusCodesForTracing = null; + @ApiStatus.Experimental private @NotNull IBackpressureMonitor backpressureMonitor = NoOpBackpressureMonitor.getInstance(); @@ -3147,6 +3149,34 @@ 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. + * + * @return the configured HTTP status code ranges, or {@code null} if unset + */ + public @Nullable List getIgnoredServerHttpStatusCodesForTracing() { + return ignoredServerHttpStatusCodesForTracing; + } + + /** + * 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. + * + * @param ignoredServerHttpStatusCodesForTracing the HTTP status code ranges, or {@code null} to + * leave the option unset + */ + public void setIgnoredServerHttpStatusCodesForTracing( + final @Nullable List ignoredServerHttpStatusCodesForTracing) { + this.ignoredServerHttpStatusCodesForTracing = + ignoredServerHttpStatusCodesForTracing == null + ? null + : new ArrayList<>(ignoredServerHttpStatusCodesForTracing); + } + /** Returns the current {@link SentryDateProvider} that is used to retrieve the current date. */ @ApiStatus.Internal public @NotNull SentryDateProvider getDateProvider() { @@ -3823,6 +3853,10 @@ public void merge(final @NotNull ExternalOptions options) { final List ignoredTransactions = new ArrayList<>(options.getIgnoredTransactions()); setIgnoredTransactions(ignoredTransactions); } + if (options.getIgnoredServerHttpStatusCodesForTracing() != null) { + setIgnoredServerHttpStatusCodesForTracing( + options.getIgnoredServerHttpStatusCodesForTracing()); + } if (options.getIgnoredErrors() != null) { final List ignoredExceptions = new ArrayList<>(options.getIgnoredErrors()); setIgnoredErrors(ignoredExceptions); diff --git a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt index 349058e4b3b..0059728721d 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -14,6 +14,7 @@ import org.junit.rules.TemporaryFolder import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.times import org.mockito.kotlin.verify class ExternalOptionsTest { @@ -408,6 +409,77 @@ class ExternalOptionsTest { withPropertiesFile("Another .*", logger) { options -> assertNull(options.ignoredTransactions) } } + @Test + fun `ignored server HTTP status codes are null if missing`() { + withPropertiesFile { assertThat(it.ignoredServerHttpStatusCodesForTracing).isNull() } + } + + @Test + fun `creates empty ignored server HTTP status codes from empty input`() { + withPropertiesFile("ignored-server-http-status-codes-for-tracing=") { + assertThat(it.ignoredServerHttpStatusCodesForTracing).isEmpty() + } + } + + @Test + fun `creates ignored server HTTP status codes and ranges`() { + withPropertiesFile("ignored-server-http-status-codes-for-tracing=404, 410 , 500-599") { options + -> + val ranges = options.ignoredServerHttpStatusCodesForTracing + assertThat(ranges).hasSize(3) + assertThat(ranges!![0].isInRange(404)).isTrue() + assertThat(ranges[1].isInRange(410)).isTrue() + assertThat(ranges[2].isInRange(500)).isTrue() + assertThat(ranges[2].isInRange(599)).isTrue() + } + } + + @Test + fun `ignores invalid entries while preserving valid ignored server HTTP status codes`() { + val logger = mock() + withPropertiesFile( + "ignored-server-http-status-codes-for-tracing=404,invalid,600,500-400", + logger, + ) { options -> + val ranges = options.ignoredServerHttpStatusCodesForTracing + assertThat(ranges).hasSize(1) + assertThat(ranges!![0].isInRange(404)).isTrue() + } + verify(logger, times(3)).log(eq(SentryLevel.WARNING), any(), any()) + } + + @Test + fun `all invalid ignored server HTTP status codes leave existing configuration unchanged`() { + withPropertiesFile("ignored-server-http-status-codes-for-tracing=invalid,600,500-400") { + externalOptions -> + assertThat(externalOptions.ignoredServerHttpStatusCodesForTracing).isNull() + + val options = + SentryOptions().apply { + ignoredServerHttpStatusCodesForTracing = listOf(HttpStatusCodeRange(410)) + } + options.merge(externalOptions) + + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(1) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(410)).isTrue() + } + } + + @Test + fun `system property takes precedence for ignored server HTTP status codes`() { + System.setProperty("sentry.ignored-server-http-status-codes-for-tracing", "410") + try { + withPropertiesFile("ignored-server-http-status-codes-for-tracing=404") { options -> + val ranges = options.ignoredServerHttpStatusCodesForTracing + assertThat(ranges).hasSize(1) + assertThat(ranges!![0].isInRange(410)).isTrue() + assertThat(ranges[0].isInRange(404)).isFalse() + } + } finally { + System.clearProperty("sentry.ignored-server-http-status-codes-for-tracing") + } + } + @Test fun `creates options with enableBackpressureHandling set to false`() { withPropertiesFile("enable-backpressure-handling=false") { options -> diff --git a/sentry/src/test/java/io/sentry/HttpStatusCodeRangeTest.kt b/sentry/src/test/java/io/sentry/HttpStatusCodeRangeTest.kt index b50806b0a00..fc9f4801606 100644 --- a/sentry/src/test/java/io/sentry/HttpStatusCodeRangeTest.kt +++ b/sentry/src/test/java/io/sentry/HttpStatusCodeRangeTest.kt @@ -1,6 +1,7 @@ package io.sentry import kotlin.test.Test +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -53,4 +54,46 @@ class HttpStatusCodeRangeTest { assertFalse(range.isInRange(600)) } + + @Test + fun `parses a single status code`() { + val range = HttpStatusCodeRange.valueOf("404") + + assertTrue(range.isInRange(404)) + assertFalse(range.isInRange(405)) + } + + @Test + fun `parses an inclusive range with whitespace`() { + val range = HttpStatusCodeRange.valueOf(" 500 - 599 ") + + assertTrue(range.isInRange(500)) + assertTrue(range.isInRange(599)) + assertFalse(range.isInRange(499)) + } + + @Test + fun `parses HTTP status boundaries`() { + assertTrue(HttpStatusCodeRange.valueOf("100-599").isInRange(100)) + assertTrue(HttpStatusCodeRange.valueOf("100-599").isInRange(599)) + } + + @Test + fun `rejects malformed ranges`() { + listOf("", "invalid", "+404", "404-", "-404", "400-499-599").forEach { + assertFailsWith { HttpStatusCodeRange.valueOf(it) } + } + } + + @Test + fun `rejects reversed ranges`() { + assertFailsWith { HttpStatusCodeRange.valueOf("500-400") } + } + + @Test + fun `rejects status codes outside HTTP range`() { + listOf("99", "600", "99-200", "500-600").forEach { + assertFailsWith { HttpStatusCodeRange.valueOf(it) } + } + } } diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 2a33e9b53f1..1a9dfac4502 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -570,6 +570,8 @@ class SentryOptionsTest { externalOptions.isSendModules = false externalOptions.ignoredCheckIns = listOf("slug1", "slug-B") externalOptions.ignoredTransactions = listOf("transactionName1", "transaction-name-B") + externalOptions.ignoredServerHttpStatusCodesForTracing = + listOf(HttpStatusCodeRange(404), HttpStatusCodeRange(500, 599)) externalOptions.ignoredErrors = listOf("Some error", "Another .*") externalOptions.isEnableBackpressureHandling = false externalOptions.isEnableDatabaseTransactionTracing = true @@ -631,6 +633,10 @@ class SentryOptionsTest { listOf("transactionName1", "transaction-name-B"), options.ignoredTransactions, ) + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(2) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(404)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(500)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(599)).isTrue() assertEquals( listOf("Some error", "Another .*"), options.ignoredErrors, @@ -1081,6 +1087,48 @@ class SentryOptionsTest { assertNull(options.ignoredSpanOrigins) } + @Test + fun `ignored server HTTP status codes for tracing are unset by default`() { + assertNull(SentryOptions().ignoredServerHttpStatusCodesForTracing) + } + + @Test + fun `ignored server HTTP status code setter preserves empty and custom ranges`() { + val options = SentryOptions() + + options.ignoredServerHttpStatusCodesForTracing = emptyList() + assertThat(options.ignoredServerHttpStatusCodesForTracing).isEmpty() + + options.ignoredServerHttpStatusCodesForTracing = + listOf(HttpStatusCodeRange(404), HttpStatusCodeRange(500, 599)) + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(2) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(404)).isTrue() + assertThat(options.ignoredServerHttpStatusCodesForTracing!![1].isInRange(599)).isTrue() + } + + @Test + fun `merging empty ignored server HTTP status codes replaces the previous value`() { + val externalOptions = ExternalOptions() + externalOptions.ignoredServerHttpStatusCodesForTracing = emptyList() + val options = SentryOptions() + options.ignoredServerHttpStatusCodesForTracing = listOf(HttpStatusCodeRange(404)) + + options.merge(externalOptions) + + assertThat(options.ignoredServerHttpStatusCodesForTracing).isEmpty() + } + + @Test + fun `merging options when ignored server HTTP status codes are not set preserves previous value`() { + val options = SentryOptions() + options.ignoredServerHttpStatusCodesForTracing = listOf(HttpStatusCodeRange(410)) + + options.merge(ExternalOptions()) + + assertThat(options.ignoredServerHttpStatusCodesForTracing).hasSize(1) + assertThat(options.ignoredServerHttpStatusCodesForTracing!![0].isInRange(410)).isTrue() + } + @Test fun `merging options when ignoredErrors is not set preserves the previous value`() { val externalOptions = ExternalOptions()