Skip to content

Commit

Permalink
Add options.ignoredErrors accepting String and Regex (#4083)
Browse files Browse the repository at this point in the history
* Add `options.ignoreExceptions` accepting String and Regex

* address comments, add tests, add entry to CHANGELOG.md

* Update CHANGELOG.md

* implement ignoredErrors instead of ignoredExceptions, matching on several possible messages as in the JS SDK and not on only on the Exception class

* add JavaDoc, update CHANGELOG.md, don't consider `event.exceptions`
  • Loading branch information
lcian authored Jan 24, 2025
1 parent a136d5b commit 5c403ff
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 11 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

### 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 .*`

### Fixes

- Avoid logging an error when a float is passed in the manifest ([#4031](https://github.com/getsentry/sentry-java/pull/4031))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class SentryAutoConfigurationTest {
"sentry.enabled=false",
"sentry.send-modules=false",
"sentry.ignored-checkins=slug1,slugB",
"sentry.ignored-errors=Some error,Another .*",
"sentry.ignored-transactions=transactionName1,transactionNameB",
"sentry.enable-backpressure-handling=false",
"sentry.enable-spotlight=true",
Expand Down Expand Up @@ -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.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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class SentryAutoConfigurationTest {
"sentry.enabled=false",
"sentry.send-modules=false",
"sentry.ignored-checkins=slug1,slugB",
"sentry.ignored-errors=Some error,Another .*",
"sentry.ignored-transactions=transactionName1,transactionNameB",
"sentry.enable-backpressure-handling=false",
"sentry.enable-spotlight=true",
Expand Down Expand Up @@ -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.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)
Expand Down
11 changes: 11 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +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 getIgnoredErrors ()Ljava/util/List;
public fun getIgnoredExceptionsForType ()Ljava/util/Set;
public fun getIgnoredTransactions ()Ljava/util/List;
public fun getInAppExcludes ()Ljava/util/List;
Expand Down Expand Up @@ -491,6 +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 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
Expand Down Expand Up @@ -2814,6 +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 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
Expand Down Expand Up @@ -2855,6 +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 getIgnoredErrors ()Ljava/util/List;
public fun getIgnoredExceptionsForType ()Ljava/util/Set;
public fun getIgnoredSpanOrigins ()Ljava/util/List;
public fun getIgnoredTransactions ()Ljava/util/List;
Expand Down Expand Up @@ -2987,6 +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 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
Expand Down Expand Up @@ -6047,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 <init> ()V
public static fun isIgnored (Ljava/util/List;Lio/sentry/SentryEvent;)Z
}

public final class io/sentry/util/EventProcessorUtils {
public fun <init> ()V
public static fun unwrap (Ljava/util/List;)Ljava/util/List;
Expand All @@ -6055,6 +6065,7 @@ public final class io/sentry/util/EventProcessorUtils {
public final class io/sentry/util/ExceptionUtils {
public fun <init> ()V
public static fun findRootCause (Ljava/lang/Throwable;)Ljava/lang/Throwable;
public static fun isIgnored (Ljava/util/Set;Ljava/lang/Throwable;)Z
}

public final class io/sentry/util/FileUtils {
Expand Down
16 changes: 12 additions & 4 deletions sentry/src/main/java/io/sentry/ExternalOptions.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,6 +36,7 @@ public final class ExternalOptions {
private @Nullable Long idleTimeout;
private final @NotNull Set<Class<? extends Throwable>> ignoredExceptionsForType =
new CopyOnWriteArraySet<>();
private @Nullable List<String> ignoredErrors;
private @Nullable Boolean printUncaughtStackTrace;
private @Nullable Boolean sendClientReports;
private @NotNull Set<String> bundleIds = new CopyOnWriteArraySet<>();
Expand Down Expand Up @@ -130,6 +128,8 @@ public final class ExternalOptions {
}
options.setIdleTimeout(propertiesProvider.getLongProperty("idle-timeout"));

options.setIgnoredErrors(propertiesProvider.getList("ignored-errors"));

options.setEnabled(propertiesProvider.getBooleanProperty("enabled"));

options.setEnablePrettySerializationOutput(
Expand Down Expand Up @@ -373,6 +373,14 @@ public void setIdleTimeout(final @Nullable Long idleTimeout) {
this.idleTimeout = idleTimeout;
}

public @Nullable List<String> getIgnoredErrors() {
return ignoredErrors;
}

public void setIgnoredErrors(final @Nullable List<String> ignoredErrors) {
this.ignoredErrors = ignoredErrors;
}

public @Nullable Boolean getSendClientReports() {
return sendClientReports;
}
Expand Down
23 changes: 16 additions & 7 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -103,7 +98,8 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul

if (event != null) {
final Throwable eventThrowable = event.getThrowable();
if (eventThrowable != null && options.containsIgnoredExceptionForType(eventThrowable)) {
if (eventThrowable != null
&& ExceptionUtils.isIgnored(options.getIgnoredExceptionsForType(), eventThrowable)) {
options
.getLogger()
.log(
Expand All @@ -115,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 it matched a string/pattern in ignoredErrors",
event.getMessage());
options
.getClientReportRecorder()
.recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error);
return SentryId.EMPTY_ID;
}
}

if (shouldApplyScopeData(event, hint)) {
Expand Down
59 changes: 59 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public class SentryOptions {
private final @NotNull Set<Class<? extends Throwable>> ignoredExceptionsForType =
new CopyOnWriteArraySet<>();

/**
* 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<FilterString> ignoredErrors = null;

/**
* Code that provides middlewares, bindings or hooks into certain frameworks or environments,
* along with code that inserts those bindings and activates them.
Expand Down Expand Up @@ -1572,6 +1578,55 @@ 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<FilterString> 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<String> ignoredErrors) {
if (ignoredErrors == null) {
this.ignoredErrors = null;
} else {
@NotNull final List<FilterString> patterns = new ArrayList<>();
for (String pattern : ignoredErrors) {
if (pattern != null && !pattern.isEmpty()) {
patterns.add(new FilterString(pattern));
}
}

this.ignoredErrors = patterns;
}
}

/**
* 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<>();
}
ignoredErrors.add(new FilterString(pattern));
}

/**
* Returns the maximum number of spans that can be attached to single transaction.
*
Expand Down Expand Up @@ -2801,6 +2856,10 @@ public void merge(final @NotNull ExternalOptions options) {
final List<String> ignoredTransactions = new ArrayList<>(options.getIgnoredTransactions());
setIgnoredTransactions(ignoredTransactions);
}
if (options.getIgnoredErrors() != null) {
final List<String> ignoredExceptions = new ArrayList<>(options.getIgnoredErrors());
setIgnoredErrors(ignoredExceptions);
}
if (options.isEnableBackpressureHandling() != null) {
setEnableBackpressureHandling(options.isEnableBackpressureHandling());
}
Expand Down
57 changes: 57 additions & 0 deletions sentry/src/main/java/io/sentry/util/ErrorUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.sentry.util;

import io.sentry.FilterString;
import io.sentry.SentryEvent;
import io.sentry.protocol.Message;
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<FilterString> ignoredErrors, final @NotNull SentryEvent event) {
if (event == null || ignoredErrors == null || ignoredErrors.isEmpty()) {
return false;
}

final @NotNull Set<String> 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 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;
}
}
9 changes: 9 additions & 0 deletions sentry/src/main/java/io/sentry/util/ExceptionUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.util;

import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

Expand All @@ -20,4 +21,12 @@ public final class ExceptionUtils {
}
return rootCause;
}

/** Checks if an exception has been ignored. */
@ApiStatus.Internal
public static boolean isIgnored(
final @NotNull Set<Class<? extends Throwable>> ignoredExceptionsForType,
final @NotNull Throwable throwable) {
return ignoredExceptionsForType.contains(throwable.getClass());
}
}
9 changes: 9 additions & 0 deletions sentry/src/test/java/io/sentry/ExternalOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ class ExternalOptionsTest {
}
}

@Test
fun `creates options with ignored error patterns using external properties`() {
val logger = mock<ILogger>()
withPropertiesFile("ignored-errors=Some error,Another .*", logger) { options ->
assertTrue(options.ignoredErrors!!.contains("Some error"))
assertTrue(options.ignoredErrors!!.contains("Another .*"))
}
}

@Test
fun `creates options with single bundle ID using external properties`() {
withPropertiesFile("bundle-ids=12ea7a02-46ac-44c0-a5bb-6d1fd9586411") { options ->
Expand Down
Loading

0 comments on commit 5c403ff

Please sign in to comment.