From 7d6c3b59a5677de14d1bb01a921db3672a634ecf Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 22 Jan 2025 05:32:22 +0100 Subject: [PATCH 01/15] Add a "Summary" headline to the changelog (#4077) * merge changelog entries of pre releases * modify changelog * Update CHANGELOG.md Co-authored-by: Lukas Bloder * review changes * update changelog * add auto ip change * use previous self hosted version since we are no longer using is_exception_group * more review comments * add a headline to fix changelog parse error --------- Co-authored-by: Lukas Bloder --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30aba6f133..51233ff750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 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` From 926429ad065f260a39051ed3295c09561c50082b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 22 Jan 2025 08:59:45 +0100 Subject: [PATCH 02/15] Replace awaitility with CountDownLatch to try and fix the test on CI (#4084) * Replace awaitility with CountDownLatch to try and fix the test on CI * Update sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt --- .../java/io/sentry/transport/RateLimiterTest.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt index e9a38631cc..5e038f7b5d 100644 --- a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt +++ b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt @@ -39,6 +39,8 @@ import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import java.io.File import java.util.UUID +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.Test import kotlin.test.assertEquals @@ -363,18 +365,17 @@ class RateLimiterTest { val rateLimiter = fixture.getSUT() whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 1, 2001) - val applied = AtomicBoolean(true) + val applied = CountDownLatch(1) + var activeForReplay = false rateLimiter.addRateLimitObserver { - applied.set(rateLimiter.isActiveForCategory(Replay)) + applied.countDown() + activeForReplay = rateLimiter.isActiveForCategory(Replay) } rateLimiter.updateRetryAfterLimits("1:replay:key", null, 1) rateLimiter.close() - // If rate limit didn't already change, wait for 1.5s to ensure the timer has run after 1s - if (!applied.get()) { - await.untilTrue(applied) - } - assertTrue(applied.get()) + applied.await(2, TimeUnit.SECONDS) + assertTrue(activeForReplay) } } From 22bba295447536927cf0f14e9a8f42fa5744fe21 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 22 Jan 2025 13:24:00 +0100 Subject: [PATCH 03/15] Improve v8 changelog (#4093) link to migration guide where possible and also at the top fix exra -> extra --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51233ff750..d8623a5d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Version 8 of the Sentry Android/Java SDK brings a variety of features and fixes. - We now support GraphQL v22 (`sentry-graphql-22`) - Metrics have been removed +Please take a look at [our migration guide in docs](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). + ### Sentry Self-hosted Compatibility This SDK version is compatible with a self-hosted version of Sentry `22.12.0` or higher. If you are using an older version of [self-hosted Sentry](https://develop.sentry.dev/self-hosted/) (aka onpremise), you will need to [upgrade](https://develop.sentry.dev/self-hosted/releases/). If you're using `sentry.io` no action is required. @@ -62,12 +64,12 @@ This SDK version is compatible with a self-hosted version of Sentry `22.12.0` or - Global scope is attached to all events created by the SDK. It can also be modified before `Sentry.init` has been called. It can be manipulated using `Sentry.configureScope(ScopeType.GLOBAL, (scope) -> { ... })`. - Isolation scope can be used e.g. to attach data to all events that come up while handling an incoming request. It can also be used for other isolation purposes. It can be manipulated using `Sentry.configureScope(ScopeType.ISOLATION, (scope) -> { ... })`. The SDK automatically forks isolation scope in certain cases like incoming requests, CRON jobs, Spring `@Async` and more. - Current scope is forked often and data added to it is only added to events that are created while this scope is active. Data is also passed on to newly forked child scopes but not to parents. It can be manipulated using `Sentry.configureScope(ScopeType.CURRENT, (scope) -> { ... })`. -- `Sentry.popScope` has been deprecated, please call `.close()` on the token returned by `Sentry.pushScope` instead or use it in a way described in more detail in "Migration Guide". +- `Sentry.popScope` has been deprecated, please call `.close()` on the token returned by `Sentry.pushScope` instead or use it in a way described in more detail in [our migration guide](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). - We have chosen a default scope that is used for `Sentry.configureScope()` as well as API like `Sentry.setTag()` - For Android the type defaults to `CURRENT` scope - For Backend and other JVM applicatons it defaults to `ISOLATION` scope - Event processors on `Scope` can now be ordered by overriding the `getOrder` method on implementations of `EventProcessor`. NOTE: This order only applies to event processors on `Scope` but not `SentryOptions` at the moment. Feel free to request this if you need it. -- `Hub` is deprecated in favor of `Scopes`, alongside some `Hub` relevant APIs. More details can be found in the "Migration Guide" section. +- `Hub` is deprecated in favor of `Scopes`, alongside some `Hub` relevant APIs. More details can be found in [our migration guide](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). - Send file name and path only if `isSendDefaultPii` is `true` ([#3919](https://github.com/getsentry/sentry-java/pull/3919)) - (Android) Enable Performance V2 by default ([#3824](https://github.com/getsentry/sentry-java/pull/3824)) - With this change cold app start spans will include spans for ContentProviders, Application and Activity load. @@ -148,7 +150,7 @@ This SDK version is compatible with a self-hosted version of Sentry `22.12.0` or - Previously request body was only attached for `application/json` requests - Set breadcrumb level based on http status ([#3771](https://github.com/getsentry/sentry-java/pull/3771)) - Emit transaction.data inside contexts.trace.data ([#3735](https://github.com/getsentry/sentry-java/pull/3735)) - - Also does not emit `transaction.data` in `exras` anymore + - Also does not emit `transaction.data` in `extras` anymore - Add a sample for showcasing Sentry with OpenTelemetry for Spring Boot 3 with our Java agent (`sentry-samples-spring-boot-jakarta-opentelemetry`) ([#3856](https://github.com/getsentry/sentry-java/pull/3828)) - Add a sample for showcasing Sentry with OpenTelemetry for Spring Boot 3 without our Java agent (`sentry-samples-spring-boot-jakarta-opentelemetry-noagent`) ([#3856](https://github.com/getsentry/sentry-java/pull/3856)) - Add a sample for showcasing Sentry with OpenTelemetry (`sentry-samples-console-opentelemetry-noagent`) ([#3856](https://github.com/getsentry/sentry-java/pull/3862)) From 890be7396fa2edd6a5066991ac8aa41772127ba0 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 22 Jan 2025 16:08:18 +0100 Subject: [PATCH 04/15] Add Lorenzo to CODEOWNERS (#4088) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dfca015d13..a19e12c1c1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @adinauer @romtsn @stefanosiano @markushi +* @adinauer @romtsn @stefanosiano @markushi @lcian From 0bb5768e8f98be46e05c6a815df4f4610a777764 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 22 Jan 2025 16:43:20 +0100 Subject: [PATCH 05/15] chore: Increase date range for MIT licence (#4095) It's 2025 now. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f49694a15b..f05bcf3e45 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Sentry +Copyright (c) 2019-2025 Sentry Copyright (c) 2015 Salomon BRYS for Android ANRWatchDog Permission is hereby granted, free of charge, to any person obtaining a copy From 830e57d26a30355ace7c3b026592e5b1048141c4 Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 22 Jan 2025 17:03:24 +0100 Subject: [PATCH 06/15] Avoid logging an error when a float is passed in the manifest (#4031) --- CHANGELOG.md | 6 ++++++ .../java/io/sentry/android/core/ManifestMetadataReader.java | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8623a5d80..2e4603cd4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Avoid logging an error when a float is passed in the manifest ([#4031](https://github.com/getsentry/sentry-java/pull/4031)) + ## 8.0.0 ### Summary diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index fcb755ec02..1bad409125 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -475,7 +475,10 @@ private static boolean readBool( private static @NotNull Double readDouble( final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { // manifest meta-data only reads float - final Double value = ((Number) metadata.getFloat(key, metadata.getInt(key, -1))).doubleValue(); + double value = ((Float) metadata.getFloat(key, -1)).doubleValue(); + if (value == -1) { + value = ((Integer) metadata.getInt(key, -1)).doubleValue(); + } logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } From ce2672f91189645636796bd61c6e4c26bbc15515 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:02:19 +0100 Subject: [PATCH 07/15] Bump codecov/codecov-action from 5.1.1 to 5.1.2 (#4062) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.1 to 5.1.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/7f8b4b4bde536c465e797be725718b88c5d95e0e...1e68e06f1dbfde0e4cefc87efeba9e4643565303) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe3684e89d..97c2de1c43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: run: make preMerge - name: Upload coverage to Codecov - uses: codecov/codecov-action@7f8b4b4bde536c465e797be725718b88c5d95e0e # pin@v4 + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # pin@v4 with: name: sentry-java fail_ci_if_error: false From e2e1435c22edf955e5f8a09a4235373f696a9dd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:10:12 +0000 Subject: [PATCH 08/15] Bump actions/create-github-app-token from 1.11.0 to 1.11.1 (#4064) Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.11.0 to 1.11.1. - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/5d869da34e18e7287c1daad50e0b8ea0f506ce69...c1a285145b9d317df6ced56c09f525b5c2b6f755) --- updated-dependencies: - dependency-name: actions/create-github-app-token dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd08109449..c15509c56d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From 467b52b2fd6d6b93881e1b488c95d1d243b40c96 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 23 Jan 2025 16:43:13 +0100 Subject: [PATCH 09/15] Remove debug logs when searching for OpenTelemetry marker classes (#4091) * Remove debug logs when searching for OpenTelemetry marker classes * changelog --- CHANGELOG.md | 3 +++ .../java/io/sentry/opentelemetry/OpenTelemetryUtil.java | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4603cd4f..c0dc15d200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### Fixes - Avoid logging an error when a float is passed in the manifest ([#4031](https://github.com/getsentry/sentry-java/pull/4031)) +- Remove `java.lang.ClassNotFoundException` debug logs when searching for OpenTelemetry marker classes ([#4091](https://github.com/getsentry/sentry-java/pull/4091)) + - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. + - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. ## 8.0.0 diff --git a/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java b/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java index b491ab5278..3bf7eac1a2 100644 --- a/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java +++ b/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java @@ -1,5 +1,6 @@ package io.sentry.opentelemetry; +import io.sentry.NoOpLogger; import io.sentry.SentryOpenTelemetryMode; import io.sentry.SentryOptions; import io.sentry.util.LoadClass; @@ -29,15 +30,15 @@ public static void applyIgnoredSpanOrigins( final @NotNull SentryOpenTelemetryMode openTelemetryMode = options.getOpenTelemetryMode(); if (SentryOpenTelemetryMode.AUTO.equals(openTelemetryMode)) { if (loadClass.isClassAvailable( - "io.sentry.opentelemetry.agent.AgentMarker", options.getLogger())) { + "io.sentry.opentelemetry.agent.AgentMarker", NoOpLogger.getInstance())) { return SpanUtils.ignoredSpanOriginsForOpenTelemetry(SentryOpenTelemetryMode.AGENT); } if (loadClass.isClassAvailable( - "io.sentry.opentelemetry.agent.AgentlessMarker", options.getLogger())) { + "io.sentry.opentelemetry.agent.AgentlessMarker", NoOpLogger.getInstance())) { return SpanUtils.ignoredSpanOriginsForOpenTelemetry(SentryOpenTelemetryMode.AGENTLESS); } if (loadClass.isClassAvailable( - "io.sentry.opentelemetry.agent.AgentlessSpringMarker", options.getLogger())) { + "io.sentry.opentelemetry.agent.AgentlessSpringMarker", NoOpLogger.getInstance())) { return SpanUtils.ignoredSpanOriginsForOpenTelemetry( SentryOpenTelemetryMode.AGENTLESS_SPRING); } From 2872e8eea256f8fdc2a15f4621caa23a1545effa Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 23 Jan 2025 17:14:04 +0100 Subject: [PATCH 10/15] Fix flaky RateLimiter test (#4100) * changed RateLimiterTest `close cancels the timer` to use reflection --- .../io/sentry/transport/RateLimiterTest.kt | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt index 5e038f7b5d..ade4ab88b9 100644 --- a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt +++ b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt @@ -28,6 +28,8 @@ import io.sentry.hints.DiskFlushNotification import io.sentry.protocol.SentryId import io.sentry.protocol.SentryTransaction import io.sentry.protocol.User +import io.sentry.test.getProperty +import io.sentry.test.injectForField import io.sentry.util.HintUtils import org.awaitility.kotlin.await import org.mockito.kotlin.eq @@ -38,9 +40,8 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import java.io.File +import java.util.Timer import java.util.UUID -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.Test import kotlin.test.assertEquals @@ -363,19 +364,16 @@ class RateLimiterTest { @Test fun `close cancels the timer`() { val rateLimiter = fixture.getSUT() - whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 1, 2001) - - val applied = CountDownLatch(1) - var activeForReplay = false - rateLimiter.addRateLimitObserver { - applied.countDown() - activeForReplay = rateLimiter.isActiveForCategory(Replay) - } + val timer = mock() + rateLimiter.injectForField("timer", timer) - rateLimiter.updateRetryAfterLimits("1:replay:key", null, 1) + // When the rate limiter is closed rateLimiter.close() - applied.await(2, TimeUnit.SECONDS) - assertTrue(activeForReplay) + // Then the timer is cancelled + verify(timer).cancel() + + // And is removed by the rateLimiter + assertNull(rateLimiter.getProperty("timer")) } } From b4e611a56f8252e8b43a6e20fa4eacbc17ac5cbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:47:25 +0000 Subject: [PATCH 11/15] Bump github/codeql-action from 3.27.9 to 3.28.1 (#4063) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.9 to 3.28.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/df409f7d9260372bd5f19e5b04e83cb3c43714ae...b6a472f63d85b9c78a3ac5e89422239fc15e9b3c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 30ad0b95d7..73769be7d6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: gradle-home-cache-cleanup: true - name: Initialize CodeQL - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # pin@v2 + uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # pin@v2 with: languages: 'java' @@ -48,4 +48,4 @@ jobs: ./gradlew buildForCodeQL - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # pin@v2 + uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # pin@v2 From aff6e09b6b4bc88b8d86121a7f018826d831b75a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:52:25 +0000 Subject: [PATCH 12/15] Bump gradle/actions (#4097) Bumps [gradle/actions](https://github.com/gradle/actions) from 3839b20885c2c3507be5f0521853826f4b37038a to 8790d96bb8fdd8ae7edfb2eada090c650b257f27. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/3839b20885c2c3507be5f0521853826f4b37038a...8790d96bb8fdd8ae7edfb2eada090c650b257f27) --- updated-dependencies: - dependency-name: gradle/actions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/agp-matrix.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/enforce-license-compliance.yml | 2 +- .github/workflows/generate-javadocs.yml | 2 +- .github/workflows/integration-tests-benchmarks.yml | 4 ++-- .github/workflows/integration-tests-ui-critical.yml | 2 +- .github/workflows/integration-tests-ui.yml | 2 +- .github/workflows/release-build.yml | 2 +- .github/workflows/system-tests-backend.yml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index a0a7e2405c..f1e375ba65 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -38,7 +38,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97c2de1c43..9160d5c8ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 73769be7d6..22feb2486f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index ee69bb0b5c..0fe847cd55 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/generate-javadocs.yml b/.github/workflows/generate-javadocs.yml index 564cf4c43d..dc0e2f5da2 100644 --- a/.github/workflows/generate-javadocs.yml +++ b/.github/workflows/generate-javadocs.yml @@ -20,7 +20,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-benchmarks.yml b/.github/workflows/integration-tests-benchmarks.yml index 1b6295bc5b..c04d2bc624 100644 --- a/.github/workflows/integration-tests-benchmarks.yml +++ b/.github/workflows/integration-tests-benchmarks.yml @@ -37,7 +37,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true @@ -86,7 +86,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-ui-critical.yml b/.github/workflows/integration-tests-ui-critical.yml index 201b1551f9..112dc8ad25 100644 --- a/.github/workflows/integration-tests-ui-critical.yml +++ b/.github/workflows/integration-tests-ui-critical.yml @@ -32,7 +32,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-ui.yml b/.github/workflows/integration-tests-ui.yml index 2b1bbeacc2..0bf4b12dd6 100644 --- a/.github/workflows/integration-tests-ui.yml +++ b/.github/workflows/integration-tests-ui.yml @@ -32,7 +32,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 0883d940f5..4de0e49e1a 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -26,7 +26,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index e8bb0d77e8..b4d4233b08 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -56,7 +56,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3839b20885c2c3507be5f0521853826f4b37038a # pin@v3 + uses: gradle/actions/setup-gradle@8790d96bb8fdd8ae7edfb2eada090c650b257f27 # pin@v3 with: gradle-home-cache-cleanup: true From a136d5be492ce47935ec6f66aa6292c8c1d250af Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 24 Jan 2025 09:58:46 +0100 Subject: [PATCH 13/15] Remove date range for LICENSE (#4096) In our internal Open Source Legal Policy, we decided that licenses don't require a data range. This also has the advantage of not updating the date range yearly. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f05bcf3e45..6b8b8d58af 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Sentry +Copyright (c) 2019 Sentry Copyright (c) 2015 Salomon BRYS for Android ANRWatchDog Permission is hereby granted, free of charge, to any person obtaining a copy From 5c403ff8a71e31d6b5f92d6e8fc81b96e54f1dc3 Mon Sep 17 00:00:00 2001 From: Lorenzo Cian Date: Fri, 24 Jan 2025 12:49:21 +0100 Subject: [PATCH 14/15] Add `options.ignoredErrors` accepting String and Regex (#4083) * 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` --- CHANGELOG.md | 8 +++ .../jakarta/SentryAutoConfigurationTest.kt | 2 + .../boot/SentryAutoConfigurationTest.kt | 2 + sentry/api/sentry.api | 11 ++++ .../main/java/io/sentry/ExternalOptions.java | 16 +++-- .../src/main/java/io/sentry/SentryClient.java | 23 ++++--- .../main/java/io/sentry/SentryOptions.java | 59 ++++++++++++++++++ .../main/java/io/sentry/util/ErrorUtils.java | 57 ++++++++++++++++++ .../java/io/sentry/util/ExceptionUtils.java | 9 +++ .../java/io/sentry/ExternalOptionsTest.kt | 9 +++ .../test/java/io/sentry/SentryClientTest.kt | 60 +++++++++++++++++++ .../test/java/io/sentry/SentryOptionsTest.kt | 2 + 12 files changed, 247 insertions(+), 11 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/ErrorUtils.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c0dc15d200..7e8a44bbbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) 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 e559502e62..3498c90319 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-errors=Some error,Another .*", "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.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 2512fe1f11..cd105dab44 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-errors=Some error,Another .*", "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.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 a0d559f12f..642b67ec06 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -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; @@ -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 @@ -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 @@ -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; @@ -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 @@ -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 ()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; @@ -6055,6 +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/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 d9b075e1c8..ed2b4e1103 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 ignoredErrors; private @Nullable Boolean printUncaughtStackTrace; private @Nullable Boolean sendClientReports; private @NotNull Set bundleIds = new CopyOnWriteArraySet<>(); @@ -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( @@ -373,6 +373,14 @@ public void setIdleTimeout(final @Nullable Long idleTimeout) { this.idleTimeout = idleTimeout; } + public @Nullable List getIgnoredErrors() { + return ignoredErrors; + } + + public void setIgnoredErrors(final @Nullable List ignoredErrors) { + this.ignoredErrors = ignoredErrors; + } + 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 1cfbf60313..277be87aee 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,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( @@ -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)) { diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 3a6d75789a..2617cd3dfa 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<>(); + /** + * 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 ignoredErrors = 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,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 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; + } else { + @NotNull final List 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. * @@ -2801,6 +2856,10 @@ public void merge(final @NotNull ExternalOptions options) { final List ignoredTransactions = new ArrayList<>(options.getIgnoredTransactions()); setIgnoredTransactions(ignoredTransactions); } + 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 0000000000..cb8b3dbce9 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/ErrorUtils.java @@ -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 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 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 04285751c1..9d6033a96c 100644 --- a/sentry/src/main/java/io/sentry/util/ExceptionUtils.java +++ b/sentry/src/main/java/io/sentry/util/ExceptionUtils.java @@ -1,5 +1,6 @@ package io.sentry.util; +import java.util.Set; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -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> ignoredExceptionsForType, + final @NotNull Throwable throwable) { + 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 b25f67405c..f32b6cf8c0 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -209,6 +209,15 @@ class ExternalOptionsTest { } } + @Test + fun `creates options with ignored error patterns using external properties`() { + val logger = mock() + 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 -> diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index da57f5376e..63052022eb 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 @@ -1758,6 +1759,65 @@ class SentryClientTest { verify(fixture.transport, never()).send(any(), anyOrNull()) } + @Test + fun `when event message matches string in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError("hello") + val sut = fixture.getSut() + 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 event message matches regex pattern in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError("hello .*") + val sut = fixture.getSut() + 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 message matches regex pattern in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError(".*hello .*") + val sut = fixture.getSut() + sut.captureException(RuntimeException("hello world")) + verify(fixture.transport, never()).send(any(), anyOrNull()) + } + + @Test + fun `when class matches regex pattern in ignoredErrors, capturing event does not send it`() { + fixture.sentryOptions.addIgnoredError("java\\.lang\\..*") + val sut = fixture.getSut() + sut.captureException(RuntimeException("hello world")) + verify(fixture.transport, never()).send(any(), anyOrNull()) + } + + @Test + fun `when ignoredExceptionsForType and ignoredErrors are not explicitly specified, capturing event sends event`() { + val sut = fixture.getSut() + sut.captureException(RuntimeException("test")) + 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 46482a1083..278c351916 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.ignoredErrors = listOf("Some error", "Another .*") 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("Some error"), FilterString("Another .*")), options.ignoredErrors) assertFalse(options.isEnableBackpressureHandling) assertTrue(options.isForceInit) assertNotNull(options.cron) From 29a4185d68730c90a62ce47fb0e88a45b0ca79fa Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 24 Jan 2025 14:44:00 +0100 Subject: [PATCH 15/15] Add `request` details to transactions created through OpenTelemetry (#4098) * Attach request object to event for OTel * fix test name * rename test class * changelog * do not override existing url on request even with full url --- CHANGELOG.md | 2 + .../api/sentry-opentelemetry-core.api | 5 + .../OpenTelemetryAttributesExtractor.java | 90 ++++++++ .../opentelemetry/SentrySpanExporter.java | 10 +- .../OpenTelemetryAttributesExtractorTest.kt | 201 ++++++++++++++++++ 5 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java create mode 100644 sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8a44bbbd..4608e9bdf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ### Fixes - Avoid logging an error when a float is passed in the manifest ([#4031](https://github.com/getsentry/sentry-java/pull/4031)) +- Add `request` details to transactions created through OpenTelemetry ([#4098](https://github.com/getsentry/sentry-java/pull/4098)) + - We now add HTTP request method and URL where Sentry expects it to display it in Sentry UI - Remove `java.lang.ClassNotFoundException` debug logs when searching for OpenTelemetry marker classes ([#4091](https://github.com/getsentry/sentry-java/pull/4091)) - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api index fd2099a730..1e9bb60416 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api +++ b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api @@ -1,3 +1,8 @@ +public final class io/sentry/opentelemetry/OpenTelemetryAttributesExtractor { + public fun ()V + public fun extract (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/ISpan;Lio/sentry/IScope;)V +} + public final class io/sentry/opentelemetry/OpenTelemetryLinkErrorEventProcessor : io/sentry/EventProcessor { public fun ()V public fun getOrder ()Ljava/lang/Long; diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java new file mode 100644 index 0000000000..431b4d274e --- /dev/null +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java @@ -0,0 +1,90 @@ +package io.sentry.opentelemetry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.UrlAttributes; +import io.sentry.IScope; +import io.sentry.ISpan; +import io.sentry.protocol.Request; +import io.sentry.util.UrlUtils; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public final class OpenTelemetryAttributesExtractor { + + public void extract( + final @NotNull SpanData otelSpan, + final @NotNull ISpan sentrySpan, + final @NotNull IScope scope) { + final @NotNull Attributes attributes = otelSpan.getAttributes(); + addRequestAttributesToScope(attributes, scope); + } + + private void addRequestAttributesToScope(Attributes attributes, IScope scope) { + if (scope.getRequest() == null) { + scope.setRequest(new Request()); + } + final @Nullable Request request = scope.getRequest(); + if (request != null) { + final @Nullable String requestMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD); + if (requestMethod != null) { + request.setMethod(requestMethod); + } + + if (request.getUrl() == null) { + final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL); + if (urlFull != null) { + final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(urlFull); + urlDetails.applyToRequest(request); + } + } + + if (request.getUrl() == null) { + final String urlString = buildUrlString(attributes); + if (!urlString.isEmpty()) { + request.setUrl(urlString); + } + } + + if (request.getQueryString() == null) { + final @Nullable String query = attributes.get(UrlAttributes.URL_QUERY); + if (query != null) { + request.setQueryString(query); + } + } + } + } + + private @NotNull String buildUrlString(final @NotNull Attributes attributes) { + final @Nullable String scheme = attributes.get(UrlAttributes.URL_SCHEME); + final @Nullable String serverAddress = attributes.get(ServerAttributes.SERVER_ADDRESS); + final @Nullable Long serverPort = attributes.get(ServerAttributes.SERVER_PORT); + final @Nullable String path = attributes.get(UrlAttributes.URL_PATH); + + if (scheme == null || serverAddress == null) { + return ""; + } + + final @NotNull StringBuilder urlBuilder = new StringBuilder(); + urlBuilder.append(scheme); + urlBuilder.append("://"); + + if (serverAddress != null) { + urlBuilder.append(serverAddress); + if (serverPort != null) { + urlBuilder.append(":"); + urlBuilder.append(serverPort); + } + } + + if (path != null) { + urlBuilder.append(path); + } + + return urlBuilder.toString(); + } +} diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java index 7851f07550..4d2e7545c6 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java @@ -19,6 +19,7 @@ import io.sentry.ISpan; import io.sentry.ITransaction; import io.sentry.Instrumenter; +import io.sentry.ScopeType; import io.sentry.ScopesAdapter; import io.sentry.SentryDate; import io.sentry.SentryInstantDate; @@ -50,6 +51,8 @@ public final class SentrySpanExporter implements SpanExporter { private final @NotNull SentryWeakSpanStorage spanStorage = SentryWeakSpanStorage.getInstance(); private final @NotNull SpanDescriptionExtractor spanDescriptionExtractor = new SpanDescriptionExtractor(); + private final @NotNull OpenTelemetryAttributesExtractor attributesExtractor = + new OpenTelemetryAttributesExtractor(); private final @NotNull IScopes scopes; private final @NotNull List attributeKeysToRemove = @@ -267,8 +270,10 @@ private void transferSpanDetails( spanStorage.getSentrySpan(span.getSpanContext()); final @Nullable IScopes scopesMaybe = sentrySpanMaybe != null ? sentrySpanMaybe.getScopes() : null; - final @NotNull IScopes scopesToUse = + final @NotNull IScopes scopesToUseBeforeForking = scopesMaybe == null ? ScopesAdapter.getInstance() : scopesMaybe; + final @NotNull IScopes scopesToUse = + scopesToUseBeforeForking.forkedCurrentScope("SentrySpanExporter.createTransaction"); final @NotNull OtelSpanInfo spanInfo = spanDescriptionExtractor.extractSpanInfo(span, sentrySpanMaybe); @@ -331,6 +336,9 @@ private void transferSpanDetails( setOtelSpanKind(span, sentryTransaction); transferSpanDetails(sentrySpanMaybe, sentryTransaction); + scopesToUse.configureScope( + ScopeType.CURRENT, scope -> attributesExtractor.extract(span, sentryTransaction, scope)); + return sentryTransaction; } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt new file mode 100644 index 0000000000..f962cfa594 --- /dev/null +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt @@ -0,0 +1,201 @@ +package io.sentry.opentelemetry + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.sdk.internal.AttributesMap +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.semconv.ServerAttributes +import io.opentelemetry.semconv.UrlAttributes +import io.sentry.ISpan +import io.sentry.Scope +import io.sentry.SentryOptions +import io.sentry.protocol.Request +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class OpenTelemetryAttributesExtractorTest { + + private class Fixture { + val spanData = mock() + val attributes = AttributesMap.create(100, 100) + val sentrySpan = mock() + val options = SentryOptions.empty() + val scope = Scope(options) + + init { + whenever(spanData.attributes).thenReturn(attributes) + } + } + + private val fixture = Fixture() + + @Test + fun `sets URL based on OTel attributes`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + UrlAttributes.URL_PATH to "/path/to/123", + UrlAttributes.URL_QUERY to "q=123456&b=X", + ServerAttributes.SERVER_ADDRESS to "io.sentry", + ServerAttributes.SERVER_PORT to 8081L + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsSetTo("https://io.sentry:8081/path/to/123") + thenQueryIsSetTo("q=123456&b=X") + } + + @Test + fun `when there is an existing request on scope it is filled with more details`() { + fixture.scope.request = Request().also { it.bodySize = 123L } + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + UrlAttributes.URL_PATH to "/path/to/123", + UrlAttributes.URL_QUERY to "q=123456&b=X", + ServerAttributes.SERVER_ADDRESS to "io.sentry", + ServerAttributes.SERVER_PORT to 8081L + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsSetTo("https://io.sentry:8081/path/to/123") + thenQueryIsSetTo("q=123456&b=X") + assertEquals(123L, fixture.scope.request!!.bodySize) + } + + @Test + fun `when there is an existing request with url on scope it is kept`() { + fixture.scope.request = Request().also { + it.url = "http://docs.sentry.io:3000/platform" + it.queryString = "s=abc" + } + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + UrlAttributes.URL_PATH to "/path/to/123", + UrlAttributes.URL_QUERY to "q=123456&b=X", + ServerAttributes.SERVER_ADDRESS to "io.sentry", + ServerAttributes.SERVER_PORT to 8081L + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsSetTo("http://docs.sentry.io:3000/platform") + thenQueryIsSetTo("s=abc") + } + + @Test + fun `when there is an existing request with url on scope it is kept with URL_FULL`() { + fixture.scope.request = Request().also { + it.url = "http://docs.sentry.io:3000/platform" + it.queryString = "s=abc" + } + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://io.sentry:8081/path/to/123?q=123456&b=X" + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsSetTo("http://docs.sentry.io:3000/platform") + thenQueryIsSetTo("s=abc") + } + + @Test + fun `sets URL based on OTel attributes without port`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + UrlAttributes.URL_PATH to "/path/to/123", + ServerAttributes.SERVER_ADDRESS to "io.sentry" + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsSetTo("https://io.sentry/path/to/123") + } + + @Test + fun `sets URL based on OTel attributes without path`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + ServerAttributes.SERVER_ADDRESS to "io.sentry" + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsSetTo("https://io.sentry") + } + + @Test + fun `does not set URL if server address is missing`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https" + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsNotSet() + } + + @Test + fun `does not set URL if scheme is missing`() { + givenAttributes( + mapOf( + ServerAttributes.SERVER_ADDRESS to "io.sentry" + ) + ) + + whenExtractingAttributes() + + thenRequestIsSet() + thenUrlIsNotSet() + } + + private fun givenAttributes(map: Map, Any>) { + map.forEach { k, v -> + fixture.attributes.put(k, v) + } + } + + private fun whenExtractingAttributes() { + OpenTelemetryAttributesExtractor().extract(fixture.spanData, fixture.sentrySpan, fixture.scope) + } + + private fun thenRequestIsSet() { + assertNotNull(fixture.scope.request) + } + + private fun thenUrlIsSetTo(expected: String) { + assertEquals(expected, fixture.scope.request!!.url) + } + + private fun thenUrlIsNotSet() { + assertNull(fixture.scope.request!!.url) + } + + private fun thenQueryIsSetTo(expected: String) { + assertEquals(expected, fixture.scope.request!!.queryString) + } +}