Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
- App starts are measured from process start on Android 7.0+ (API 24), and slow and frozen frames are measured per span
- `ActivityFramesTracker` is removed, and the `ActivityLifecycleIntegration` constructor no longer takes it

### Fixes

- Preserve independent Netty read and write timeout events when exception singletons are reused ([#6465](https://github.com/getsentry/sentry-java/pull/6465))

### Improvements

- Promote check-in APIs from experimental to stable ([#6422](https://github.com/getsentry/sentry-java/pull/6422))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ final class ManifestMetadataReader {

static final String IGNORED_ERRORS = "io.sentry.ignored-errors";

static final String IGNORED_ERRORS_FOR_DEDUPLICATION =
"io.sentry.ignored-errors-for-deduplication";

static final String IN_APP_INCLUDES = "io.sentry.in-app-includes";

static final String IN_APP_EXCLUDES = "io.sentry.in-app-excludes";
Expand Down Expand Up @@ -731,6 +734,12 @@ static void applyMetadata(

options.setIgnoredErrors(readList(metadata, logger, IGNORED_ERRORS));

final @Nullable List<String> ignoredErrorsForDeduplication =
readList(metadata, logger, IGNORED_ERRORS_FOR_DEDUPLICATION);
if (ignoredErrorsForDeduplication != null) {
options.setIgnoredErrorsForDeduplication(ignoredErrorsForDeduplication);
}

final @Nullable List<String> includes = readList(metadata, logger, IN_APP_INCLUDES);
if (includes != null && !includes.isEmpty()) {
for (final @NotNull String include : includes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2087,6 +2087,46 @@ class ManifestMetadataReaderTest {
)
}

@Test
fun `applyMetadata reads ignored errors for deduplication if found`() {
val bundle =
bundleOf(
ManifestMetadataReader.IGNORED_ERRORS_FOR_DEDUPLICATION to
"example.FirstException,example.SecondException"
)
val context = fixture.getContext(metaData = bundle)

ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

assertThat(fixture.options.ignoredErrorsForDeduplication)
.containsExactly("example.FirstException", "example.SecondException")
.inOrder()
}

@Test
fun `applyMetadata preserves default ignored errors for deduplication if missing`() {
val context = fixture.getContext()

ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

assertThat(fixture.options.ignoredErrorsForDeduplication)
.containsExactly(
"io.netty.handler.timeout.ReadTimeoutException",
"io.netty.handler.timeout.WriteTimeoutException",
)
.inOrder()
}

@Test
fun `applyMetadata empty ignored errors for deduplication removes the default`() {
val bundle = bundleOf(ManifestMetadataReader.IGNORED_ERRORS_FOR_DEDUPLICATION to "")
val context = fixture.getContext(metaData = bundle)

ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

assertThat(fixture.options.ignoredErrorsForDeduplication).isEmpty()
}

@Test
fun `applyMetadata reads inAppIncludes to options and sets the value if found`() {
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ class SentryAutoConfigurationTest {
"sentry.send-modules=false",
"sentry.ignored-checkins=slug1,slugB",
"sentry.ignored-errors=Some error,Another .*",
"sentry.ignored-errors-for-deduplication=example.FirstException,example.SecondException",
"sentry.ignored-transactions=transactionName1,transactionNameB",
"sentry.enable-backpressure-handling=false",
"sentry.enable-database-transaction-tracing=true",
Expand Down Expand Up @@ -280,6 +281,8 @@ class SentryAutoConfigurationTest {
assertThat(options.isSendModules).isEqualTo(false)
assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB")
assertThat(options.ignoredErrors).containsOnly("Some error", "Another .*")
assertThat(options.ignoredErrorsForDeduplication)
.containsOnly("example.FirstException", "example.SecondException")
assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB")
assertThat(options.isEnableBackpressureHandling).isEqualTo(false)
assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true)
Expand Down Expand Up @@ -353,6 +356,19 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `empty ignored errors for deduplication removes the default`() {
contextRunner
.withPropertyValues(
"sentry.dsn=http://key@localhost/proj",
"sentry.ignored-errors-for-deduplication=",
)
.run {
val options = it.getBean(SentryProperties::class.java)
assertThat(options.ignoredErrorsForDeduplication).isEmpty()
}
}

@Test
fun `when traces sample rate is set to null and tracing is enabled, traces sample rate should be set to 0`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class SentryAutoConfigurationTest {
"sentry.send-modules=false",
"sentry.ignored-checkins=slug1,slugB",
"sentry.ignored-errors=Some error,Another .*",
"sentry.ignored-errors-for-deduplication=example.FirstException,example.SecondException",
"sentry.ignored-transactions=transactionName1,transactionNameB",
"sentry.enable-backpressure-handling=false",
"sentry.enable-database-transaction-tracing=true",
Expand Down Expand Up @@ -285,6 +286,8 @@ class SentryAutoConfigurationTest {
assertThat(options.isSendModules).isEqualTo(false)
assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB")
assertThat(options.ignoredErrors).containsOnly("Some error", "Another .*")
assertThat(options.ignoredErrorsForDeduplication)
.containsOnly("example.FirstException", "example.SecondException")
assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB")
assertThat(options.isEnableBackpressureHandling).isEqualTo(false)
assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true)
Expand Down Expand Up @@ -361,6 +364,19 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `empty ignored errors for deduplication removes the default`() {
contextRunner
.withPropertyValues(
"sentry.dsn=http://key@localhost/proj",
"sentry.ignored-errors-for-deduplication=",
)
.run {
val options = it.getBean(SentryProperties::class.java)
assertThat(options.ignoredErrorsForDeduplication).isEmpty()
}
}

@Test
fun `when traces sample rate is set to null and tracing is enabled, traces sample rate should be set to 0`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ class SentryAutoConfigurationTest {
"sentry.send-modules=false",
"sentry.ignored-checkins=slug1,slugB",
"sentry.ignored-errors=Some error,Another .*",
"sentry.ignored-errors-for-deduplication=example.FirstException,example.SecondException",
"sentry.ignored-transactions=transactionName1,transactionNameB",
"sentry.enable-backpressure-handling=false",
"sentry.enable-database-transaction-tracing=true",
Expand Down Expand Up @@ -283,6 +284,8 @@ class SentryAutoConfigurationTest {
assertThat(options.isSendModules).isEqualTo(false)
assertThat(options.ignoredCheckIns).containsOnly("slug1", "slugB")
assertThat(options.ignoredErrors).containsOnly("Some error", "Another .*")
assertThat(options.ignoredErrorsForDeduplication)
.containsOnly("example.FirstException", "example.SecondException")
assertThat(options.ignoredTransactions).containsOnly("transactionName1", "transactionNameB")
assertThat(options.isEnableBackpressureHandling).isEqualTo(false)
assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true)
Expand Down Expand Up @@ -359,6 +362,19 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `empty ignored errors for deduplication removes the default`() {
contextRunner
.withPropertyValues(
"sentry.dsn=http://key@localhost/proj",
"sentry.ignored-errors-for-deduplication=",
)
.run {
val options = it.getBean(SentryProperties::class.java)
assertThat(options.ignoredErrorsForDeduplication).isEmpty()
}
}

@Test
fun `when traces sample rate is set to null and tracing is enabled, traces sample rate should be set to 0`() {
dsnEnabledRunner.run {
Expand Down
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ public final class io/sentry/ExternalOptions {
public fun getIdleTimeout ()Ljava/lang/Long;
public fun getIgnoredCheckIns ()Ljava/util/List;
public fun getIgnoredErrors ()Ljava/util/List;
public fun getIgnoredErrorsForDeduplication ()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 @@ -623,6 +624,7 @@ public final class io/sentry/ExternalOptions {
public fun setIdleTimeout (Ljava/lang/Long;)V
public fun setIgnoredCheckIns (Ljava/util/List;)V
public fun setIgnoredErrors (Ljava/util/List;)V
public fun setIgnoredErrorsForDeduplication (Ljava/util/List;)V
public fun setIgnoredTransactions (Ljava/util/List;)V
public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V
public fun setOrgId (Ljava/lang/String;)V
Expand Down Expand Up @@ -3495,6 +3497,7 @@ public class io/sentry/SentryOptions : io/sentry/transport/RateLimiterConfig {
public fun addEventProcessor (Lio/sentry/EventProcessor;)V
public fun addIgnoredCheckIn (Ljava/lang/String;)V
public fun addIgnoredError (Ljava/lang/String;)V
public fun addIgnoredErrorForDeduplication (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 @@ -3554,7 +3557,9 @@ public class io/sentry/SentryOptions : io/sentry/transport/RateLimiterConfig {
public fun getIgnoredCheckInFilters ()Ljava/util/List;
public fun getIgnoredCheckIns ()Ljava/util/List;
public fun getIgnoredErrorFilters ()Ljava/util/List;
public fun getIgnoredErrorFiltersForDeduplication ()Ljava/util/List;
public fun getIgnoredErrors ()Ljava/util/List;
public fun getIgnoredErrorsForDeduplication ()Ljava/util/List;
public fun getIgnoredExceptionsForType ()Ljava/util/Set;
public fun getIgnoredSpanOriginFilters ()Ljava/util/List;
public fun getIgnoredSpanOrigins ()Ljava/util/List;
Expand Down Expand Up @@ -3729,6 +3734,7 @@ public class io/sentry/SentryOptions : io/sentry/transport/RateLimiterConfig {
public fun setIdleTimeout (Ljava/lang/Long;)V
public fun setIgnoredCheckIns (Ljava/util/List;)V
public fun setIgnoredErrors (Ljava/util/List;)V
public fun setIgnoredErrorsForDeduplication (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
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,49 @@ public DuplicateEventDetectionEventProcessor(final @NotNull SentryOptions option
if (options.isEnableDeduplication()) {
final Throwable throwable = event.getThrowable();
if (throwable != null) {
if (capturedObjects.containsKey(throwable)
|| containsAnyKey(capturedObjects, allCauses(throwable))) {
if (isIgnoredForDeduplication(throwable)) {
return event;
}
if (capturedObjects.containsKey(throwable) || containsAnyKey(allCauses(throwable))) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Duplicate Exception detected. Event %s will be discarded.",
event.getEventId());
return null;
} else {
capturedObjects.put(throwable, null);
}
capturedObjects.put(throwable, null);
}
} else {
options.getLogger().log(SentryLevel.DEBUG, "Event deduplication is disabled.");
}
return event;
}

private static <T> boolean containsAnyKey(
final @NotNull Map<T, Object> map, final @NotNull List<T> list) {
for (T entry : list) {
if (map.containsKey(entry)) {
private boolean containsAnyKey(final @NotNull List<Throwable> throwables) {
for (final Throwable throwable : throwables) {
if (isIgnoredForDeduplication(throwable)) {
return false;
}
if (capturedObjects.containsKey(throwable)) {
return true;
}
}
return false;
}

private boolean isIgnoredForDeduplication(final @NotNull Throwable throwable) {
final @Nullable List<FilterString> ignoredErrors =
options.getIgnoredErrorFiltersForDeduplication();
if (ignoredErrors == null || ignoredErrors.isEmpty()) {
return false;
}

final String exceptionClassName = throwable.getClass().getName();
for (final FilterString ignoredError : ignoredErrors) {
if (ignoredError.getFilterString().equals(exceptionClassName)
|| ignoredError.matches(exceptionClassName)) {
return true;
}
}
Expand Down
12 changes: 12 additions & 0 deletions sentry/src/main/java/io/sentry/ExternalOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public final class ExternalOptions {
private final @NotNull Set<Class<? extends Throwable>> ignoredExceptionsForType =
new CopyOnWriteArraySet<>();
private @Nullable List<String> ignoredErrors;
private @Nullable List<String> ignoredErrorsForDeduplication;
private @Nullable Boolean printUncaughtStackTrace;
private @Nullable Boolean sendClientReports;
private @NotNull Set<String> bundleIds = new CopyOnWriteArraySet<>();
Expand Down Expand Up @@ -148,6 +149,8 @@ public final class ExternalOptions {
propertiesProvider.getLongProperty("session-flush-timeout-millis"));

options.setIgnoredErrors(propertiesProvider.getListOrNull("ignored-errors"));
options.setIgnoredErrorsForDeduplication(
propertiesProvider.getListOrNull("ignored-errors-for-deduplication"));

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

Expand Down Expand Up @@ -549,6 +552,15 @@ public void setIgnoredErrors(final @Nullable List<String> ignoredErrors) {
this.ignoredErrors = ignoredErrors;
}

public @Nullable List<String> getIgnoredErrorsForDeduplication() {
return ignoredErrorsForDeduplication;
}

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

public @Nullable Boolean getSendClientReports() {
return sendClientReports;
}
Expand Down
Loading
Loading