From c2a20cb5e91420a20c6ad00644b98a992cf2738c Mon Sep 17 00:00:00 2001 From: lcian Date: Tue, 21 Jan 2025 10:01:54 +0100 Subject: [PATCH 1/5] Add `options.ignoreExceptions` accepting String and Regex --- sentry/api/sentry.api | 6 +++ .../main/java/io/sentry/ExternalOptions.java | 25 ++++++++-- .../src/main/java/io/sentry/SentryClient.java | 47 ++++++++++++++----- .../main/java/io/sentry/SentryOptions.java | 36 ++++++++++++++ .../java/io/sentry/ExternalOptionsTest.kt | 10 ++++ .../test/java/io/sentry/SentryClientTest.kt | 34 ++++++++++++++ .../test/java/io/sentry/SentryOptionsTest.kt | 2 + 7 files changed, 145 insertions(+), 15 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index a0d559f12f5..bb7efd03ab7 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -436,6 +436,7 @@ public final class io/sentry/ExternalOptions { public fun ()V public fun addBundleId (Ljava/lang/String;)V public fun addContextTag (Ljava/lang/String;)V + public fun addIgnoredException (Ljava/lang/String;)V public fun addIgnoredExceptionForType (Ljava/lang/Class;)V public fun addInAppExclude (Ljava/lang/String;)V public fun addInAppInclude (Ljava/lang/String;)V @@ -452,6 +453,7 @@ public final class io/sentry/ExternalOptions { public fun getEnvironment ()Ljava/lang/String; public fun getIdleTimeout ()Ljava/lang/Long; public fun getIgnoredCheckIns ()Ljava/util/List; + public fun getIgnoredExceptions ()Ljava/util/List; public fun getIgnoredExceptionsForType ()Ljava/util/Set; public fun getIgnoredTransactions ()Ljava/util/List; public fun getInAppExcludes ()Ljava/util/List; @@ -491,6 +493,7 @@ public final class io/sentry/ExternalOptions { public fun setGlobalHubMode (Ljava/lang/Boolean;)V public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V + public fun setIgnoredExceptions (Ljava/util/List;)V public fun setIgnoredTransactions (Ljava/util/List;)V public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V public fun setPrintUncaughtStackTrace (Ljava/lang/Boolean;)V @@ -2814,6 +2817,7 @@ public class io/sentry/SentryOptions { public fun addContextTag (Ljava/lang/String;)V public fun addEventProcessor (Lio/sentry/EventProcessor;)V public fun addIgnoredCheckIn (Ljava/lang/String;)V + public fun addIgnoredException (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 @@ -2855,6 +2859,7 @@ public class io/sentry/SentryOptions { public fun getGestureTargetLocators ()Ljava/util/List; public fun getIdleTimeout ()Ljava/lang/Long; public fun getIgnoredCheckIns ()Ljava/util/List; + public fun getIgnoredExceptions ()Ljava/util/List; public fun getIgnoredExceptionsForType ()Ljava/util/Set; public fun getIgnoredSpanOrigins ()Ljava/util/List; public fun getIgnoredTransactions ()Ljava/util/List; @@ -2987,6 +2992,7 @@ public class io/sentry/SentryOptions { public fun setGlobalHubMode (Ljava/lang/Boolean;)V public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V + public fun setIgnoredExceptions (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 d9b075e1c89..f63ae9a1abc 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -1,10 +1,7 @@ package io.sentry; import io.sentry.config.PropertiesProvider; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; @@ -39,6 +36,7 @@ public final class ExternalOptions { private @Nullable Long idleTimeout; private final @NotNull Set> ignoredExceptionsForType = new CopyOnWriteArraySet<>(); + private @Nullable List ignoredExceptions; private @Nullable Boolean printUncaughtStackTrace; private @Nullable Boolean sendClientReports; private @NotNull Set bundleIds = new CopyOnWriteArraySet<>(); @@ -130,6 +128,10 @@ public final class ExternalOptions { } options.setIdleTimeout(propertiesProvider.getLongProperty("idle-timeout")); + for (final String pattern : propertiesProvider.getList("ignored-exceptions")) { + options.addIgnoredException(pattern); + } + options.setEnabled(propertiesProvider.getBooleanProperty("enabled")); options.setEnablePrettySerializationOutput( @@ -373,6 +375,21 @@ public void setIdleTimeout(final @Nullable Long idleTimeout) { this.idleTimeout = idleTimeout; } + public @Nullable List getIgnoredExceptions() { + return ignoredExceptions; + } + + public void setIgnoredExceptions(final @Nullable List ignoredExceptions) { + this.ignoredExceptions = ignoredExceptions; + } + + public void addIgnoredException(final @NotNull String pattern) { + if (ignoredExceptions == null) { + ignoredExceptions = new ArrayList<>(); + } + ignoredExceptions.add(pattern); + } + public @Nullable Boolean getSendClientReports() { return sendClientReports; } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 1cfbf60313d..8e91dbb7eaa 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -103,17 +103,42 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul if (event != null) { final Throwable eventThrowable = event.getThrowable(); - if (eventThrowable != null && options.containsIgnoredExceptionForType(eventThrowable)) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Event was dropped as the exception %s is ignored", - eventThrowable.getClass()); - options - .getClientReportRecorder() - .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); - return SentryId.EMPTY_ID; + if (eventThrowable != null) { + if (options.containsIgnoredExceptionForType(eventThrowable)) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Event was dropped as the exception %s is ignored", + eventThrowable.getClass()); + options + .getClientReportRecorder() + .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); + return SentryId.EMPTY_ID; + } + + final List ignoredExceptions = options.getIgnoredExceptions(); + if (ignoredExceptions != null) { + final String throwableClassName = eventThrowable.getClass().getCanonicalName(); + if (throwableClassName != null) { + for (final FilterString filter : ignoredExceptions) { + if (filter.getFilterString().equals(throwableClassName) + || filter.matches(throwableClassName)) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Event was dropped as the exception %s matches the ignoreExceptions filter pattern %s", + throwableClassName, + filter.getFilterString()); + options + .getClientReportRecorder() + .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); + return SentryId.EMPTY_ID; + } + } + } + } } } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 3a6d75789a8..5a1f13fe2cd 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -69,6 +69,12 @@ public class SentryOptions { private final @NotNull Set> ignoredExceptionsForType = new CopyOnWriteArraySet<>(); + /** + * Exception names or regex patterns that the captured exception will be tested against. If there + * is a match, the captured exception will not be sent to Sentry as {@link SentryEvent}. + */ + private @Nullable List ignoredExceptions = null; + /** * Code that provides middlewares, bindings or hooks into certain frameworks or environments, * along with code that inserts those bindings and activates them. @@ -1572,6 +1578,32 @@ boolean containsIgnoredExceptionForType(final @NotNull Throwable throwable) { return this.ignoredExceptionsForType.contains(throwable.getClass()); } + public @Nullable List getIgnoredExceptions() { + return ignoredExceptions; + } + + public void setIgnoredExceptions(final @Nullable List ignoredExceptions) { + if (ignoredExceptions == null) { + this.ignoredExceptions = null; + } else { + @NotNull final List patterns = new ArrayList<>(); + for (String pattern : ignoredExceptions) { + if (pattern != null && !pattern.isEmpty()) { + patterns.add(new FilterString(pattern)); + } + } + + this.ignoredExceptions = patterns; + } + } + + public void addIgnoredException(final @NotNull String pattern) { + if (ignoredExceptions == null) { + ignoredExceptions = new ArrayList<>(); + } + ignoredExceptions.add(new FilterString(pattern)); + } + /** * Returns the maximum number of spans that can be attached to single transaction. * @@ -2801,6 +2833,10 @@ public void merge(final @NotNull ExternalOptions options) { final List ignoredTransactions = new ArrayList<>(options.getIgnoredTransactions()); setIgnoredTransactions(ignoredTransactions); } + if (options.getIgnoredExceptions() != null) { + final List ignoredExceptions = new ArrayList<>(options.getIgnoredExceptions()); + setIgnoredExceptions(ignoredExceptions); + } if (options.isEnableBackpressureHandling() != null) { setEnableBackpressureHandling(options.isEnableBackpressureHandling()); } diff --git a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt index b25f67405cf..9b3816bef1a 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -209,6 +209,16 @@ class ExternalOptionsTest { } } + @Test + fun `creates options with ignored exception patterns using external properties`() { + val logger = mock() + withPropertiesFile("ignored-exceptions=java.lang.RuntimeException,io.sentry..*", logger) { options -> + System.out.println(options.ignoredExceptions) + assertTrue(options.ignoredExceptions!!.contains("java.lang.RuntimeException")) + assertTrue(options.ignoredExceptions!!.contains("io.sentry..*")) + } + } + @Test fun `creates options with single bundle ID using external properties`() { withPropertiesFile("bundle-ids=12ea7a02-46ac-44c0-a5bb-6d1fd9586411") { options -> diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index da57f5376e6..2853a2cc38f 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -1758,6 +1758,40 @@ class SentryClientTest { verify(fixture.transport, never()).send(any(), anyOrNull()) } + @Test + fun `when exception matches pattern in ignoredExceptions, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredException(IllegalStateException::class.java.canonicalName) + val sut = fixture.getSut() + sut.captureException(IllegalStateException()) + verify(fixture.transport, never()).send(any(), anyOrNull()) + } + + @Test + fun `when exception does not match pattern in ignoredExceptions, capturing event sends it`() { + fixture.sentryOptions.addIgnoredException(IllegalStateException::class.java.canonicalName) + val sut = fixture.getSut() + class MyException(message: String) : Exception(message) + sut.captureException(MyException("hello")) + verify(fixture.transport).send(any(), anyOrNull()) + } + + @Test + fun `when exception matches regex pattern in ignoredExceptions, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredException("java.lang..*") + val sut = fixture.getSut() + sut.captureException(IllegalStateException()) + verify(fixture.transport, never()).send(any(), anyOrNull()) + } + + @Test + fun `when exception does not match regex pattern in ignoredExceptions, capturing event sends it`() { + fixture.sentryOptions.addIgnoredException("java.lang..*") + val sut = fixture.getSut() + class MyException(message: String) : Exception(message) + sut.captureException(MyException("hello")) + verify(fixture.transport).send(any(), anyOrNull()) + } + @Test fun `screenshot is added to the envelope from the hint`() { val sut = fixture.getSut() diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 46482a10833..8b6ea6ac736 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -326,6 +326,7 @@ class SentryOptionsTest { externalOptions.isSendModules = false externalOptions.ignoredCheckIns = listOf("slug1", "slug-B") externalOptions.ignoredTransactions = listOf("transactionName1", "transaction-name-B") + externalOptions.ignoredExceptions = listOf("com.some.Exception1", "com.some.Exception2") externalOptions.isEnableBackpressureHandling = false externalOptions.maxRequestBodySize = SentryOptions.RequestSize.MEDIUM externalOptions.isSendDefaultPii = true @@ -370,6 +371,7 @@ class SentryOptionsTest { assertFalse(options.isSendModules) assertEquals(listOf(FilterString("slug1"), FilterString("slug-B")), options.ignoredCheckIns) assertEquals(listOf(FilterString("transactionName1"), FilterString("transaction-name-B")), options.ignoredTransactions) + assertEquals(listOf(FilterString("com.some.Exception1"), FilterString("com.some.Exception2")), options.ignoredExceptions) assertFalse(options.isEnableBackpressureHandling) assertTrue(options.isForceInit) assertNotNull(options.cron) From 82b9faf46d4d5aaa323708edcaa23b7d677ccc68 Mon Sep 17 00:00:00 2001 From: lcian Date: Wed, 22 Jan 2025 14:01:41 +0100 Subject: [PATCH 2/5] address comments, add tests, add entry to CHANGELOG.md --- CHANGELOG.md | 11 ++++ .../jakarta/SentryAutoConfigurationTest.kt | 2 + .../boot/SentryAutoConfigurationTest.kt | 2 + sentry/api/sentry.api | 1 + .../main/java/io/sentry/ExternalOptions.java | 4 +- .../src/main/java/io/sentry/SentryClient.java | 58 +++++-------------- .../java/io/sentry/util/ExceptionUtils.java | 42 ++++++++++++++ .../test/java/io/sentry/SentryClientTest.kt | 10 ++++ 8 files changed, 85 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfdfa2879b8..bbe8bb16949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +### Features + +- Add `options.ignoreExceptions` to filter out exceptions that match a certain String or Regex ([#4083](https://github.com/getsentry/sentry-java/pull/4083)) + - Can be set in `sentry.properties`, e.g. `ignored-exceptions=java.lang.RuntimeException,io.sentry..*` + - Can be set in environment variables, e.g. `SENTRY_IGNORED_EXCEPTIONS=java.lang.RuntimeException,io.sentry..*` + - For Spring Boot, it can be set in `application.properties`, e.g. `sentry.ignored-exceptions=java.lang.RuntimeException,io.sentry..*` + +## 8.0.0 + +### Summary + Version 8 of the Sentry Android/Java SDK brings a variety of features and fixes. The most notable changes are: - `Hub` has been replaced by `Scopes` 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 e559502e626..ca10809ddd8 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 @@ -174,6 +174,7 @@ class SentryAutoConfigurationTest { "sentry.enabled=false", "sentry.send-modules=false", "sentry.ignored-checkins=slug1,slugB", + "sentry.ignored-exceptions=com.some.Exception,io.sentry..*", "sentry.ignored-transactions=transactionName1,transactionNameB", "sentry.enable-backpressure-handling=false", "sentry.enable-spotlight=true", @@ -215,6 +216,7 @@ class SentryAutoConfigurationTest { assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) + assertThat(options.ignoredExceptions).containsOnly(FilterString("com.some.Exception"), FilterString("io.sentry..*")) assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) 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 2512fe1f11e..08d9b6350a3 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 @@ -173,6 +173,7 @@ class SentryAutoConfigurationTest { "sentry.enabled=false", "sentry.send-modules=false", "sentry.ignored-checkins=slug1,slugB", + "sentry.ignored-exceptions=com.some.Exception,io.sentry..*", "sentry.ignored-transactions=transactionName1,transactionNameB", "sentry.enable-backpressure-handling=false", "sentry.enable-spotlight=true", @@ -214,6 +215,7 @@ class SentryAutoConfigurationTest { assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) + assertThat(options.ignoredExceptions).containsOnly(FilterString("com.some.Exception"), FilterString("io.sentry..*")) assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index bb7efd03ab7..65d5c1d9837 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -6061,6 +6061,7 @@ public final class io/sentry/util/EventProcessorUtils { public final class io/sentry/util/ExceptionUtils { public fun ()V public static fun findRootCause (Ljava/lang/Throwable;)Ljava/lang/Throwable; + public static fun isIgnored (Ljava/util/Set;Ljava/util/List;Ljava/lang/Throwable;)Z } public final class io/sentry/util/FileUtils { diff --git a/sentry/src/main/java/io/sentry/ExternalOptions.java b/sentry/src/main/java/io/sentry/ExternalOptions.java index f63ae9a1abc..d4def01f2d2 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -128,9 +128,7 @@ public final class ExternalOptions { } options.setIdleTimeout(propertiesProvider.getLongProperty("idle-timeout")); - for (final String pattern : propertiesProvider.getList("ignored-exceptions")) { - options.addIgnoredException(pattern); - } + options.setIgnoredExceptions(propertiesProvider.getList("ignored-exceptions")); options.setEnabled(propertiesProvider.getBooleanProperty("enabled")); diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 8e91dbb7eaa..71d771ceaf7 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -11,12 +11,7 @@ import io.sentry.protocol.SentryTransaction; import io.sentry.transport.ITransport; import io.sentry.transport.RateLimiter; -import io.sentry.util.CheckInUtils; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import io.sentry.util.Random; -import io.sentry.util.SentryRandom; -import io.sentry.util.TracingUtils; +import io.sentry.util.*; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; @@ -103,42 +98,21 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul if (event != null) { final Throwable eventThrowable = event.getThrowable(); - if (eventThrowable != null) { - if (options.containsIgnoredExceptionForType(eventThrowable)) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Event was dropped as the exception %s is ignored", - eventThrowable.getClass()); - options - .getClientReportRecorder() - .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); - return SentryId.EMPTY_ID; - } - - final List ignoredExceptions = options.getIgnoredExceptions(); - if (ignoredExceptions != null) { - final String throwableClassName = eventThrowable.getClass().getCanonicalName(); - if (throwableClassName != null) { - for (final FilterString filter : ignoredExceptions) { - if (filter.getFilterString().equals(throwableClassName) - || filter.matches(throwableClassName)) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Event was dropped as the exception %s matches the ignoreExceptions filter pattern %s", - throwableClassName, - filter.getFilterString()); - options - .getClientReportRecorder() - .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); - return SentryId.EMPTY_ID; - } - } - } - } + if (eventThrowable != null + && ExceptionUtils.isIgnored( + options.getIgnoredExceptionsForType(), + options.getIgnoredExceptions(), + eventThrowable)) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Event was dropped as the exception %s is ignored", + eventThrowable.getClass()); + options + .getClientReportRecorder() + .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); + return SentryId.EMPTY_ID; } } diff --git a/sentry/src/main/java/io/sentry/util/ExceptionUtils.java b/sentry/src/main/java/io/sentry/util/ExceptionUtils.java index 04285751c10..42b8bf13880 100644 --- a/sentry/src/main/java/io/sentry/util/ExceptionUtils.java +++ b/sentry/src/main/java/io/sentry/util/ExceptionUtils.java @@ -1,7 +1,11 @@ package io.sentry.util; +import io.sentry.FilterString; +import java.util.List; +import java.util.Set; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public final class ExceptionUtils { @@ -20,4 +24,42 @@ public final class ExceptionUtils { } return rootCause; } + + /** Checks if an exception has been ignored. */ + @ApiStatus.Internal + public static @NotNull boolean isIgnored( + final @NotNull Set> ignoredExceptionsForType, + final @Nullable List ignoredExceptions, + final @NotNull Throwable throwable) { + if (throwable == null) { + return false; + } + + final Class throwableClass = throwable.getClass(); + if (ignoredExceptionsForType.contains(throwableClass)) { + return true; + } + + if (ignoredExceptions == null || ignoredExceptions.isEmpty()) { + return false; + } + final String throwableClassName = throwableClass.getCanonicalName(); + if (throwableClassName == null) { + return false; + } + + for (final FilterString filter : ignoredExceptions) { + if (filter.getFilterString().equals(throwableClassName)) { + return true; + } + } + + for (final FilterString filter : ignoredExceptions) { + if (filter.matches(throwableClassName)) { + return true; + } + } + + return false; + } } diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index 2853a2cc38f..95b1ba11780 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -32,6 +32,7 @@ import org.junit.rules.TemporaryFolder import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeast import org.mockito.kotlin.check import org.mockito.kotlin.doAnswer import org.mockito.kotlin.eq @@ -1792,6 +1793,15 @@ class SentryClientTest { verify(fixture.transport).send(any(), anyOrNull()) } + @Test + fun `when ignoredExceptionsForType and ignoredExceptions are not explicitly specified, capturing event sends exceptions`() { + val sut = fixture.getSut() + sut.captureException(IllegalStateException()) + class MyException(message: String) : Exception(message) + sut.captureException(MyException("hello")) + verify(fixture.transport, atLeast(2)).send(any(), anyOrNull()) + } + @Test fun `screenshot is added to the envelope from the hint`() { val sut = fixture.getSut() From 14518c3ecab3f115b3423c765e56dba144e77ed0 Mon Sep 17 00:00:00 2001 From: Lorenzo Cian Date: Wed, 22 Jan 2025 14:29:45 +0100 Subject: [PATCH 3/5] Update CHANGELOG.md --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea8d585043..505cde6b981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,6 @@ # Changelog -## 8.0.0 - -### Summary +## Unreleased ### Features From 6e78fd082ac5ba90307cdbd61e8948e7d3c4f744 Mon Sep 17 00:00:00 2001 From: lcian Date: Thu, 23 Jan 2025 17:04:33 +0100 Subject: [PATCH 4/5] implement ignoredErrors instead of ignoredExceptions, matching on several possible messages as in the JS SDK and not on only on the Exception class --- CHANGELOG.md | 8 +-- .../jakarta/SentryAutoConfigurationTest.kt | 4 +- .../boot/SentryAutoConfigurationTest.kt | 4 +- sentry/api/sentry.api | 18 +++-- .../main/java/io/sentry/ExternalOptions.java | 19 ++--- .../src/main/java/io/sentry/SentryClient.java | 18 +++-- .../main/java/io/sentry/SentryOptions.java | 34 ++++----- .../main/java/io/sentry/util/ErrorUtils.java | 69 +++++++++++++++++++ .../java/io/sentry/util/ExceptionUtils.java | 37 +--------- .../java/io/sentry/ExternalOptionsTest.kt | 9 ++- .../test/java/io/sentry/SentryClientTest.kt | 58 ++++++++++------ .../test/java/io/sentry/SentryOptionsTest.kt | 4 +- 12 files changed, 170 insertions(+), 112 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/ErrorUtils.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 505cde6b981..6658b1834e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ ### Features -- Add `options.ignoreExceptions` to filter out exceptions that match a certain String or Regex ([#4083](https://github.com/getsentry/sentry-java/pull/4083)) - - Can be set in `sentry.properties`, e.g. `ignored-exceptions=java.lang.RuntimeException,io.sentry..*` - - Can be set in environment variables, e.g. `SENTRY_IGNORED_EXCEPTIONS=java.lang.RuntimeException,io.sentry..*` - - For Spring Boot, it can be set in `application.properties`, e.g. `sentry.ignored-exceptions=java.lang.RuntimeException,io.sentry..*` +- Add `options.ignoredErrors` to filter out errors that match a certain String or Regex ([#4083](https://github.com/getsentry/sentry-java/pull/4083)) + - Can be set in `sentry.properties`, e.g. `ignored-errors=Some error,Another .*` + - Can be set in environment variables, e.g. `SENTRY_IGNORED_ERRORS=Some error,Another .*` + - For Spring Boot, it can be set in `application.properties`, e.g. `sentry.ignored-errors=Some error,Another .*` ## 8.0.0 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 ca10809ddd8..3498c903194 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 @@ -174,7 +174,7 @@ class SentryAutoConfigurationTest { "sentry.enabled=false", "sentry.send-modules=false", "sentry.ignored-checkins=slug1,slugB", - "sentry.ignored-exceptions=com.some.Exception,io.sentry..*", + "sentry.ignored-errors=Some error,Another .*", "sentry.ignored-transactions=transactionName1,transactionNameB", "sentry.enable-backpressure-handling=false", "sentry.enable-spotlight=true", @@ -216,7 +216,7 @@ class SentryAutoConfigurationTest { assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) - assertThat(options.ignoredExceptions).containsOnly(FilterString("com.some.Exception"), FilterString("io.sentry..*")) + assertThat(options.ignoredErrors).containsOnly(FilterString("Some error"), FilterString("Another .*")) assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) 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 08d9b6350a3..cd105dab445 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 @@ -173,7 +173,7 @@ class SentryAutoConfigurationTest { "sentry.enabled=false", "sentry.send-modules=false", "sentry.ignored-checkins=slug1,slugB", - "sentry.ignored-exceptions=com.some.Exception,io.sentry..*", + "sentry.ignored-errors=Some error,Another .*", "sentry.ignored-transactions=transactionName1,transactionNameB", "sentry.enable-backpressure-handling=false", "sentry.enable-spotlight=true", @@ -215,7 +215,7 @@ class SentryAutoConfigurationTest { assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) - assertThat(options.ignoredExceptions).containsOnly(FilterString("com.some.Exception"), FilterString("io.sentry..*")) + assertThat(options.ignoredErrors).containsOnly(FilterString("Some error"), FilterString("Another .*")) assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 65d5c1d9837..642b67ec06f 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -436,7 +436,6 @@ public final class io/sentry/ExternalOptions { public fun ()V public fun addBundleId (Ljava/lang/String;)V public fun addContextTag (Ljava/lang/String;)V - public fun addIgnoredException (Ljava/lang/String;)V public fun addIgnoredExceptionForType (Ljava/lang/Class;)V public fun addInAppExclude (Ljava/lang/String;)V public fun addInAppInclude (Ljava/lang/String;)V @@ -453,7 +452,7 @@ public final class io/sentry/ExternalOptions { public fun getEnvironment ()Ljava/lang/String; public fun getIdleTimeout ()Ljava/lang/Long; public fun getIgnoredCheckIns ()Ljava/util/List; - public fun getIgnoredExceptions ()Ljava/util/List; + public fun getIgnoredErrors ()Ljava/util/List; public fun getIgnoredExceptionsForType ()Ljava/util/Set; public fun getIgnoredTransactions ()Ljava/util/List; public fun getInAppExcludes ()Ljava/util/List; @@ -493,7 +492,7 @@ public final class io/sentry/ExternalOptions { public fun setGlobalHubMode (Ljava/lang/Boolean;)V public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V - public fun setIgnoredExceptions (Ljava/util/List;)V + public fun setIgnoredErrors (Ljava/util/List;)V public fun setIgnoredTransactions (Ljava/util/List;)V public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V public fun setPrintUncaughtStackTrace (Ljava/lang/Boolean;)V @@ -2817,7 +2816,7 @@ public class io/sentry/SentryOptions { public fun addContextTag (Ljava/lang/String;)V public fun addEventProcessor (Lio/sentry/EventProcessor;)V public fun addIgnoredCheckIn (Ljava/lang/String;)V - public fun addIgnoredException (Ljava/lang/String;)V + public fun addIgnoredError (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 @@ -2859,7 +2858,7 @@ public class io/sentry/SentryOptions { public fun getGestureTargetLocators ()Ljava/util/List; public fun getIdleTimeout ()Ljava/lang/Long; public fun getIgnoredCheckIns ()Ljava/util/List; - public fun getIgnoredExceptions ()Ljava/util/List; + public fun getIgnoredErrors ()Ljava/util/List; public fun getIgnoredExceptionsForType ()Ljava/util/Set; public fun getIgnoredSpanOrigins ()Ljava/util/List; public fun getIgnoredTransactions ()Ljava/util/List; @@ -2992,7 +2991,7 @@ public class io/sentry/SentryOptions { public fun setGlobalHubMode (Ljava/lang/Boolean;)V public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V - public fun setIgnoredExceptions (Ljava/util/List;)V + public fun setIgnoredErrors (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 @@ -6053,6 +6052,11 @@ public final class io/sentry/util/DebugMetaPropertiesApplier { public static fun getProguardUuid (Ljava/util/Properties;)Ljava/lang/String; } +public final class io/sentry/util/ErrorUtils { + public fun ()V + public static fun isIgnored (Ljava/util/List;Lio/sentry/SentryEvent;)Z +} + public final class io/sentry/util/EventProcessorUtils { public fun ()V public static fun unwrap (Ljava/util/List;)Ljava/util/List; @@ -6061,7 +6065,7 @@ public final class io/sentry/util/EventProcessorUtils { public final class io/sentry/util/ExceptionUtils { public fun ()V public static fun findRootCause (Ljava/lang/Throwable;)Ljava/lang/Throwable; - public static fun isIgnored (Ljava/util/Set;Ljava/util/List;Ljava/lang/Throwable;)Z + public static fun isIgnored (Ljava/util/Set;Ljava/lang/Throwable;)Z } public final class io/sentry/util/FileUtils { diff --git a/sentry/src/main/java/io/sentry/ExternalOptions.java b/sentry/src/main/java/io/sentry/ExternalOptions.java index d4def01f2d2..ed2b4e1103a 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -36,7 +36,7 @@ public final class ExternalOptions { private @Nullable Long idleTimeout; private final @NotNull Set> ignoredExceptionsForType = new CopyOnWriteArraySet<>(); - private @Nullable List ignoredExceptions; + private @Nullable List ignoredErrors; private @Nullable Boolean printUncaughtStackTrace; private @Nullable Boolean sendClientReports; private @NotNull Set bundleIds = new CopyOnWriteArraySet<>(); @@ -128,7 +128,7 @@ public final class ExternalOptions { } options.setIdleTimeout(propertiesProvider.getLongProperty("idle-timeout")); - options.setIgnoredExceptions(propertiesProvider.getList("ignored-exceptions")); + options.setIgnoredErrors(propertiesProvider.getList("ignored-errors")); options.setEnabled(propertiesProvider.getBooleanProperty("enabled")); @@ -373,19 +373,12 @@ public void setIdleTimeout(final @Nullable Long idleTimeout) { this.idleTimeout = idleTimeout; } - public @Nullable List getIgnoredExceptions() { - return ignoredExceptions; + public @Nullable List getIgnoredErrors() { + return ignoredErrors; } - public void setIgnoredExceptions(final @Nullable List ignoredExceptions) { - this.ignoredExceptions = ignoredExceptions; - } - - public void addIgnoredException(final @NotNull String pattern) { - if (ignoredExceptions == null) { - ignoredExceptions = new ArrayList<>(); - } - ignoredExceptions.add(pattern); + public void setIgnoredErrors(final @Nullable List ignoredErrors) { + this.ignoredErrors = ignoredErrors; } public @Nullable Boolean getSendClientReports() { diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 71d771ceaf7..74f057d2306 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -99,10 +99,7 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul if (event != null) { final Throwable eventThrowable = event.getThrowable(); if (eventThrowable != null - && ExceptionUtils.isIgnored( - options.getIgnoredExceptionsForType(), - options.getIgnoredExceptions(), - eventThrowable)) { + && ExceptionUtils.isIgnored(options.getIgnoredExceptionsForType(), eventThrowable)) { options .getLogger() .log( @@ -114,6 +111,19 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); return SentryId.EMPTY_ID; } + + if (ErrorUtils.isIgnored(options.getIgnoredErrors(), event)) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Event was dropped as the error %s is ignored", + event.getMessage()); + options + .getClientReportRecorder() + .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error); + return SentryId.EMPTY_ID; + } } if (shouldApplyScopeData(event, hint)) { diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 5a1f13fe2cd..91c159e6f65 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -70,10 +70,10 @@ public class SentryOptions { new CopyOnWriteArraySet<>(); /** - * Exception names or regex patterns that the captured exception will be tested against. If there - * is a match, the captured exception will not be sent to Sentry as {@link SentryEvent}. + * Strings or regex patterns that possible error messages for an event will be tested against. If + * there is a match, the captured event will not be sent to Sentry. */ - private @Nullable List ignoredExceptions = null; + private @Nullable List ignoredErrors = null; /** * Code that provides middlewares, bindings or hooks into certain frameworks or environments, @@ -1578,30 +1578,30 @@ boolean containsIgnoredExceptionForType(final @NotNull Throwable throwable) { return this.ignoredExceptionsForType.contains(throwable.getClass()); } - public @Nullable List getIgnoredExceptions() { - return ignoredExceptions; + public @Nullable List getIgnoredErrors() { + return ignoredErrors; } - public void setIgnoredExceptions(final @Nullable List ignoredExceptions) { - if (ignoredExceptions == null) { - this.ignoredExceptions = null; + public void setIgnoredErrors(final @Nullable List ignoredErrors) { + if (ignoredErrors == null) { + this.ignoredErrors = null; } else { @NotNull final List patterns = new ArrayList<>(); - for (String pattern : ignoredExceptions) { + for (String pattern : ignoredErrors) { if (pattern != null && !pattern.isEmpty()) { patterns.add(new FilterString(pattern)); } } - this.ignoredExceptions = patterns; + this.ignoredErrors = patterns; } } - public void addIgnoredException(final @NotNull String pattern) { - if (ignoredExceptions == null) { - ignoredExceptions = new ArrayList<>(); + public void addIgnoredError(final @NotNull String pattern) { + if (ignoredErrors == null) { + ignoredErrors = new ArrayList<>(); } - ignoredExceptions.add(new FilterString(pattern)); + ignoredErrors.add(new FilterString(pattern)); } /** @@ -2833,9 +2833,9 @@ public void merge(final @NotNull ExternalOptions options) { final List ignoredTransactions = new ArrayList<>(options.getIgnoredTransactions()); setIgnoredTransactions(ignoredTransactions); } - if (options.getIgnoredExceptions() != null) { - final List ignoredExceptions = new ArrayList<>(options.getIgnoredExceptions()); - setIgnoredExceptions(ignoredExceptions); + if (options.getIgnoredErrors() != null) { + final List ignoredExceptions = new ArrayList<>(options.getIgnoredErrors()); + setIgnoredErrors(ignoredExceptions); } if (options.isEnableBackpressureHandling() != null) { setEnableBackpressureHandling(options.isEnableBackpressureHandling()); diff --git a/sentry/src/main/java/io/sentry/util/ErrorUtils.java b/sentry/src/main/java/io/sentry/util/ErrorUtils.java new file mode 100644 index 00000000000..c485cbbd76a --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/ErrorUtils.java @@ -0,0 +1,69 @@ +package io.sentry.util; + +import io.sentry.FilterString; +import io.sentry.SentryEvent; +import io.sentry.protocol.Message; +import io.sentry.protocol.SentryException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class ErrorUtils { + + /** Checks if an error has been ignored. */ + @ApiStatus.Internal + public static boolean isIgnored( + final @Nullable List ignoredErrors, final @NotNull SentryEvent event) { + if (event == null || ignoredErrors == null || ignoredErrors.isEmpty()) { + return false; + } + + final @NotNull Set possibleMessages = new HashSet<>(); + + final @Nullable Message eventMessage = event.getMessage(); + if (eventMessage != null) { + final @Nullable String stringMessage = eventMessage.getMessage(); + if (stringMessage != null) { + possibleMessages.add(stringMessage); + } + final @Nullable String formattedMessage = eventMessage.getFormatted(); + if (formattedMessage != null) { + possibleMessages.add(formattedMessage); + } + } + final @Nullable List exceptions = event.getExceptions(); + if (exceptions != null && !exceptions.isEmpty()) { + for (final @Nullable SentryException exception : exceptions) { + if (exception != null) { + final @Nullable String value = exception.getValue(); + if (value != null) { + possibleMessages.add(value); + } + } + } + } + final @Nullable Throwable throwable = event.getThrowable(); + if (throwable != null) { + possibleMessages.add(throwable.toString()); + } + + for (final @NotNull FilterString filter : ignoredErrors) { + if (possibleMessages.contains(filter.getFilterString())) { + return true; + } + } + + for (final @NotNull FilterString filter : ignoredErrors) { + for (final @NotNull String message : possibleMessages) { + if (filter.matches(message)) { + return true; + } + } + } + + return false; + } +} diff --git a/sentry/src/main/java/io/sentry/util/ExceptionUtils.java b/sentry/src/main/java/io/sentry/util/ExceptionUtils.java index 42b8bf13880..9d6033a96c3 100644 --- a/sentry/src/main/java/io/sentry/util/ExceptionUtils.java +++ b/sentry/src/main/java/io/sentry/util/ExceptionUtils.java @@ -1,11 +1,8 @@ package io.sentry.util; -import io.sentry.FilterString; -import java.util.List; import java.util.Set; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public final class ExceptionUtils { @@ -27,39 +24,9 @@ public final class ExceptionUtils { /** Checks if an exception has been ignored. */ @ApiStatus.Internal - public static @NotNull boolean isIgnored( + public static boolean isIgnored( final @NotNull Set> ignoredExceptionsForType, - final @Nullable List ignoredExceptions, final @NotNull Throwable throwable) { - if (throwable == null) { - return false; - } - - final Class throwableClass = throwable.getClass(); - if (ignoredExceptionsForType.contains(throwableClass)) { - return true; - } - - if (ignoredExceptions == null || ignoredExceptions.isEmpty()) { - return false; - } - final String throwableClassName = throwableClass.getCanonicalName(); - if (throwableClassName == null) { - return false; - } - - for (final FilterString filter : ignoredExceptions) { - if (filter.getFilterString().equals(throwableClassName)) { - return true; - } - } - - for (final FilterString filter : ignoredExceptions) { - if (filter.matches(throwableClassName)) { - return true; - } - } - - return false; + return ignoredExceptionsForType.contains(throwable.getClass()); } } diff --git a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt index 9b3816bef1a..f32b6cf8c01 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -210,12 +210,11 @@ class ExternalOptionsTest { } @Test - fun `creates options with ignored exception patterns using external properties`() { + fun `creates options with ignored error patterns using external properties`() { val logger = mock() - withPropertiesFile("ignored-exceptions=java.lang.RuntimeException,io.sentry..*", logger) { options -> - System.out.println(options.ignoredExceptions) - assertTrue(options.ignoredExceptions!!.contains("java.lang.RuntimeException")) - assertTrue(options.ignoredExceptions!!.contains("io.sentry..*")) + withPropertiesFile("ignored-errors=Some error,Another .*", logger) { options -> + assertTrue(options.ignoredErrors!!.contains("Some error")) + assertTrue(options.ignoredErrors!!.contains("Another .*")) } } diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index 95b1ba11780..63052022ebc 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -15,6 +15,7 @@ import io.sentry.hints.DiskFlushNotification import io.sentry.hints.TransactionEnd import io.sentry.protocol.Contexts import io.sentry.protocol.Mechanism +import io.sentry.protocol.Message import io.sentry.protocol.Request import io.sentry.protocol.SdkVersion import io.sentry.protocol.SentryException @@ -32,7 +33,6 @@ import org.junit.rules.TemporaryFolder import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.atLeast import org.mockito.kotlin.check import org.mockito.kotlin.doAnswer import org.mockito.kotlin.eq @@ -1760,46 +1760,62 @@ class SentryClientTest { } @Test - fun `when exception matches pattern in ignoredExceptions, capturing event does not send it`() { - fixture.sentryOptions.addIgnoredException(IllegalStateException::class.java.canonicalName) + fun `when event message matches string in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError("hello") val sut = fixture.getSut() - sut.captureException(IllegalStateException()) + val event = SentryEvent() + val message = Message() + message.message = "hello" + event.setMessage(message) + sut.captureEvent(event) verify(fixture.transport, never()).send(any(), anyOrNull()) } @Test - fun `when exception does not match pattern in ignoredExceptions, capturing event sends it`() { - fixture.sentryOptions.addIgnoredException(IllegalStateException::class.java.canonicalName) + fun `when event message matches regex pattern in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError("hello .*") val sut = fixture.getSut() - class MyException(message: String) : Exception(message) - sut.captureException(MyException("hello")) + val event = SentryEvent() + val message = Message() + message.message = "hello world" + event.setMessage(message) + sut.captureEvent(event) + verify(fixture.transport, never()).send(any(), anyOrNull()) + } + + @Test + fun `when event message does not match regex pattern in ignoredErrors, capturing event sends it`() { + fixture.sentryOptions.addIgnoredError("hello .*") + val sut = fixture.getSut() + val event = SentryEvent() + val message = Message() + message.message = "test" + event.setMessage(message) + sut.captureEvent(event) verify(fixture.transport).send(any(), anyOrNull()) } @Test - fun `when exception matches regex pattern in ignoredExceptions, capturing event does not send it`() { - fixture.sentryOptions.addIgnoredException("java.lang..*") + fun `when exception message matches regex pattern in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError(".*hello .*") val sut = fixture.getSut() - sut.captureException(IllegalStateException()) + sut.captureException(RuntimeException("hello world")) verify(fixture.transport, never()).send(any(), anyOrNull()) } @Test - fun `when exception does not match regex pattern in ignoredExceptions, capturing event sends it`() { - fixture.sentryOptions.addIgnoredException("java.lang..*") + fun `when class matches regex pattern in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError("java\\.lang\\..*") val sut = fixture.getSut() - class MyException(message: String) : Exception(message) - sut.captureException(MyException("hello")) - verify(fixture.transport).send(any(), anyOrNull()) + sut.captureException(RuntimeException("hello world")) + verify(fixture.transport, never()).send(any(), anyOrNull()) } @Test - fun `when ignoredExceptionsForType and ignoredExceptions are not explicitly specified, capturing event sends exceptions`() { + fun `when ignoredExceptionsForType and ignoredErrors are not explicitly specified, capturing event sends event`() { val sut = fixture.getSut() - sut.captureException(IllegalStateException()) - class MyException(message: String) : Exception(message) - sut.captureException(MyException("hello")) - verify(fixture.transport, atLeast(2)).send(any(), anyOrNull()) + sut.captureException(RuntimeException("test")) + verify(fixture.transport).send(any(), anyOrNull()) } @Test diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 8b6ea6ac736..278c3519162 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -326,7 +326,7 @@ class SentryOptionsTest { externalOptions.isSendModules = false externalOptions.ignoredCheckIns = listOf("slug1", "slug-B") externalOptions.ignoredTransactions = listOf("transactionName1", "transaction-name-B") - externalOptions.ignoredExceptions = listOf("com.some.Exception1", "com.some.Exception2") + externalOptions.ignoredErrors = listOf("Some error", "Another .*") externalOptions.isEnableBackpressureHandling = false externalOptions.maxRequestBodySize = SentryOptions.RequestSize.MEDIUM externalOptions.isSendDefaultPii = true @@ -371,7 +371,7 @@ class SentryOptionsTest { assertFalse(options.isSendModules) assertEquals(listOf(FilterString("slug1"), FilterString("slug-B")), options.ignoredCheckIns) assertEquals(listOf(FilterString("transactionName1"), FilterString("transaction-name-B")), options.ignoredTransactions) - assertEquals(listOf(FilterString("com.some.Exception1"), FilterString("com.some.Exception2")), options.ignoredExceptions) + assertEquals(listOf(FilterString("Some error"), FilterString("Another .*")), options.ignoredErrors) assertFalse(options.isEnableBackpressureHandling) assertTrue(options.isForceInit) assertNotNull(options.cron) From 8c7a7e507ba4a08325c0bf6fd0cf4f880163fd8d Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 24 Jan 2025 10:46:51 +0100 Subject: [PATCH 5/5] add JavaDoc, update CHANGELOG.md, don't consider `event.exceptions` --- CHANGELOG.md | 1 + .../src/main/java/io/sentry/SentryClient.java | 2 +- .../main/java/io/sentry/SentryOptions.java | 23 +++++++++++++++++++ .../main/java/io/sentry/util/ErrorUtils.java | 12 ---------- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f12eda145..7e8a44bbbde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Add `options.ignoredErrors` to filter out errors that match a certain String or Regex ([#4083](https://github.com/getsentry/sentry-java/pull/4083)) + - The matching is attempted on `event.message`, `event.formatted`, and `{event.throwable.class.name}: {event.throwable.message}` - Can be set in `sentry.properties`, e.g. `ignored-errors=Some error,Another .*` - Can be set in environment variables, e.g. `SENTRY_IGNORED_ERRORS=Some error,Another .*` - For Spring Boot, it can be set in `application.properties`, e.g. `sentry.ignored-errors=Some error,Another .*` diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 74f057d2306..277be87aeef 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -117,7 +117,7 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul .getLogger() .log( SentryLevel.DEBUG, - "Event was dropped as the error %s is ignored", + "Event was dropped as it matched a string/pattern in ignoredErrors", event.getMessage()); options .getClientReportRecorder() diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 91c159e6f65..2617cd3dfa5 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1578,10 +1578,26 @@ boolean containsIgnoredExceptionForType(final @NotNull Throwable throwable) { return this.ignoredExceptionsForType.contains(throwable.getClass()); } + /** + * Returns the list of strings/regex patterns that `event.message`, `event.formatted`, and + * `{event.throwable.class.name}: {event.throwable.message}` are checked against to determine if + * an event shall be sent to Sentry or ignored. + * + * @return the list of strings/regex patterns that `event.message`, `event.formatted`, and + * `{event.throwable.class.name}: {event.throwable.message}` are checked against to determine + * if an event shall be sent to Sentry or ignored + */ public @Nullable List getIgnoredErrors() { return ignoredErrors; } + /** + * Sets the list of strings/regex patterns that `event.message`, `event.formatted`, and + * `{event.throwable.class.name}: {event.throwable.message}` are checked against to determine if + * an event shall be sent to Sentry or ignored. + * + * @param ignoredErrors the list of strings/regex patterns + */ public void setIgnoredErrors(final @Nullable List ignoredErrors) { if (ignoredErrors == null) { this.ignoredErrors = null; @@ -1597,6 +1613,13 @@ public void setIgnoredErrors(final @Nullable List ignoredErrors) { } } + /** + * Adds an item to the list of strings/regex patterns that `event.message`, `event.formatted`, and + * `{event.throwable.class.name}: {event.throwable.message}` are checked against to determine if + * an event shall be sent to Sentry or ignored. + * + * @param pattern the string/regex pattern + */ public void addIgnoredError(final @NotNull String pattern) { if (ignoredErrors == null) { ignoredErrors = new ArrayList<>(); diff --git a/sentry/src/main/java/io/sentry/util/ErrorUtils.java b/sentry/src/main/java/io/sentry/util/ErrorUtils.java index c485cbbd76a..cb8b3dbce93 100644 --- a/sentry/src/main/java/io/sentry/util/ErrorUtils.java +++ b/sentry/src/main/java/io/sentry/util/ErrorUtils.java @@ -3,7 +3,6 @@ import io.sentry.FilterString; import io.sentry.SentryEvent; import io.sentry.protocol.Message; -import io.sentry.protocol.SentryException; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -34,17 +33,6 @@ public static boolean isIgnored( possibleMessages.add(formattedMessage); } } - final @Nullable List exceptions = event.getExceptions(); - if (exceptions != null && !exceptions.isEmpty()) { - for (final @Nullable SentryException exception : exceptions) { - if (exception != null) { - final @Nullable String value = exception.getValue(); - if (value != null) { - possibleMessages.add(value); - } - } - } - } final @Nullable Throwable throwable = event.getThrowable(); if (throwable != null) { possibleMessages.add(throwable.toString());