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 7360e732da..08a8a54c9d 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 fc2dd676ec..f574ced344 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 28b190de23..7f86feb1df 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 49e5242da7..7d71919832 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 efeb005f7b..89c2a0d5fd 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 690ab115c5..be56ab2154 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 71dd4588e3..04c1434892 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 349058e4b3..0059728721 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 b50806b0a0..fc9f480160 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 2a33e9b53f..1a9dfac450 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()