diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fbdf6cfd70..0fb73b3c75b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ - This keeps it from being garbage collected too early - Close backpressure monitor on SDK shutdown ([#3998](https://github.com/getsentry/sentry-java/pull/3998)) - Due to the backpressure monitor rescheduling a task to run every 10s, it very likely caused shutdown to wait the full `shutdownTimeoutMillis` (defaulting to 2s) instead of being able to terminate immediately +- Improve ignored check performance ([#3992](https://github.com/getsentry/sentry-java/pull/3992)) + - Checking if a span origin, a transaction or a checkIn should be ignored is now faster ## 8.0.0-rc.2 diff --git a/sentry-apollo-3/src/test/java/io/sentry/apollo3/SentryApollo3InterceptorTest.kt b/sentry-apollo-3/src/test/java/io/sentry/apollo3/SentryApollo3InterceptorTest.kt index 30689f0b3b5..03072ae8b5a 100644 --- a/sentry-apollo-3/src/test/java/io/sentry/apollo3/SentryApollo3InterceptorTest.kt +++ b/sentry-apollo-3/src/test/java/io/sentry/apollo3/SentryApollo3InterceptorTest.kt @@ -210,7 +210,7 @@ class SentryApollo3InterceptorTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { - fixture.options.ignoredSpanOrigins = listOf("auto.graphql.apollo3") + fixture.options.setIgnoredSpanOrigins(listOf("auto.graphql.apollo3")) executeQuery(isSpanActive = false) val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!! diff --git a/sentry-apollo/src/test/java/io/sentry/apollo/SentryApolloInterceptorTest.kt b/sentry-apollo/src/test/java/io/sentry/apollo/SentryApolloInterceptorTest.kt index 97d13555f0a..937aae53404 100644 --- a/sentry-apollo/src/test/java/io/sentry/apollo/SentryApolloInterceptorTest.kt +++ b/sentry-apollo/src/test/java/io/sentry/apollo/SentryApolloInterceptorTest.kt @@ -163,7 +163,7 @@ class SentryApolloInterceptorTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { - fixture.options.ignoredSpanOrigins = listOf("auto.graphql.apollo") + fixture.options.setIgnoredSpanOrigins(listOf("auto.graphql.apollo")) executeQuery(isSpanActive = false) val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!! diff --git a/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt index 53b4cca93a9..f18b9673337 100644 --- a/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt +++ b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt @@ -213,7 +213,7 @@ class SentryOkHttpInterceptorTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { val sut = fixture.getSut(isSpanActive = false) { options -> - options.ignoredSpanOrigins = listOf("auto.http.okhttp") + options.setIgnoredSpanOrigins(listOf("auto.http.okhttp")) } sut.newCall(getRequest()).execute() val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!! diff --git a/sentry-openfeign/src/test/kotlin/io/sentry/openfeign/SentryFeignClientTest.kt b/sentry-openfeign/src/test/kotlin/io/sentry/openfeign/SentryFeignClientTest.kt index e34feed7ff9..539abbf70bd 100644 --- a/sentry-openfeign/src/test/kotlin/io/sentry/openfeign/SentryFeignClientTest.kt +++ b/sentry-openfeign/src/test/kotlin/io/sentry/openfeign/SentryFeignClientTest.kt @@ -133,7 +133,7 @@ class SentryFeignClientTest { @Test fun `does not add sentry trace header when span origin is ignored`() { fixture.sentryOptions.dsn = "https://key@sentry.io/proj" - fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.openfeign") + fixture.sentryOptions.setIgnoredSpanOrigins(listOf("auto.http.openfeign")) val sut = fixture.getSut(isSpanActive = false) sut.getOk() val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!! 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 1263c79df7f..e559502e626 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 @@ -5,6 +5,7 @@ import io.opentelemetry.api.OpenTelemetry import io.sentry.AsyncHttpTransportFactory import io.sentry.Breadcrumb import io.sentry.EventProcessor +import io.sentry.FilterString import io.sentry.Hint import io.sentry.IScopes import io.sentry.ITransportFactory @@ -213,8 +214,8 @@ class SentryAutoConfigurationTest { assertThat(options.tracePropagationTargets).containsOnly("localhost", "^(http|https)://api\\..*\$") assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) - assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB") - assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB") + assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) + assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) assertThat(options.isGlobalHubMode).isEqualTo(true) diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestClientCustomizerTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestClientCustomizerTest.kt index 2f98188eef2..a69059482de 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestClientCustomizerTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestClientCustomizerTest.kt @@ -251,7 +251,7 @@ class SentrySpanRestClientCustomizerTest { @Test fun `does not add sentry-trace header if span origin is ignored`() { - fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.restclient") + fixture.sentryOptions.setIgnoredSpanOrigins(listOf("auto.http.spring_jakarta.restclient")) val sut = fixture.getSut(isTransactionActive = false) val headers = HttpHeaders() diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt index b928fd62892..7f4a4c4fbbe 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt @@ -201,7 +201,7 @@ class SentrySpanRestTemplateCustomizerTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { - fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.resttemplate") + fixture.sentryOptions.setIgnoredSpanOrigins(listOf("auto.http.spring_jakarta.resttemplate")) val sut = fixture.getSut(isTransactionActive = false) val headers = HttpHeaders() val requestEntity = HttpEntity(headers) diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanWebClientCustomizerTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanWebClientCustomizerTest.kt index 55f43c8121e..71b949adcd4 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanWebClientCustomizerTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanWebClientCustomizerTest.kt @@ -168,7 +168,7 @@ class SentrySpanWebClientCustomizerTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { val sut = fixture.getSut(isTransactionActive = false, includeMockServerInTracingOrigins = true) { options -> - options.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.webclient") + options.setIgnoredSpanOrigins(listOf("auto.http.spring_jakarta.webclient")) } sut .get() 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 2ec9c763fce..2512fe1f11e 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 @@ -5,6 +5,7 @@ import io.opentelemetry.api.OpenTelemetry import io.sentry.AsyncHttpTransportFactory import io.sentry.Breadcrumb import io.sentry.EventProcessor +import io.sentry.FilterString import io.sentry.Hint import io.sentry.IScopes import io.sentry.ITransportFactory @@ -212,8 +213,8 @@ class SentryAutoConfigurationTest { assertThat(options.tracePropagationTargets).containsOnly("localhost", "^(http|https)://api\\..*\$") assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) - assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB") - assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB") + assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) + assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) assertThat(options.isGlobalHubMode).isEqualTo(true) diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt index e20ebed3fb9..bd2e98a99bb 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt @@ -203,7 +203,7 @@ class SentrySpanRestTemplateCustomizerTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { - fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.spring.resttemplate") + fixture.sentryOptions.setIgnoredSpanOrigins(listOf("auto.http.spring.resttemplate")) val sut = fixture.getSut(isTransactionActive = false) val headers = HttpHeaders() val requestEntity = HttpEntity(headers) diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanWebClientCustomizerTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanWebClientCustomizerTest.kt index f72232da2f1..f0c17333ae9 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanWebClientCustomizerTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanWebClientCustomizerTest.kt @@ -170,7 +170,7 @@ class SentrySpanWebClientCustomizerTest { @Test fun `does not add sentry-trace header when span origin is ignored`() { val sut = fixture.getSut(isTransactionActive = false, includeMockServerInTracingOrigins = true) { options -> - options.ignoredSpanOrigins = listOf("auto.http.spring.webclient") + options.setIgnoredSpanOrigins(listOf("auto.http.spring.webclient")) } sut .get() diff --git a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/tracing/SentryTracingFilterTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/tracing/SentryTracingFilterTest.kt index 864c5a57115..ff0020ddfeb 100644 --- a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/tracing/SentryTracingFilterTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/tracing/SentryTracingFilterTest.kt @@ -291,7 +291,7 @@ class SentryTracingFilterTest { val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1" val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET") fixture.options.tracesSampleRate = null - fixture.options.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.webmvc") + fixture.options.setIgnoredSpanOrigins(listOf("auto.http.spring_jakarta.webmvc")) val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings) filter.doFilter(fixture.request, fixture.response, fixture.chain) diff --git a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt index bfd2d9e1adb..c7b732f4602 100644 --- a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt @@ -330,7 +330,7 @@ class SentryWebFluxTracingFilterTest { val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1" val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET") fixture.options.tracesSampleRate = null - fixture.options.ignoredSpanOrigins = listOf("auto.spring_jakarta.webflux") + fixture.options.setIgnoredSpanOrigins(listOf("auto.spring_jakarta.webflux")) val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings) withMockScopes { diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/tracing/SentryTracingFilterTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/tracing/SentryTracingFilterTest.kt index c6eac05a146..538ac7c8cc6 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/tracing/SentryTracingFilterTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/tracing/SentryTracingFilterTest.kt @@ -291,7 +291,7 @@ class SentryTracingFilterTest { val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1" val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET") fixture.options.tracesSampleRate = null - fixture.options.ignoredSpanOrigins = listOf("auto.http.spring.webmvc") + fixture.options.setIgnoredSpanOrigins(listOf("auto.http.spring.webmvc")) val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings) filter.doFilter(fixture.request, fixture.response, fixture.chain) diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt index 56e925ab3fd..6eba98d1d2e 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt @@ -331,7 +331,7 @@ class SentryWebFluxTracingFilterTest { val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1" val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET") fixture.options.tracesSampleRate = null - fixture.options.ignoredSpanOrigins = listOf("auto.spring.webflux") + fixture.options.setIgnoredSpanOrigins(listOf("auto.spring.webflux")) val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings) withMockScopes { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 59e1b1251f6..d7ea46862f8 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -509,6 +509,14 @@ public final class io/sentry/ExternalOptions { public fun setTracesSampleRate (Ljava/lang/Double;)V } +public final class io/sentry/FilterString { + public fun (Ljava/lang/String;)V + public fun equals (Ljava/lang/Object;)Z + public fun getFilterString ()Ljava/lang/String; + public fun hashCode ()I + public fun matches (Ljava/lang/String;)Z +} + public final class io/sentry/FullyDisplayedReporter { public static fun getInstance ()Lio/sentry/FullyDisplayedReporter; public fun registerFullyDrawnListener (Lio/sentry/FullyDisplayedReporter$FullyDisplayedReporterListener;)V @@ -2806,8 +2814,10 @@ public class io/sentry/SentryOptions { public fun addBundleId (Ljava/lang/String;)V public fun addContextTag (Ljava/lang/String;)V public fun addEventProcessor (Lio/sentry/EventProcessor;)V + public fun addIgnoredCheckIn (Ljava/lang/String;)V public fun addIgnoredExceptionForType (Ljava/lang/Class;)V public fun addIgnoredSpanOrigin (Ljava/lang/String;)V + public fun addIgnoredTransaction (Ljava/lang/String;)V public fun addInAppExclude (Ljava/lang/String;)V public fun addInAppInclude (Ljava/lang/String;)V public fun addIntegration (Lio/sentry/Integration;)V diff --git a/sentry/src/main/java/io/sentry/FilterString.java b/sentry/src/main/java/io/sentry/FilterString.java new file mode 100644 index 00000000000..8dd5f479492 --- /dev/null +++ b/sentry/src/main/java/io/sentry/FilterString.java @@ -0,0 +1,35 @@ +package io.sentry; + +import java.util.Objects; +import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; + +public final class FilterString { + private final @NotNull String filterString; + private final @NotNull Pattern pattern; + + public FilterString(@NotNull String filterString) { + this.filterString = filterString; + this.pattern = Pattern.compile(filterString); + } + + public @NotNull String getFilterString() { + return filterString; + } + + public boolean matches(String input) { + return pattern.matcher(input).matches(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + FilterString that = (FilterString) o; + return Objects.equals(filterString, that.filterString); + } + + @Override + public int hashCode() { + return Objects.hash(filterString); + } +} diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 21535a0fe55..24da2eaa397 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -466,12 +466,12 @@ public class SentryOptions { private boolean enableScopePersistence = true; /** Contains a list of monitor slugs for which check-ins should not be sent. */ - @ApiStatus.Experimental private @Nullable List ignoredCheckIns = null; + @ApiStatus.Experimental private @Nullable List ignoredCheckIns = null; /** Contains a list of span origins for which spans / transactions should not be created. */ - @ApiStatus.Experimental private @Nullable List ignoredSpanOrigins = null; + @ApiStatus.Experimental private @Nullable List ignoredSpanOrigins = null; - private @Nullable List ignoredTransactions = null; + private @Nullable List ignoredTransactions = null; @ApiStatus.Experimental private @NotNull IBackpressureMonitor backpressureMonitor = NoOpBackpressureMonitor.getInstance(); @@ -2175,31 +2175,16 @@ public void setSendModules(boolean sendModules) { } @ApiStatus.Experimental - public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { - if (ignoredCheckIns == null) { - this.ignoredCheckIns = null; - } else { - @NotNull final List filteredIgnoredCheckIns = new ArrayList<>(); - for (String slug : ignoredCheckIns) { - if (!slug.isEmpty()) { - filteredIgnoredCheckIns.add(slug); - } - } - - this.ignoredCheckIns = filteredIgnoredCheckIns; - } - } - - @ApiStatus.Experimental - public @Nullable List getIgnoredSpanOrigins() { + public @Nullable List getIgnoredSpanOrigins() { return ignoredSpanOrigins; } - public void addIgnoredSpanOrigin(final @NotNull String origin) { - if (this.ignoredSpanOrigins == null) { - this.ignoredSpanOrigins = new ArrayList<>(); + @ApiStatus.Experimental + public void addIgnoredSpanOrigin(String ignoredSpanOrigin) { + if (ignoredSpanOrigins == null) { + ignoredSpanOrigins = new ArrayList<>(); } - this.ignoredSpanOrigins.add(origin); + ignoredSpanOrigins.add(new FilterString(ignoredSpanOrigin)); } @ApiStatus.Experimental @@ -2207,10 +2192,10 @@ public void setIgnoredSpanOrigins(final @Nullable List ignoredSpanOrigin if (ignoredSpanOrigins == null) { this.ignoredSpanOrigins = null; } else { - @NotNull final List filtered = new ArrayList<>(); + @NotNull final List filtered = new ArrayList<>(); for (String origin : ignoredSpanOrigins) { if (origin != null && !origin.isEmpty()) { - filtered.add(origin); + filtered.add(new FilterString(origin)); } } @@ -2219,23 +2204,55 @@ public void setIgnoredSpanOrigins(final @Nullable List ignoredSpanOrigin } @ApiStatus.Experimental - public @Nullable List getIgnoredCheckIns() { + public @Nullable List getIgnoredCheckIns() { return ignoredCheckIns; } - public @Nullable List getIgnoredTransactions() { + @ApiStatus.Experimental + public void addIgnoredCheckIn(String ignoredCheckIn) { + if (ignoredCheckIns == null) { + ignoredCheckIns = new ArrayList<>(); + } + ignoredCheckIns.add(new FilterString(ignoredCheckIn)); + } + + @ApiStatus.Experimental + public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { + if (ignoredCheckIns == null) { + this.ignoredCheckIns = null; + } else { + @NotNull final List filteredIgnoredCheckIns = new ArrayList<>(); + for (String slug : ignoredCheckIns) { + if (!slug.isEmpty()) { + filteredIgnoredCheckIns.add(new FilterString(slug)); + } + } + + this.ignoredCheckIns = filteredIgnoredCheckIns; + } + } + + public @Nullable List getIgnoredTransactions() { return ignoredTransactions; } + @ApiStatus.Experimental + public void addIgnoredTransaction(String ignoredTransaction) { + if (ignoredTransactions == null) { + ignoredTransactions = new ArrayList<>(); + } + ignoredTransactions.add(new FilterString(ignoredTransaction)); + } + @ApiStatus.Experimental public void setIgnoredTransactions(final @Nullable List ignoredTransactions) { if (ignoredTransactions == null) { this.ignoredTransactions = null; } else { - @NotNull final List filtered = new ArrayList<>(); + @NotNull final List filtered = new ArrayList<>(); for (String transactionName : ignoredTransactions) { if (transactionName != null && !transactionName.isEmpty()) { - filtered.add(transactionName); + filtered.add(new FilterString(transactionName)); } } diff --git a/sentry/src/main/java/io/sentry/util/CheckInUtils.java b/sentry/src/main/java/io/sentry/util/CheckInUtils.java index 49846ac1392..7b44fffbc35 100644 --- a/sentry/src/main/java/io/sentry/util/CheckInUtils.java +++ b/sentry/src/main/java/io/sentry/util/CheckInUtils.java @@ -3,6 +3,7 @@ import io.sentry.CheckIn; import io.sentry.CheckInStatus; import io.sentry.DateUtils; +import io.sentry.FilterString; import io.sentry.IScopes; import io.sentry.ISentryLifecycleToken; import io.sentry.MonitorConfig; @@ -116,18 +117,20 @@ public static U withCheckIn( /** Checks if a check-in for a monitor (CRON) has been ignored. */ @ApiStatus.Internal public static boolean isIgnored( - final @Nullable List ignoredSlugs, final @NotNull String slug) { + final @Nullable List ignoredSlugs, final @NotNull String slug) { if (ignoredSlugs == null || ignoredSlugs.isEmpty()) { return false; } - for (final String ignoredSlug : ignoredSlugs) { - if (ignoredSlug.equalsIgnoreCase(slug)) { + for (final FilterString ignoredSlug : ignoredSlugs) { + if (ignoredSlug.getFilterString().equalsIgnoreCase(slug)) { return true; } + } + for (final FilterString ignoredSlug : ignoredSlugs) { try { - if (slug.matches(ignoredSlug)) { + if (ignoredSlug.matches(slug)) { return true; } } catch (Throwable t) { diff --git a/sentry/src/main/java/io/sentry/util/SpanUtils.java b/sentry/src/main/java/io/sentry/util/SpanUtils.java index 323bdbee987..ed846271713 100644 --- a/sentry/src/main/java/io/sentry/util/SpanUtils.java +++ b/sentry/src/main/java/io/sentry/util/SpanUtils.java @@ -1,8 +1,11 @@ package io.sentry.util; +import io.sentry.FilterString; import io.sentry.SentryOpenTelemetryMode; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,21 +44,31 @@ public final class SpanUtils { return origins; } + private static final Map ignoredSpanDecisionsCache = new ConcurrentHashMap<>(); + /** Checks if a span origin has been ignored. */ @ApiStatus.Internal public static boolean isIgnored( - final @Nullable List ignoredOrigins, final @Nullable String origin) { + final @Nullable List ignoredOrigins, final @Nullable String origin) { if (origin == null || ignoredOrigins == null || ignoredOrigins.isEmpty()) { return false; } - for (final String ignoredOrigin : ignoredOrigins) { - if (ignoredOrigin.equalsIgnoreCase(origin)) { + if (ignoredSpanDecisionsCache.containsKey(origin)) { + return ignoredSpanDecisionsCache.get(origin); + } + + for (final FilterString ignoredOrigin : ignoredOrigins) { + if (ignoredOrigin.getFilterString().equalsIgnoreCase(origin)) { + ignoredSpanDecisionsCache.put(origin, true); return true; } + } + for (final FilterString ignoredOrigin : ignoredOrigins) { try { - if (origin.matches(ignoredOrigin)) { + if (ignoredOrigin.matches(origin)) { + ignoredSpanDecisionsCache.put(origin, true); return true; } } catch (Throwable t) { @@ -63,6 +76,7 @@ public static boolean isIgnored( } } + ignoredSpanDecisionsCache.put(origin, false); return false; } } diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index 2f0cc4a98ec..16655be634c 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -2,6 +2,7 @@ import io.sentry.Baggage; import io.sentry.BaggageHeader; +import io.sentry.FilterString; import io.sentry.IScope; import io.sentry.IScopes; import io.sentry.ISpan; @@ -123,7 +124,8 @@ public TracingHeaders( /** Checks if a transaction is to be ignored. */ @ApiStatus.Internal public static boolean isIgnored( - final @Nullable List ignoredTransactions, final @Nullable String transactionName) { + final @Nullable List ignoredTransactions, + final @Nullable String transactionName) { if (transactionName == null) { return false; } @@ -131,13 +133,16 @@ public static boolean isIgnored( return false; } - for (final String ignoredSlug : ignoredTransactions) { - if (ignoredSlug.equalsIgnoreCase(transactionName)) { + for (final FilterString ignoredTransaction : ignoredTransactions) { + if (ignoredTransaction.getFilterString().equalsIgnoreCase(transactionName)) { return true; } + } + + for (final FilterString ignoredTransaction : ignoredTransactions) { try { - if (transactionName.matches(ignoredSlug)) { + if (ignoredTransaction.matches(transactionName)) { return true; } } catch (Throwable t) { diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 91ad296e060..fdbbf61b058 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2097,7 +2097,7 @@ class ScopesTest { @Test fun `creating a transaction with an ignored origin noops`() { val scopes = generateScopes { - it.ignoredSpanOrigins = listOf("ignored.span.origin") + it.setIgnoredSpanOrigins(listOf("ignored.span.origin")) } val transactionContext = TransactionContext("transaction-name", "transaction-op") @@ -2114,7 +2114,7 @@ class ScopesTest { @Test fun `creating a transaction with a non ignored origin creates the transaction`() { val scopes = generateScopes { - it.ignoredSpanOrigins = listOf("ignored.span.origin") + it.setIgnoredSpanOrigins(listOf("ignored.span.origin")) } val transactionContext = TransactionContext("transaction-name", "transaction-op") diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index e1b13a87734..da57f5376e6 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -587,7 +587,7 @@ class SentryClientTest { @Test fun `when captureCheckIn, envelope is sent if ignored slug does not match`() { val sut = fixture.getSut { options -> - options.ignoredCheckIns = listOf("non_matching_slug") + options.setIgnoredCheckIns(listOf("non_matching_slug")) } sut.captureCheckIn(checkIn, null, null) @@ -611,7 +611,7 @@ class SentryClientTest { @Test fun `when captureCheckIn, envelope is not sent if slug is ignored`() { val sut = fixture.getSut { options -> - options.ignoredCheckIns = listOf("some_slug") + options.setIgnoredCheckIns(listOf("some_slug")) } sut.captureCheckIn(checkIn, null, null) @@ -821,7 +821,7 @@ class SentryClientTest { @Test fun `transaction dropped by ignoredTransactions is recorded`() { - fixture.sentryOptions.ignoredTransactions = listOf("a-transaction") + fixture.sentryOptions.setIgnoredTransactions(listOf("a-transaction")) val transaction = SentryTransaction(fixture.sentryTracer) @@ -843,7 +843,7 @@ class SentryClientTest { @Test fun `transaction dropped by ignoredTransactions with regex is recorded`() { - fixture.sentryOptions.ignoredTransactions = listOf("a.*action") + fixture.sentryOptions.setIgnoredTransactions(listOf("a.*action")) val transaction = SentryTransaction(fixture.sentryTracer) diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 3db264c7c6b..46482a10833 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -368,8 +368,8 @@ class SentryOptionsTest { assertFalse(options.isEnabled) assertFalse(options.isEnablePrettySerializationOutput) assertFalse(options.isSendModules) - assertEquals(listOf("slug1", "slug-B"), options.ignoredCheckIns) - assertEquals(listOf("transactionName1", "transaction-name-B"), options.ignoredTransactions) + assertEquals(listOf(FilterString("slug1"), FilterString("slug-B")), options.ignoredCheckIns) + assertEquals(listOf(FilterString("transactionName1"), FilterString("transaction-name-B")), options.ignoredTransactions) assertFalse(options.isEnableBackpressureHandling) assertTrue(options.isForceInit) assertNotNull(options.cron) diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 3d5eca7f485..eb333187b29 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -87,7 +87,7 @@ class SentryTracerTest { val tracer = fixture.getSut({ it.setDebug(true) it.setLogger(SystemOutLogger()) - it.ignoredSpanOrigins = listOf("ignored") + it.setIgnoredSpanOrigins(listOf("ignored")) }) tracer.startChild("child1", null, SpanOptions().also { it.origin = "ignored" }) tracer.startChild("child2") diff --git a/sentry/src/test/java/io/sentry/util/CheckInUtilsTest.kt b/sentry/src/test/java/io/sentry/util/CheckInUtilsTest.kt index 53c5dd7de6e..cc15da93c31 100644 --- a/sentry/src/test/java/io/sentry/util/CheckInUtilsTest.kt +++ b/sentry/src/test/java/io/sentry/util/CheckInUtilsTest.kt @@ -1,6 +1,7 @@ package io.sentry.util import io.sentry.CheckInStatus +import io.sentry.FilterString import io.sentry.IScopes import io.sentry.ISentryLifecycleToken import io.sentry.MonitorConfig @@ -26,12 +27,12 @@ class CheckInUtilsTest { @Test fun `ignores exact match`() { - assertTrue(CheckInUtils.isIgnored(listOf("slugA"), "slugA")) + assertTrue(CheckInUtils.isIgnored(listOf(FilterString("slugA")), "slugA")) } @Test fun `ignores regex match`() { - assertTrue(CheckInUtils.isIgnored(listOf("slug-.*"), "slug-A")) + assertTrue(CheckInUtils.isIgnored(listOf(FilterString("slug-.*")), "slug-A")) } @Test @@ -46,12 +47,12 @@ class CheckInUtilsTest { @Test fun `does not ignore if slug is not in ignored list`() { - assertFalse(CheckInUtils.isIgnored(listOf("slugB"), "slugA")) + assertFalse(CheckInUtils.isIgnored(listOf(FilterString("slugB")), "slugA")) } @Test fun `does not ignore if slug is does not match ignored list`() { - assertFalse(CheckInUtils.isIgnored(listOf("slug-.*"), "slugA")) + assertFalse(CheckInUtils.isIgnored(listOf(FilterString("slug-.*")), "slugA")) } @Test diff --git a/sentry/src/test/java/io/sentry/util/SpanUtilsTest.kt b/sentry/src/test/java/io/sentry/util/SpanUtilsTest.kt new file mode 100644 index 00000000000..90f721432c3 --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/SpanUtilsTest.kt @@ -0,0 +1,81 @@ +package io.sentry.util + +import io.sentry.FilterString +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class SpanUtilsTest { + + @Test + fun `isIgnored returns true for exact match`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring_jakarta.webmvc")) + assertTrue(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns true for exact match with multiple invocations`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring_jakarta.webmvc")) + assertTrue(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring_jakarta.webmvc")) + assertTrue(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns true for regex match`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring.*")) + assertTrue(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns true for regex match with multiple invocations`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring.*")) + assertTrue(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring_jakarta.webmvc")) + assertTrue(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns false for no match`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring_jakarta.webmvc")) + assertFalse(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring.webflux")) + } + + @Test + fun `isIgnored returns false for no match with multiple invocations`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring_jakarta.webmvc")) + assertFalse(SpanUtils.isIgnored(ignoredOrigins, "auto.http.spring.webflux")) + } + + @Test + fun `isIgnored returns false for null origin`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring_jakarta.webmvc")) + assertFalse(SpanUtils.isIgnored(ignoredOrigins, null)) + } + + @Test + fun `isIgnored returns false for null origin with multiple invocations`() { + val ignoredOrigins = listOf(FilterString("auto.http.spring_jakarta.webmvc")) + assertFalse(SpanUtils.isIgnored(ignoredOrigins, null)) + } + + @Test + fun `isIgnored returns false for null ignoredOrigins`() { + assertFalse(SpanUtils.isIgnored(null, "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns false for null ignoredOrigins with multiple invocations`() { + assertFalse(SpanUtils.isIgnored(null, "auto.http.spring_jakarta.webmvc")) + assertFalse(SpanUtils.isIgnored(null, "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns false for empty ignoredOrigins`() { + assertFalse(SpanUtils.isIgnored(emptyList(), "auto.http.spring_jakarta.webmvc")) + } + + @Test + fun `isIgnored returns false for empty ignoredOrigins with multiple invocations`() { + assertFalse(SpanUtils.isIgnored(emptyList(), "auto.http.spring_jakarta.webmvc")) + assertFalse(SpanUtils.isIgnored(emptyList(), "auto.http.spring_jakarta.webmvc")) + } +}