diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 2efc786187c..3fc57ffad65 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -36,7 +36,7 @@ jobs:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- name: Initialize CodeQL
- uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pin@v2
+ uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # pin@v2
with:
languages: 'java'
@@ -45,4 +45,4 @@ jobs:
./gradlew buildForCodeQL --no-build-cache
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pin@v2
+ uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # pin@v2
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
index e40b4563b00..a39e3f8906a 100644
--- a/.github/workflows/danger.yml
+++ b/.github/workflows/danger.yml
@@ -8,4 +8,4 @@ jobs:
danger:
runs-on: ubuntu-latest
steps:
- - uses: getsentry/github-workflows/danger@607fed74f812e69201531a5185b6c3c57caa4e89 # v3
+ - uses: getsentry/github-workflows/danger@959162c159b866489d3fabe93327f48f9b8cb7ee # v3
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2696d4f4758..6dd30d341b2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,7 +34,7 @@ jobs:
fetch-depth: 0
submodules: 'recursive'
- name: Prepare release
- uses: getsentry/craft@55694f8bf3a0fd051e21a877f57b4c0dd8e946bb # v2
+ uses: getsentry/craft@25028d0646040cc0a669ae3314cbf884f4c4347a # v2
env:
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
with:
diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml
index 5b8d3d11628..ef51d5da084 100644
--- a/.github/workflows/update-deps.yml
+++ b/.github/workflows/update-deps.yml
@@ -18,7 +18,7 @@ jobs:
native:
runs-on: ubuntu-latest
steps:
- - uses: getsentry/github-workflows/updater@607fed74f812e69201531a5185b6c3c57caa4e89 # v3
+ - uses: getsentry/github-workflows/updater@959162c159b866489d3fabe93327f48f9b8cb7ee # v3
with:
path: scripts/update-sentry-native-ndk.sh
name: Native SDK
diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml
index ca5108943de..307c7474be9 100644
--- a/.github/workflows/validate-pr.yml
+++ b/.github/workflows/validate-pr.yml
@@ -10,7 +10,7 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: getsentry/github-workflows/validate-pr@607fed74f812e69201531a5185b6c3c57caa4e89 # v3
+ - uses: getsentry/github-workflows/validate-pr@959162c159b866489d3fabe93327f48f9b8cb7ee # v3
with:
app-id: ${{ vars.SDK_MAINTAINER_BOT_APP_ID }}
private-key: ${{ secrets.SDK_MAINTAINER_BOT_PRIVATE_KEY }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e59606e335c..5476f6d9831 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,121 +4,22 @@
### Features
-- Add `LocalSentrySpan` to `sentry-compose` so apps can provide a parent `ISpan` to a composable subtree and have nested `SentryTraced` spans attach to it ([#6112]https://github.com/getsentry/sentry-java/pull/6112)
-- Add `dataCollection`, a fine-grained replacement for `sendDefaultPii`, for controlling data collected automatically by SDK integrations ([#5759](https://github.com/getsentry/sentry-java/pull/5759))
- - `sendDefaultPii` remains supported for backwards compatibility. When `dataCollection` is not configured, the SDK preserves the existing `sendDefaultPii` behavior.
- - Configuring any `dataCollection` option makes it the source of truth. `sendDefaultPii` is then ignored, and omitted `dataCollection` options use the defaults below.
- - The Logback appender is a compatibility exception. When an encoder is configured, `sendDefaultPii=true` continues to include the original message template and parameters. To opt in independently of `sendDefaultPii`, set `true` on the Sentry appender in `logback.xml` or `logback-spring.xml`.
- - Data explicitly supplied through APIs such as `Sentry.setUser`, scopes, event processors, or `beforeSend` is not affected.
-
- To opt in to the documented `dataCollection` defaults without configuring an individual option:
-
- ```java
- Sentry.init(options -> options.getDataCollection().forceDataCollection());
- ```
-
- | Option | Default | Behavior |
- | --- | --- | --- |
- | `userInfo` | `true` | Allows integrations to populate user identity and IP address information automatically. |
- | `cookies` | `{ mode: DENY_LIST, terms: [] }` | Collects cookies while filtering sensitive values. |
- | `httpHeaders.request` | `{ mode: DENY_LIST, terms: [] }` | Collects request headers while filtering sensitive values. |
- | `httpHeaders.response` | `{ mode: DENY_LIST, terms: [] }` | Collects response headers while filtering sensitive values. |
- | `httpBodies` | All supported body types | Collects supported incoming and outgoing request and response bodies. An empty set disables body collection. |
- | `urlQueryParams` | `{ mode: DENY_LIST, terms: [] }` | Collects URL query parameters while filtering sensitive values. |
- | `graphql.document` | `true` | Collects GraphQL documents. |
- | `graphql.variables` | `true` | Collects GraphQL variables. |
- | `databaseQueryData` | `true` | Allows collection of associated query data, such as bound parameters, write payloads, and results, where supported. Sanitized query statements and structural database metadata remain available. |
- | `filePaths` | `true` | Allows file-system instrumentation to collect file and directory paths. File extensions and byte counts remain available when disabled. |
+- Deprecate `sendDefaultPii` in favor of `dataCollection` ahead of its removal in 9.0 ([#6158](https://github.com/getsentry/sentry-java/pull/6158))
+- Make the tombstone merge time threshold configurable via `SentryAndroidOptions.setTombstoneMergeTimeThresholdMillis` and the `io.sentry.tombstone.merge-time-threshold-millis` manifest option ([#6154](https://github.com/getsentry/sentry-java/pull/6154))
- Cookies, HTTP headers, and URL query parameters support three modes:
-
- - `OFF`: Do not collect the category.
- - `DENY_LIST`: Collect values except those matching the built-in sensitive deny-list or additional configured terms.
- - `ALLOW_LIST`: Only send plaintext values for matching terms. The built-in sensitive deny-list still applies.
-
- Matching is case-insensitive and partial. The built-in sensitive deny-list contains `auth`, `token`, `secret`, `password`, `passwd`, `pwd`, `key`, `jwt`, `bearer`, `sso`, `saml`, `csrf`, `xsrf`, `credentials`, `session`, `sid`, and `identity`. Filtered values are replaced with `"[Filtered]"`. Custom deny-list terms extend rather than replace this list.
-
- Configure all HTTP body types, a custom cookie deny-list, a request-header allow-list, and disable URL query parameter and file path collection in an options callback:
-
- ```java
- Sentry.init(
- options -> {
- options
- .getDataCollection()
- .setHttpBodies(
- EnumSet.of(
- HttpBodyType.INCOMING_REQUEST,
- HttpBodyType.OUTGOING_REQUEST,
- HttpBodyType.INCOMING_RESPONSE,
- HttpBodyType.OUTGOING_RESPONSE));
- options
- .getDataCollection()
- .setCookies(
- KeyValueCollectionBehavior.denyList(
- "forwarded", "-ip", "remote-", "via", "-user"));
- options
- .getDataCollection()
- .getHttpHeaders()
- .setRequest(
- KeyValueCollectionBehavior.allowList("content-type", "x-request-id"));
- options
- .getDataCollection()
- .setUrlQueryParams(KeyValueCollectionBehavior.off());
- options.getDataCollection().setFilePaths(false);
- });
- ```
-
- Configure the same options in `sentry.properties`:
-
- ```properties
- data-collection.http-bodies=incoming_request,outgoing_request,incoming_response,outgoing_response
- data-collection.cookies.mode=deny_list
- data-collection.cookies.terms=forwarded,-ip,remote-,via,-user
- data-collection.http-headers.request.mode=allow_list
- data-collection.http-headers.request.terms=content-type,x-request-id
- data-collection.url-query-params.mode=off
- data-collection.file-paths=false
- ```
+### Improvements
- Configure them with Spring Boot properties:
+- Recover Android 17 `MemoryLimiter` app exits recorded as `ApplicationExitInfo.REASON_MEMORY_LIMITER` ([#6174](https://github.com/getsentry/sentry-java/pull/6174))
- ```properties
- sentry.data-collection.http-bodies=incoming-request,outgoing-request,incoming-response,outgoing-response
- sentry.data-collection.cookies.mode=deny-list
- sentry.data-collection.cookies.terms=forwarded,-ip,remote-,via,-user
- sentry.data-collection.http-headers.request.mode=allow-list
- sentry.data-collection.http-headers.request.terms=content-type,x-request-id
- sentry.data-collection.url-query-params.mode=off
- sentry.data-collection.file-paths=false
- ```
+## 8.58.0
- Configure them in `AndroidManifest.xml`:
+### Features
- ```xml
-
-
-
-
-
-
-
- ```
+- Add `LocalSentrySpan` to `sentry-compose` so apps can provide a parent `ISpan` to a composable subtree and have nested `SentryTraced` spans attach to it ([#6112]https://github.com/getsentry/sentry-java/pull/6112)
+- Add `dataCollection`, a fine-grained replacement for `sendDefaultPii`, for controlling data collected automatically by SDK integrations. See the Data Collection documentation for [Android](https://docs.sentry.io/platforms/android/configuration/options/#dataCollection), [Java](https://docs.sentry.io/platforms/java/configuration/options/#dataCollection), and [Spring Boot](https://docs.sentry.io/platforms/java/guides/spring-boot/configuration/options/#dataCollection) ([#5759](https://github.com/getsentry/sentry-java/pull/5759))
- See the [Data Collection documentation](https://docs.sentry.io/platforms/java/configuration/options/#dataCollection) for all configuration keys, supported integrations, and migration guidance.
+> [!WARNING]
+> `sendDefaultPii` will be removed in the next major SDK version. Migrate to `dataCollection` before upgrading.
### Fixes
@@ -214,6 +115,7 @@
### Fixes
- Keep dropped tombstone and ANR events dropped, instead of reporting the same app exit again at every app start ([#6002](https://github.com/getsentry/sentry-java/pull/6002))
+- Clear the persisted replay id on SDK init, so a replay id from a previous process is no longer attached to ANR events from the current one ([#6033](https://github.com/getsentry/sentry-java/pull/6033))
- Apply `Sentry.withScope` and `Sentry.withIsolationScope` data to events captured inside the callback when `globalHubMode` is enabled ([#6004](https://github.com/getsentry/sentry-java/pull/6004))
- `globalHubMode` is enabled by default on Android, where tags, extras, contexts and level set inside the callback were silently dropped
- Scopes that are explicitly made current, e.g. via `Sentry.setCurrentScopes` or the `SentryContext` coroutine integration, are now also honoured when `globalHubMode` is enabled
diff --git a/build.gradle.kts b/build.gradle.kts
index 4f7cdb7cbb2..52e17b1d309 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -117,6 +117,7 @@ subprojects {
buildUponDefaultConfig = true
allRules = true
config.setFrom("${rootProject.rootDir}/detekt.yml")
+ baseline = file("detekt-baseline.xml")
}
}
diff --git a/detekt.yml b/detekt.yml
index 49f91ae64fb..e8a01a87135 100644
--- a/detekt.yml
+++ b/detekt.yml
@@ -1,3 +1,16 @@
style:
ReturnCount:
max: 3
+ MagicNumber:
+ ignorePropertyDeclaration: true
+ UnusedPrivateMember:
+ ignoreAnnotated: ['Preview']
+naming:
+ FunctionNaming:
+ ignoreAnnotated: ['Composable']
+ TopLevelPropertyNaming:
+ # Accept both SCREAMING_CASE and Compose's PascalCase constants.
+ constantPattern: '[A-Z][_A-Za-z0-9]*'
+complexity:
+ TooManyFunctions:
+ ignoreAnnotatedFunctions: ['Preview']
diff --git a/gradle.properties b/gradle.properties
index bcd121eb063..a9cd715bc73 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,7 +16,7 @@ android.newDsl=false
android.experimental.lint.version=9.4.0
# Release information
-versionName=8.57.0
+versionName=8.58.0
# Override the SDK name on native crashes on Android
sentryAndroidSdkName=sentry.native.android
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fbe9ef0177a..bcab265e5a5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -49,6 +49,7 @@ sqldelight = "2.3.2"
# Android
targetSdk = "37"
compileSdk = "37"
+compileSdkMinor = "2"
minSdk = "21"
[plugins]
diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api
index 9a8b9d835db..efd776cabb8 100644
--- a/sentry-android-core/api/sentry-android-core.api
+++ b/sentry-android-core/api/sentry-android-core.api
@@ -435,6 +435,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun getNdkHandlerStrategy ()I
public fun getScreenshot ()Lio/sentry/android/core/SentryScreenshotOptions;
public fun getStartupCrashDurationThresholdMillis ()J
+ public fun getTombstoneMergeTimeThresholdMillis ()J
public fun isAnrEnabled ()Z
public fun isAnrProfilingEnabled ()Z
public fun isAnrReportInDebug ()Z
@@ -505,6 +506,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setReportHistoricalMemoryLimiterExits (Z)V
public fun setReportHistoricalTombstones (Z)V
public fun setTombstoneEnabled (Z)V
+ public fun setTombstoneMergeTimeThresholdMillis (J)V
}
public abstract interface class io/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback {
diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts
index 75b77752349..72a526a7a5e 100644
--- a/sentry-android-core/build.gradle.kts
+++ b/sentry-android-core/build.gradle.kts
@@ -12,6 +12,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.core"
defaultConfig {
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 b5ef861a1a4..7de5a0c2716 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
@@ -52,6 +52,8 @@ final class ManifestMetadataReader {
static final String TOMBSTONE_ENABLE = "io.sentry.tombstone.enable";
static final String TOMBSTONE_ATTACH_RAW = "io.sentry.tombstone.attach-raw";
static final String TOMBSTONE_REPORT_HISTORICAL = "io.sentry.tombstone.report-historical";
+ static final String TOMBSTONE_MERGE_TIME_THRESHOLD_MILLIS =
+ "io.sentry.tombstone.merge-time-threshold-millis";
static final String MEMORY_LIMITER_ENABLE = "io.sentry.memory-limiter.enable";
static final String MEMORY_LIMITER_REPORT_HISTORICAL =
"io.sentry.memory-limiter.report-historical";
@@ -278,6 +280,12 @@ static void applyMetadata(
logger,
TOMBSTONE_REPORT_HISTORICAL,
options.isReportHistoricalTombstones()));
+ options.setTombstoneMergeTimeThresholdMillis(
+ readLong(
+ metadata,
+ logger,
+ TOMBSTONE_MERGE_TIME_THRESHOLD_MILLIS,
+ options.getTombstoneMergeTimeThresholdMillis()));
options.setMemoryLimiterEnabled(
readBool(metadata, logger, MEMORY_LIMITER_ENABLE, options.isMemoryLimiterEnabled()));
options.setReportHistoricalMemoryLimiterExits(
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/MemoryLimiterIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/MemoryLimiterIntegration.java
index 648d8a4adac..f259250a733 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/MemoryLimiterIntegration.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/MemoryLimiterIntegration.java
@@ -190,24 +190,30 @@ private MemoryLimiterPolicy(final @NotNull SentryAndroidOptions options) {
/**
* Returns true if the provided {@code exitInfo} looks like it came from a MemoryLimiter-induced
* process death.
- *
- *
Criteria taken from here.
*/
@Override
@RequiresApi(api = Build.VERSION_CODES.R)
public boolean matches(final @NotNull ApplicationExitInfo exitInfo) {
- if (exitInfo.getReason() != ApplicationExitInfo.REASON_OTHER) {
- return false;
+ final int reason = exitInfo.getReason();
+
+ // 1. Return true for explicit matches.
+ if (reason == ApplicationExitInfo.REASON_MEMORY_LIMITER) {
+ return true;
}
- final String description = exitInfo.getDescription();
- // We match on the "MemoryLimiter:" prefix rather than the full "MemoryLimiter:AnonSwap"
- // string mentioned in the Android 17 release notes because we want to capture any future
- // MemoryLimiter kill reason without a code change. (MemoryLimiter source already tracks
- // MemoryLimiter:Memory and MemoryLimiter:Swap, but for now doesn't kill the process because
- // of them.)
- return description != null && description.contains(MEMORY_LIMITER_DESCRIPTION_PREFIX);
+ // 2. Return true for heuristic matches based on criteria discussed here:
+ // https://developer.android.com/about/versions/17/behavior-changes-all#app-memory-limits
+ if (reason == ApplicationExitInfo.REASON_OTHER) {
+ final String description = exitInfo.getDescription();
+ // We match on the "MemoryLimiter:" prefix rather than the full "MemoryLimiter:AnonSwap"
+ // string mentioned in the Android 17 release notes because we want to capture any future
+ // MemoryLimiter kill reason without a code change. (MemoryLimiter source already tracks
+ // MemoryLimiter:Memory and MemoryLimiter:Swap, but for now doesn't kill the process because
+ // of them.)
+ return description != null && description.contains(MEMORY_LIMITER_DESCRIPTION_PREFIX);
+ }
+
+ return false;
}
@Override
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java
index 2cf5acd05fa..69dfa9c16be 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java
@@ -38,8 +38,6 @@ public final class NativeEventCollector {
private static final String NATIVE_PLATFORM = "native";
- private static final long TIMESTAMP_TOLERANCE_MS = 5000;
-
private final @NotNull SentryAndroidOptions options;
/** Lightweight metadata collected during scan phase. */
@@ -175,18 +173,30 @@ public void collect() {
// Lazily collect on first use (runs on executor thread, not main thread)
collect();
+ final long thresholdMs = options.getTombstoneMergeTimeThresholdMillis();
for (final NativeEnvelopeMetadata metadata : nativeEnvelopes) {
final long timeDiff = Math.abs(tombstoneTimestampMs - metadata.getTimestampMs());
- if (timeDiff <= TIMESTAMP_TOLERANCE_MS) {
+ if (timeDiff <= thresholdMs) {
options
.getLogger()
- .log(SentryLevel.DEBUG, "Matched native event by timestamp (diff: %d ms)", timeDiff);
+ .log(
+ SentryLevel.DEBUG,
+ "Matched native event by timestamp (diff: %d ms, threshold: %d ms)",
+ timeDiff,
+ thresholdMs);
nativeEnvelopes.remove(metadata);
// Only load full event data when we have a match
return loadFullNativeEventData(metadata.getFile());
}
}
+ options
+ .getLogger()
+ .log(
+ SentryLevel.DEBUG,
+ "No native event matched the tombstone timestamp %d within %d ms.",
+ tombstoneTimestampMs,
+ thresholdMs);
return null;
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java
index 825d0cbedb9..c135172bc5c 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java
@@ -305,6 +305,13 @@ public interface BeforeCaptureCallback {
private boolean enableTombstone = false;
+ /**
+ * The maximum time difference, in milliseconds, between a tombstone from {@link
+ * ApplicationExitInfo} and a native crash event in the outbox for the two to be merged into a
+ * single event.
+ */
+ private long tombstoneMergeTimeThresholdMillis = 5000;
+
/**
* Screenshot masking options. Configure which views should be masked when capturing screenshots
* on error events.
@@ -752,6 +759,34 @@ public void setAttachAnrThreadDump(final boolean attachAnrThreadDump) {
this.attachAnrThreadDump = attachAnrThreadDump;
}
+ public long getTombstoneMergeTimeThresholdMillis() {
+ return tombstoneMergeTimeThresholdMillis;
+ }
+
+ /**
+ * Sets the maximum time difference, in milliseconds, between a tombstone from {@link
+ * ApplicationExitInfo} and a native crash event in the outbox for the two to be merged into a
+ * single event. Defaults to 5000 ms.
+ *
+ *
The two timestamps come from different sources: the tombstone timestamp is recorded by the
+ * system when the process died, the native event timestamp is recorded by the SDK signal handler.
+ * Raise the threshold when crashes are reported as separate 'signalhandler' events instead of a
+ * merged event, because the gap between the two exceeded the threshold.
+ *
+ *
Do not raise it more than necessary. The threshold is the only criterion used to pair the
+ * two, so a high value can merge a tombstone with a native crash that belongs to a different
+ * process death. The merged event then reports the wrong stack trace, and the native crash it
+ * consumed is never sent on its own.
+ *
+ *
A value of 0 merges only events with identical timestamps. A negative value disables merging
+ * completely, so tombstone and native crash are both reported as separate events.
+ *
+ * @param tombstoneMergeTimeThresholdMillis the threshold in milliseconds
+ */
+ public void setTombstoneMergeTimeThresholdMillis(final long tombstoneMergeTimeThresholdMillis) {
+ this.tombstoneMergeTimeThresholdMillis = tombstoneMergeTimeThresholdMillis;
+ }
+
public boolean isAttachRawTombstone() {
return attachRawTombstone;
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java
index 48832ca8dce..43de4034ec5 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java
@@ -241,7 +241,7 @@ public void markReported(final long timestamp) {
nativeEventCollector.findAndRemoveMatchingNativeEvent(tombstoneTimestamp);
if (matchingNativeEvent == null) {
- options.getLogger().log(SentryLevel.DEBUG, "No matching native event found for tombstone.");
+ // NativeEventCollector already logs why no event matched.
return null;
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt
index 229e22d55f2..463b5d82ddb 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt
@@ -464,6 +464,31 @@ class ManifestMetadataReaderTest {
assertEquals(false, fixture.options.isReportHistoricalTombstones)
}
+ @Test
+ fun `applyMetadata reads tombstone merge time threshold to options`() {
+ // Arrange
+ val bundle = bundleOf(ManifestMetadataReader.TOMBSTONE_MERGE_TIME_THRESHOLD_MILLIS to 10000)
+ val context = fixture.getContext(metaData = bundle)
+
+ // Act
+ ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
+
+ // Assert
+ assertEquals(10000, fixture.options.tombstoneMergeTimeThresholdMillis)
+ }
+
+ @Test
+ fun `applyMetadata reads tombstone merge time threshold to options and keeps default`() {
+ // Arrange
+ val context = fixture.getContext()
+
+ // Act
+ ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
+
+ // Assert
+ assertEquals(5000, fixture.options.tombstoneMergeTimeThresholdMillis)
+ }
+
@Test
fun `applyMetadata reads anr report historical to options`() {
// Arrange
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/MemoryLimiterIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/MemoryLimiterIntegrationTest.kt
index 183ca3dfbdd..0b72fb6e894 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/MemoryLimiterIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/MemoryLimiterIntegrationTest.kt
@@ -214,8 +214,30 @@ class MemoryLimiterIntegrationTest {
verify(fixture.options.executorService).submit(any())
}
+ // "Explicit records" are ApplicationExitInfo instances that use
+ // ApplicationExitInfo.REASON_MEMORY_LIMITER to indicate a MemoryLimiter kill. Contrast "heuristic
+ // records" below.
@Test
- fun `captures exit when reason and description match exits produced by MemoryLimiter`() {
+ fun `captures exit for explicit MemoryLimiter record`() {
+ val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
+ fixture.addAppExitInfo(
+ reason = ApplicationExitInfo.REASON_MEMORY_LIMITER,
+ description = null,
+ timestamp = newTimestamp,
+ )
+
+ integration.register(fixture.scopes, fixture.options)
+
+ verify(fixture.scopes).captureEvent(any(), anyOrNull())
+ }
+
+ // "Heuristic records" are ApplicationExitInfo instances that use something other than
+ // ApplicationExitInfo.REASON_MEMORY_LIMITER to indicate a MemoryLimiter kill.
+ // (REASON_MEMORY_LIMITER wasn't introduced until 37.2.) They consist of
+ // ApplicationExitInfo.REASON_OTHER + an ApplicationExitInfo.description that contains the prefix
+ // "MemoryLimiter:".
+ @Test
+ fun `captures exit for heuristic MemoryLimiter record`() {
val integration =
fixture.getSut(
memoryLimiterEnabled = true,
@@ -285,25 +307,34 @@ class MemoryLimiterIntegrationTest {
}
@Test
- fun `ignores exit when reason does not match exits produced by MemoryLimiter`() {
+ fun `captures exit for heuristic record even if description suffix is not AnonSwap`() {
val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
fixture.addAppExitInfo(
- reason = ApplicationExitInfo.REASON_ANR,
- description = MemoryLimiterIntegration.MEMORY_LIMITER_DESCRIPTION,
+ reason = ApplicationExitInfo.REASON_OTHER,
+ // Description suffix is "Memory" rather than "AnonSwap".
+ description = "MemoryLimiter:Memory",
timestamp = newTimestamp,
)
integration.register(fixture.scopes, fixture.options)
- verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes)
+ .captureEvent(
+ check { event ->
+ assertEquals("MemoryLimiter:Memory", event.exceptions!!.single().mechanism!!.description)
+ },
+ anyOrNull(),
+ )
}
+ // "Heuristic candidates" are ApplicationExitInfo instances that may or may not be heuristic
+ // records.
@Test
- fun `ignores exit when description does not match exits produced by MemoryLimiter`() {
+ fun `ignores exit for heuristic candidate when reason does not match`() {
val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
fixture.addAppExitInfo(
- reason = ApplicationExitInfo.REASON_OTHER,
- description = "LowSwapKiller",
+ reason = ApplicationExitInfo.REASON_ANR,
+ description = MemoryLimiterIntegration.MEMORY_LIMITER_DESCRIPTION,
timestamp = newTimestamp,
)
@@ -313,29 +344,21 @@ class MemoryLimiterIntegrationTest {
}
@Test
- fun `captures exit for any MemoryLimiter sub-reason, not just AnonSwap`() {
+ fun `ignores exit for heuristic candidate when description does not match`() {
val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
- // A future MemoryLimiter kill sub-reason (e.g. the memory or swap limits) still lives in the
- // "MemoryLimiter:" namespace and must be captured, with its raw sub-reason preserved.
fixture.addAppExitInfo(
reason = ApplicationExitInfo.REASON_OTHER,
- description = "MemoryLimiter:Memory",
+ description = "LowSwapKiller",
timestamp = newTimestamp,
)
integration.register(fixture.scopes, fixture.options)
- verify(fixture.scopes)
- .captureEvent(
- check { event ->
- assertEquals("MemoryLimiter:Memory", event.exceptions!!.single().mechanism!!.description)
- },
- anyOrNull(),
- )
+ verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
}
@Test
- fun `ignores exit when description mentions MemoryLimiter without the namespace delimiter`() {
+ fun `ignores exit for heuristic candidate when description mentions MemoryLimiter without the namespace delimiter`() {
val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
// "MemoryLimiter" without the ":" delimiter is not a MemoryLimiter kill; matching requires the
// namespace prefix so we don't over-capture unrelated REASON_OTHER exits.
@@ -351,7 +374,7 @@ class MemoryLimiterIntegrationTest {
}
@Test
- fun `ignores exit when description is null`() {
+ fun `ignores exit for heuristic candidate when description is null`() {
val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
fixture.addAppExitInfo(
reason = ApplicationExitInfo.REASON_OTHER,
@@ -481,7 +504,7 @@ class MemoryLimiterIntegrationTest {
}
@Test
- fun `dedupes MemoryLimiter exists independently of ANR exits`() {
+ fun `dedupes MemoryLimiter exits independently of ANR exits`() {
val integration =
fixture.getSut(
tmpDir,
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/NativeEventCollectorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/NativeEventCollectorTest.kt
index 243c20a2069..490847b3182 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/NativeEventCollectorTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/NativeEventCollectorTest.kt
@@ -179,6 +179,35 @@ class NativeEventCollectorTest {
assertNull(noMatch)
}
+ @Test
+ fun `does not match when the gap exceeds the default threshold`() {
+ val sut = fixture.getSut(tmpDir)
+ copyEnvelopeToOutbox("native-event.txt")
+
+ val timestamp = DateUtils.getDateTime("2023-07-15T10:30:05.800Z").time
+ assertNull(sut.findAndRemoveMatchingNativeEvent(timestamp))
+ }
+
+ @Test
+ fun `matches when the gap is within a raised threshold`() {
+ fixture.options.tombstoneMergeTimeThresholdMillis = 10000
+ val sut = fixture.getSut(tmpDir)
+ copyEnvelopeToOutbox("native-event.txt")
+
+ val timestamp = DateUtils.getDateTime("2023-07-15T10:30:05.800Z").time
+ assertNotNull(sut.findAndRemoveMatchingNativeEvent(timestamp))
+ }
+
+ @Test
+ fun `does not match when the gap exceeds a lowered threshold`() {
+ fixture.options.tombstoneMergeTimeThresholdMillis = 1000
+ val sut = fixture.getSut(tmpDir)
+ copyEnvelopeToOutbox("native-event.txt")
+
+ val timestamp = DateUtils.getDateTime("2023-07-15T10:30:02.000Z").time
+ assertNull(sut.findAndRemoveMatchingNativeEvent(timestamp))
+ }
+
private fun copyEnvelopeToOutbox(name: String): File {
val resourcePath = "envelopes/$name"
val inputStream =
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt
index 94857b91058..d580ff54cd0 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt
@@ -246,6 +246,15 @@ class SentryAndroidOptionsTest {
assertEquals(5000L, sentryOptions.ndkAppHangTimeoutIntervalMillis)
}
+ @Test
+ fun `tombstone merge time threshold defaults to 5s and is configurable`() {
+ val sentryOptions = SentryAndroidOptions()
+ assertEquals(5000L, sentryOptions.tombstoneMergeTimeThresholdMillis)
+
+ sentryOptions.tombstoneMergeTimeThresholdMillis = 10000L
+ assertEquals(10000L, sentryOptions.tombstoneMergeTimeThresholdMillis)
+ }
+
private class CustomDebugImagesLoader : IDebugImagesLoader {
override fun loadDebugImages(): List? = null
diff --git a/sentry-android-distribution/build.gradle.kts b/sentry-android-distribution/build.gradle.kts
index c699c364c3b..074d56c08e6 100644
--- a/sentry-android-distribution/build.gradle.kts
+++ b/sentry-android-distribution/build.gradle.kts
@@ -4,10 +4,12 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
id("com.android.library")
alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.detekt)
}
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.distribution"
defaultConfig { minSdk = libs.versions.minSdk.get().toInt() }
diff --git a/sentry-android-distribution/detekt-baseline.xml b/sentry-android-distribution/detekt-baseline.xml
new file mode 100644
index 00000000000..78b4ed376de
--- /dev/null
+++ b/sentry-android-distribution/detekt-baseline.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ MagicNumber:DistributionHttpClient.kt$DistributionHttpClient$200
+ MagicNumber:DistributionHttpClient.kt$DistributionHttpClient$299
+ MagicNumber:DistributionHttpClient.kt$DistributionHttpClient.HttpResponse$200
+ MagicNumber:DistributionHttpClient.kt$DistributionHttpClient.HttpResponse$299
+ MagicNumber:UpdateResponseParser.kt$UpdateResponseParser$200
+ MagicNumber:UpdateResponseParser.kt$UpdateResponseParser$400
+ MagicNumber:UpdateResponseParser.kt$UpdateResponseParser$499
+ MagicNumber:UpdateResponseParser.kt$UpdateResponseParser$500
+ MagicNumber:UpdateResponseParser.kt$UpdateResponseParser$599
+ MaxLineLength:DistributionHttpClient.kt$DistributionHttpClient$"/api/0/projects/${URLEncoder.encode(orgSlug, "UTF-8")}/${URLEncoder.encode(projectSlug, "UTF-8")}/preprodartifacts/check-for-updates/"
+ SwallowedException:DistributionIntegration.kt$DistributionIntegration$e: android.content.ActivityNotFoundException
+ TooGenericExceptionCaught:DistributionIntegration.kt$DistributionIntegration$e: Exception
+ TooGenericExceptionCaught:UpdateResponseParser.kt$UpdateResponseParser$e: Exception
+ UseCheckOrError:DistributionHttpClient.kt$DistributionHttpClient$throw IllegalStateException( "Missing required distribution configuration: orgSlug, projectSlug, or orgAuthToken" )
+ UseCheckOrError:DistributionHttpClient.kt$DistributionHttpClient$throw IllegalStateException("sentryClientName must be set")
+ UseCheckOrError:DistributionIntegration.kt$DistributionIntegration$throw IllegalStateException("buildConfiguration must be set in distribution options")
+ UseRequire:UpdateResponseParser.kt$UpdateResponseParser$throw IllegalArgumentException( "Missing required fields in API response: ${missingFields.joinToString(", ")}" )
+ WildcardImport:DistributionHttpClientTest.kt$import org.junit.Assert.*
+
+
diff --git a/sentry-android-fragment/build.gradle.kts b/sentry-android-fragment/build.gradle.kts
index 3ef1c1934f8..e613f1fd5bb 100644
--- a/sentry-android-fragment/build.gradle.kts
+++ b/sentry-android-fragment/build.gradle.kts
@@ -11,6 +11,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.fragment"
defaultConfig {
diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts
index 6f875e7a5e9..9ca0c1f6d9d 100644
--- a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts
+++ b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts
@@ -9,6 +9,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.uitest.android.critical"
signingConfigs {
diff --git a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts
index a00d76d6029..18a5b6cb7f5 100644
--- a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts
+++ b/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts
@@ -8,6 +8,7 @@ plugins {
android {
namespace = "io.sentry.uitest.android.macrobenchmark"
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
defaultConfig {
// Macrobenchmark requires API 23+.
diff --git a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts
index 52c17199e4d..85ff2b1a3c7 100644
--- a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts
+++ b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts
@@ -13,6 +13,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.uitest.android"
defaultConfig {
diff --git a/sentry-android-integration-tests/test-app-size/build.gradle.kts b/sentry-android-integration-tests/test-app-size/build.gradle.kts
index 20af6dad6ae..7bebbc6cfd0 100644
--- a/sentry-android-integration-tests/test-app-size/build.gradle.kts
+++ b/sentry-android-integration-tests/test-app-size/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
android {
namespace = "io.sentry.tests.size"
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
defaultConfig {
applicationId = "io.sentry.tests.size"
diff --git a/sentry-android-navigation/build.gradle.kts b/sentry-android-navigation/build.gradle.kts
index 6c1aa62a57d..19b9b3c753f 100644
--- a/sentry-android-navigation/build.gradle.kts
+++ b/sentry-android-navigation/build.gradle.kts
@@ -11,6 +11,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.navigation"
defaultConfig {
diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts
index 6867d964124..dff85906ca1 100644
--- a/sentry-android-ndk/build.gradle.kts
+++ b/sentry-android-ndk/build.gradle.kts
@@ -8,6 +8,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.ndk"
defaultConfig {
diff --git a/sentry-android-replay/build.gradle.kts b/sentry-android-replay/build.gradle.kts
index 6d03ba771b0..1318f7767c9 100644
--- a/sentry-android-replay/build.gradle.kts
+++ b/sentry-android-replay/build.gradle.kts
@@ -8,12 +8,12 @@ plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.gradle.versions)
- // TODO: enable it later
- // alias(libs.plugins.detekt)
+ alias(libs.plugins.detekt)
}
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.replay"
defaultConfig {
diff --git a/sentry-android-replay/detekt-baseline.xml b/sentry-android-replay/detekt-baseline.xml
new file mode 100644
index 00000000000..54c6754e2d8
--- /dev/null
+++ b/sentry-android-replay/detekt-baseline.xml
@@ -0,0 +1,165 @@
+
+
+
+
+ ComplexCondition:ReplayCache.kt$ReplayCache.Companion$height == null || width == null || frameRate == null || bitRate == null || (segmentId == null || segmentId == -1) || segmentTimestamp == null || replayType == null
+ ComplexCondition:ReplayIntegration.kt$ReplayIntegration$isManualPause || lastKnownConnectionStatus == DISCONNECTED || scopes?.rateLimiter?.isActiveForCategory(All) == true || scopes?.rateLimiter?.isActiveForCategory(Replay) == true
+ ComplexCondition:ReplayIntegration.kt$ReplayIntegration$name.startsWith("replay_") && !name.contains(replayId.toString()) && !(unfinishedReplayId.isNotBlank() && name.contains(unfinishedReplayId))
+ ComplexCondition:ReplayIntegration.kt$ReplayIntegration$state.get().captureStrategy is SessionCaptureStrategy && (lastKnownConnectionStatus == DISCONNECTED || scopes?.rateLimiter?.isActiveForCategory(All) == true || scopes?.rateLimiter?.isActiveForCategory(Replay) == true)
+ ComplexCondition:ScreenshotRecorder.kt$ScreenshotRecorder$root == null || root.width <= 0 || root.height <= 0 || !root.isShown
+ ComplexCondition:SimpleVideoEncoder.kt$SimpleVideoEncoder$Build.MANUFACTURER.contains("xiaomi", ignoreCase = true) || Build.MANUFACTURER.contains("motorola", ignoreCase = true) || SystemProperties.get(SystemProperties.Property.SOC_MANUFACTURER) .equals("spreadtrum", ignoreCase = true) || SystemProperties.get(SystemProperties.Property.SOC_MANUFACTURER) .equals("unisoc", ignoreCase = true)
+ CyclomaticComplexMethod:CaptureStrategy.kt$CaptureStrategy.Companion$@Suppress("NewApi") private fun buildReplay( options: SentryOptions, video: File, currentReplayId: SentryId, segmentTimestamp: Date, segmentId: Int, height: Int, width: Int, frameCount: Int, frameRate: Int, videoDuration: Long, replayType: ReplayType, screenAtStart: String?, breadcrumbs: List<Breadcrumb>, events: Deque<RRWebEvent>, traceIds: List<String>, segmentNames: List<String>, ): ReplaySegment
+ CyclomaticComplexMethod:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$private fun fromComposeNode( node: LayoutNode, parent: ViewHierarchyNode?, distance: Int, isComposeRoot: Boolean, options: SentryMaskingOptions, logger: ILogger, ): ViewHierarchyNode?
+ CyclomaticComplexMethod:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$override fun convert(breadcrumb: Breadcrumb): RRWebEvent?
+ CyclomaticComplexMethod:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$private fun Breadcrumb.toRRWebSpanEvent(): RRWebSpanEvent
+ CyclomaticComplexMethod:ReplayCache.kt$ReplayCache$public fun createVideoOf( duration: Long, from: Long, segmentId: Int, height: Int, width: Int, frameRate: Int, bitRate: Int, videoFile: File = File(replayCacheDir, "$segmentId.mp4"), ): GeneratedVideo?
+ CyclomaticComplexMethod:ReplayCache.kt$ReplayCache.Companion$internal fun fromDisk( options: SentryOptions, replayId: SentryId, replayCacheProvider: ((replayId: SentryId) -> ReplayCache)? = null, ): LastSegmentData?
+ CyclomaticComplexMethod:ReplayGestureConverter.kt$ReplayGestureConverter$fun convert( event: MotionEvent, recorderConfig: ScreenshotRecorderConfig, ): List<RRWebIncrementalSnapshotEvent>?
+ CyclomaticComplexMethod:ReplayLifecycle.kt$internal fun ReplayLifecycleState.isAllowed(newState: ReplayLifecycleState): Boolean
+ CyclomaticComplexMethod:SimpleVideoEncoder.kt$SimpleVideoEncoder$private fun drainCodec(endOfStream: Boolean)
+ EmptyCatchBlock:ReplaySmokeTest.kt$ReplaySmokeTest${}
+ EmptyDefaultConstructor:ScreenshotRecorderTest.kt$ScreenshotRecorderTest.Fixture$()
+ EmptyFunctionBlock:ReplayExecutorService.kt$CompletedFuture${}
+ ForbiddenComment:BufferCaptureStrategy.kt$BufferCaptureStrategy$// TODO: can be a single while-loop
+ ForbiddenComment:BufferCaptureStrategy.kt$BufferCaptureStrategy$// TODO: capture envelopes for buffered segments instead, but don't send them until buffer is
+ ForbiddenComment:CaptureStrategy.kt$CaptureStrategy.Companion$// TODO: support non-fullscreen windows later
+ ForbiddenComment:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$// TODO: if we get reports that it's slow, we can drop this, and just mask
+ ForbiddenComment:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$// TODO: support editable text (currently there's a way to get @Composable's padding only
+ ForbiddenComment:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$// TODO: the whole view instead of per-line
+ ForbiddenComment:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$// TODO: this currently does not support embedded AndroidViews, we'd have to
+ ForbiddenComment:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$// TODO: traverse the ViewHierarchyNode here again. For now we can recommend
+ ForbiddenComment:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$// TODO: using custom modifiers to obscure the entire node if it's sensitive
+ ForbiddenComment:Nodes.kt$// TODO: probably most of the below we can do via bytecode instrumentation and speed up at runtime
+ ForbiddenComment:Persistable.kt$PersistableLinkedList$// TODO: enable this back after we are able to serialize individual touches to disk to not overload
+ ForbiddenComment:PixelCopyStrategy.kt$PixelCopyStrategy$// TODO: disableAllMasking here and dont traverse?
+ ForbiddenComment:ReplayCache.kt$ReplayCache$// TODO: it's awful, choose a better serialization format
+ ForbiddenComment:ReplayIntegration.kt$ReplayIntegration$// TODO: previous run and set them directly to the ReplayEvent so they don't get overwritten in
+ ForbiddenComment:ReplayIntegration.kt$ReplayIntegration$// TODO: read persisted options/scope values form the
+ ForbiddenComment:ReplaySmokeTest.kt$ReplaySmokeTest$// TODO: figure out why there's more than 10
+ ForbiddenComment:SimpleVideoEncoder.kt$SimpleVideoEncoder$// TODO: because the screenshot config has to match the video config
+ ForbiddenComment:SimpleVideoEncoder.kt$SimpleVideoEncoder$// TODO: if this ever becomes a problem, move this to ScreenshotRecorderConfig.from()
+ ForbiddenComment:SimpleVideoEncoder.kt$SimpleVideoEncoder$// TODO: maybe enable this back later, for now variable bitrate seems to provide much better
+ ForbiddenComment:SimpleVideoEncoder.kt$SimpleVideoEncoder$// TODO: quality with almost no overhead in terms of video size, let's monitor that
+ ForbiddenComment:Views.kt$// TODO: maybe find a way how to check if the drawable is coming from the apk or loaded from
+ ForbiddenComment:Views.kt$// TODO: otherwise maybe check for the bitmap size and don't mask those that take a lot of height
+ FunctionParameterNaming:PixelCopyStrategyTest.kt$DeferredWindowPixelCopyShadow.Companion$_dest: Bitmap
+ FunctionParameterNaming:PixelCopyStrategyTest.kt$DeferredWindowPixelCopyShadow.Companion$_source: Window
+ LargeClass:CanvasStrategy.kt$TextIgnoringDelegateCanvas : Canvas
+ LargeClass:ReplayIntegrationTest.kt$ReplayIntegrationTest
+ LongMethod:CaptureStrategy.kt$CaptureStrategy.Companion$@Suppress("NewApi") private fun buildReplay( options: SentryOptions, video: File, currentReplayId: SentryId, segmentTimestamp: Date, segmentId: Int, height: Int, width: Int, frameCount: Int, frameRate: Int, videoDuration: Long, replayType: ReplayType, screenAtStart: String?, breadcrumbs: List<Breadcrumb>, events: Deque<RRWebEvent>, traceIds: List<String>, segmentNames: List<String>, ): ReplaySegment
+ LongMethod:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$private fun fromComposeNode( node: LayoutNode, parent: ViewHierarchyNode?, distance: Int, isComposeRoot: Boolean, options: SentryMaskingOptions, logger: ILogger, ): ViewHierarchyNode?
+ LongMethod:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$override fun convert(breadcrumb: Breadcrumb): RRWebEvent?
+ LongMethod:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$private fun Breadcrumb.toRRWebSpanEvent(): RRWebSpanEvent
+ LongMethod:PixelCopyStrategy.kt$PixelCopyStrategy$@SuppressLint("NewApi") override fun capture(root: View)
+ LongMethod:ReplayCache.kt$ReplayCache$public fun createVideoOf( duration: Long, from: Long, segmentId: Int, height: Int, width: Int, frameRate: Int, bitRate: Int, videoFile: File = File(replayCacheDir, "$segmentId.mp4"), ): GeneratedVideo?
+ LongMethod:ReplayCache.kt$ReplayCache.Companion$internal fun fromDisk( options: SentryOptions, replayId: SentryId, replayCacheProvider: ((replayId: SentryId) -> ReplayCache)? = null, ): LastSegmentData?
+ LongMethod:ReplayGestureConverter.kt$ReplayGestureConverter$fun convert( event: MotionEvent, recorderConfig: ScreenshotRecorderConfig, ): List<RRWebIncrementalSnapshotEvent>?
+ LongMethod:ReplayGestureConverterTest.kt$ReplayGestureConverterTest$@Test fun `convert multi-pointer events`()
+ LongMethod:ReplayIntegrationTest.kt$ReplayIntegrationTest$@Test fun `register finalizes previous replay`()
+ LongMethod:ReplayIntegrationWithRecorderTest.kt$ReplayIntegrationWithRecorderTest$@Test fun `works with different recorder`()
+ LongMethod:SimpleVideoEncoder.kt$SimpleVideoEncoder$private fun drainCodec(endOfStream: Boolean)
+ LongMethod:ViewHierarchyNode.kt$ViewHierarchyNode.Companion$@JvmStatic fun fromView( view: View, parent: ViewHierarchyNode?, distance: Int, options: SentryMaskingOptions, ): ViewHierarchyNode
+ LongParameterList:BaseCaptureStrategy.kt$BaseCaptureStrategy$( duration: Long, currentSegmentTimestamp: Date, replayId: SentryId, segmentId: Int, height: Int, width: Int, frameRate: Int, bitRate: Int, replayType: ReplayType = this.replayType, cache: ReplayCache? = this.cache, screenAtStart: String? = this.screenAtStart, breadcrumbs: List<Breadcrumb>? = null, events: Deque<RRWebEvent> = this.currentEvents, )
+ LongParameterList:CaptureStrategy.kt$CaptureStrategy.Companion$( options: SentryOptions, video: File, currentReplayId: SentryId, segmentTimestamp: Date, segmentId: Int, height: Int, width: Int, frameCount: Int, frameRate: Int, videoDuration: Long, replayType: ReplayType, screenAtStart: String?, breadcrumbs: List<Breadcrumb>, events: Deque<RRWebEvent>, traceIds: List<String>, segmentNames: List<String>, )
+ LongParameterList:CaptureStrategy.kt$CaptureStrategy.Companion$( scopes: IScopes?, options: SentryOptions, duration: Long, currentSegmentTimestamp: Date, replayId: SentryId, segmentId: Int, height: Int, width: Int, replayType: ReplayType, cache: ReplayCache?, frameRate: Int, bitRate: Int, screenAtStart: String?, breadcrumbs: List<Breadcrumb>?, events: Deque<RRWebEvent>, traceIds: List<String> = emptyList(), segmentNames: List<String> = emptyList(), )
+ LongParameterList:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$( node: LayoutNode, parent: ViewHierarchyNode?, distance: Int, isComposeRoot: Boolean, options: SentryMaskingOptions, logger: ILogger, )
+ LongParameterList:PixelCopyStrategy.kt$( destCanvas: Canvas, destPaint: Paint, tmpSrc: Rect, tmpDst: RectF, sourceBitmap: Bitmap, sourceX: Int, sourceY: Int, windowX: Int, windowY: Int, scaleFactorX: Float, scaleFactorY: Float, )
+ LongParameterList:PixelCopyStrategy.kt$PixelCopyStrategy$( root: View, captures: Array<SurfaceViewCapture?>, viewHierarchy: ViewHierarchyNode, windowX: Int, windowY: Int, resetUnstableCaptures: Boolean, )
+ LongParameterList:ReplayCache.kt$ReplayCache$( duration: Long, from: Long, segmentId: Int, height: Int, width: Int, frameRate: Int, bitRate: Int, videoFile: File = File(replayCacheDir, "$segmentId.mp4"), )
+ LongParameterList:ReplayIntegration.kt$ReplayIntegration$( context: Context, dateProvider: ICurrentDateProvider, recorderProvider: (() -> Recorder)?, replayCacheProvider: ((replayId: SentryId) -> ReplayCache)?, replayCaptureStrategyProvider: ((isFullSession: Boolean) -> CaptureStrategy)? = null, mainLooperHandler: MainLooperHandler? = null, gestureRecorderProvider: (() -> GestureRecorder)? = null, )
+ LongParameterList:ReplayIntegrationTest.kt$ReplayIntegrationTest.Fixture$( context: Context, sessionSampleRate: Double = 1.0, onErrorSampleRate: Double = 1.0, isRateLimited: Boolean = false, recorderProvider: (() -> Recorder)? = null, replayCaptureStrategyProvider: ((isFullSession: Boolean) -> CaptureStrategy)? = null, gestureRecorderProvider: (() -> GestureRecorder)? = null, dateProvider: ICurrentDateProvider = CurrentDateProvider.getInstance(), mainLooperHandler: MainLooperHandler = mock { doAnswer { (it.arguments[0] as Runnable).run() true } .whenever(mock) .post(any()) }, )
+ LongParameterList:ViewHierarchyNode.kt$ViewHierarchyNode$( val x: Float, val y: Float, val width: Int, val height: Int, // Elevation (in px) val elevation: Float, // Distance to the parent (index) val distance: Int, val parent: ViewHierarchyNode? = null, val shouldMask: Boolean = false, // Whether the node is important for content capture (=non-empty container) var isImportantForContentCapture: Boolean = false, val isVisible: Boolean = false, val visibleRect: Rect? = null, )
+ LongParameterList:ViewHierarchyNode.kt$ViewHierarchyNode.GenericViewHierarchyNode$( x: Float, y: Float, width: Int, height: Int, elevation: Float, distance: Int, parent: ViewHierarchyNode? = null, shouldMask: Boolean = false, isImportantForContentCapture: Boolean = false, isVisible: Boolean = false, visibleRect: Rect? = null, )
+ LongParameterList:ViewHierarchyNode.kt$ViewHierarchyNode.ImageViewHierarchyNode$( x: Float, y: Float, width: Int, height: Int, elevation: Float, distance: Int, parent: ViewHierarchyNode? = null, shouldMask: Boolean = false, isImportantForContentCapture: Boolean = false, isVisible: Boolean = false, visibleRect: Rect? = null, )
+ LongParameterList:ViewHierarchyNode.kt$ViewHierarchyNode.SurfaceViewHierarchyNode$( val surfaceViewRef: WeakReference<SurfaceView>, x: Float, y: Float, width: Int, height: Int, elevation: Float, distance: Int, parent: ViewHierarchyNode? = null, shouldMask: Boolean = false, isImportantForContentCapture: Boolean = false, isVisible: Boolean = false, visibleRect: Rect? = null, )
+ LongParameterList:ViewHierarchyNode.kt$ViewHierarchyNode.TextViewHierarchyNode$( val layout: TextLayout? = null, val dominantColor: Int? = null, val paddingLeft: Int = 0, val paddingTop: Int = 0, x: Float, y: Float, width: Int, height: Int, elevation: Float, distance: Int, parent: ViewHierarchyNode? = null, shouldMask: Boolean = false, isImportantForContentCapture: Boolean = false, isVisible: Boolean = false, visibleRect: Rect? = null, )
+ LoopWithTooManyJumpStatements:PixelCopyStrategy.kt$PixelCopyStrategy$for
+ LoopWithTooManyJumpStatements:ReplayCache.kt$ReplayCache$while
+ LoopWithTooManyJumpStatements:SimpleVideoEncoder.kt$SimpleVideoEncoder$while
+ MagicNumber:BaseCaptureStrategy.kt$BaseCaptureStrategy$26
+ MagicNumber:BufferCaptureStrategy.kt$BufferCaptureStrategy$26
+ MagicNumber:CanvasStrategy.kt$TextIgnoringDelegateCanvas$100
+ MagicNumber:CanvasStrategy.kt$TextIgnoringDelegateCanvas$10f
+ MagicNumber:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$26
+ MagicNumber:DebugOverlayDrawable.kt$DebugOverlayDrawable$6f
+ MagicNumber:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$1000.0
+ MagicNumber:Persistable.kt$PersistableLinkedList$26
+ MagicNumber:ReplayCache.kt$ReplayCache$1000
+ MagicNumber:ReplayCache.kt$ReplayCache.Companion$1000
+ MagicNumber:ReplayGestureConverter.kt$ReplayGestureConverter$10
+ MagicNumber:ScreenshotRecorder.kt$ScreenshotRecorder$26
+ MagicNumber:ScreenshotRecorder.kt$ScreenshotRecorderConfig.Companion$16
+ MagicNumber:ScreenshotRecorder.kt$ScreenshotRecorderConfig.Companion$8
+ MagicNumber:SimpleVideoEncoder.kt$MuxerConfig$24
+ MagicNumber:SimpleVideoEncoder.kt$SimpleVideoEncoder$26
+ MagicNumber:SimpleVideoEncoder.kt$SimpleVideoEncoder$6
+ MagicNumber:ViewHierarchyNode.kt$ViewHierarchyNode$26
+ MagicNumber:Views.kt$10
+ MagicNumber:Views.kt$21
+ MagicNumber:WindowRecorder.kt$WindowRecorder$100L
+ MagicNumber:WindowRecorder.kt$WindowRecorder$26
+ MagicNumber:WindowRecorder.kt$WindowRecorder.Capturer$1000L
+ MatchingDeclarationName:ModifierExtensions.kt$SentryReplayModifiers
+ MatchingDeclarationName:Nodes.kt$ComposeTextLayout : TextLayout
+ MatchingDeclarationName:Persistable.kt$PersistableLinkedList : ConcurrentLinkedDeque
+ MatchingDeclarationName:ReplayLifecycle.kt$ReplayLifecycleState
+ MaxLineLength:ComposeMaskingOptionsTest.kt$ComposeMaskingOptionsTest$"Flaky: Robolectric intermittently reports zero bounds for nodes, causing isVisible=false and making the assertion non-deterministic"
+ NestedBlockDepth:CaptureStrategy.kt$CaptureStrategy.Companion$@Suppress("NewApi") private fun buildReplay( options: SentryOptions, video: File, currentReplayId: SentryId, segmentTimestamp: Date, segmentId: Int, height: Int, width: Int, frameCount: Int, frameRate: Int, videoDuration: Long, replayType: ReplayType, screenAtStart: String?, breadcrumbs: List<Breadcrumb>, events: Deque<RRWebEvent>, traceIds: List<String>, segmentNames: List<String>, ): ReplaySegment
+ NestedBlockDepth:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$private fun Breadcrumb.toRRWebSpanEvent(): RRWebSpanEvent
+ NestedBlockDepth:Nodes.kt$internal fun LayoutNode.findPainter(): Painter?
+ NestedBlockDepth:Nodes.kt$internal fun LayoutNode.findTextColor(): Color?
+ NestedBlockDepth:ReplayGestureConverter.kt$ReplayGestureConverter$fun convert( event: MotionEvent, recorderConfig: ScreenshotRecorderConfig, ): List<RRWebIncrementalSnapshotEvent>?
+ NestedBlockDepth:SimpleVideoEncoder.kt$SimpleVideoEncoder$private fun drainCodec(endOfStream: Boolean)
+ ReturnCount:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$fun fromView( view: View, parent: ViewHierarchyNode?, options: SentryMaskingOptions, logger: ILogger, ): Boolean
+ ReturnCount:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$private fun SemanticsConfiguration?.shouldMask( isImage: Boolean, options: SentryMaskingOptions, ): Boolean
+ ReturnCount:DefaultReplayBreadcrumbConverter.kt$DefaultReplayBreadcrumbConverter$override fun convert(breadcrumb: Breadcrumb): RRWebEvent?
+ ReturnCount:ReplayCache.kt$ReplayCache.Companion$internal fun fromDisk( options: SentryOptions, replayId: SentryId, replayCacheProvider: ((replayId: SentryId) -> ReplayCache)? = null, ): LastSegmentData?
+ ReturnCount:ReplayGestureConverter.kt$ReplayGestureConverter$fun convert( event: MotionEvent, recorderConfig: ScreenshotRecorderConfig, ): List<RRWebIncrementalSnapshotEvent>?
+ ReturnCount:ReplayIntegration.kt$ReplayIntegration$override fun captureReplay(isTerminating: Boolean?): SentryId
+ ReturnCount:ScreenshotRecorder.kt$ScreenshotRecorder$fun capture()
+ ReturnCount:SentryLayoutNodeHelper.kt$SentryLayoutNodeHelper$@Suppress("UNCHECKED_CAST") fun getChildren(node: LayoutNode): List<LayoutNode>
+ ReturnCount:SentryLayoutNodeHelper.kt$SentryLayoutNodeHelper$fun isTransparent(node: LayoutNode): Boolean
+ ReturnCount:ViewHierarchyNode.kt$ViewHierarchyNode.Companion$@JvmStatic fun fromView( view: View, parent: ViewHierarchyNode?, distance: Int, options: SentryMaskingOptions, ): ViewHierarchyNode
+ ReturnCount:ViewHierarchyNode.kt$ViewHierarchyNode.Companion$private fun View.shouldMask(options: SentryMaskingOptions): Boolean
+ ReturnCount:Views.kt$internal fun View.isVisibleToUser(): Pair<Boolean, Rect?>
+ SwallowedException:Nodes.kt$e: Throwable
+ SwallowedException:ReplayCache.kt$ReplayCache.Companion$e: Throwable
+ SwallowedException:ReplaySmokeTest.kt$ReplaySmokeTest$e: ConditionTimeoutException
+ SwallowedException:Views.kt$e: NullPointerException
+ ThrowsCount:SimpleVideoEncoder.kt$SimpleVideoEncoder$private fun drainCodec(endOfStream: Boolean)
+ TooGenericExceptionCaught:BaseCaptureStrategy.kt$BaseCaptureStrategy.<no name provided>$e: Throwable
+ TooGenericExceptionCaught:BufferCaptureStrategy.kt$BufferCaptureStrategy$e: Throwable
+ TooGenericExceptionCaught:CanvasStrategy.kt$CanvasStrategy$t: Throwable
+ TooGenericExceptionCaught:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$e: Throwable
+ TooGenericExceptionCaught:ComposeViewHierarchyNode.kt$ComposeViewHierarchyNode$t: Throwable
+ TooGenericExceptionCaught:Executors.kt$e: Throwable
+ TooGenericExceptionCaught:GestureRecorder.kt$GestureRecorder.SentryReplayGestureRecorder$e: Throwable
+ TooGenericExceptionCaught:Nodes.kt$e: Throwable
+ TooGenericExceptionCaught:PixelCopyStrategy.kt$PixelCopyStrategy$e: RuntimeException
+ TooGenericExceptionCaught:PixelCopyStrategy.kt$PixelCopyStrategy$e: Throwable
+ TooGenericExceptionCaught:ReplayCache.kt$ReplayCache$e: Throwable
+ TooGenericExceptionCaught:ReplayCache.kt$ReplayCache$t: Throwable
+ TooGenericExceptionCaught:ReplayCache.kt$ReplayCache.Companion$e: Throwable
+ TooGenericExceptionCaught:ReplayExecutorService.kt$ReplayExecutorService$e: Throwable
+ TooGenericExceptionCaught:ReplayIntegration.kt$ReplayIntegration$e: Throwable
+ TooGenericExceptionCaught:ScreenshotRecorder.kt$ScreenshotRecorder$e: Throwable
+ TooGenericExceptionCaught:SimpleVideoEncoder.kt$SimpleVideoEncoder$e: RuntimeException
+ TooGenericExceptionCaught:SimpleVideoEncoder.kt$SimpleVideoEncoder$e: Throwable
+ TooGenericExceptionCaught:Views.kt$e: NullPointerException
+ TooGenericExceptionCaught:WindowRecorder.kt$WindowRecorder.Capturer$e: Throwable
+ TooGenericExceptionThrown:ReplayIntegrationTest.kt$ReplayIntegrationTest$throw RuntimeException("test")
+ TooGenericExceptionThrown:SimpleVideoEncoder.kt$SimpleVideoEncoder$throw RuntimeException("encoderOutputBuffer $encoderStatus was null")
+ TooGenericExceptionThrown:SimpleVideoEncoder.kt$SimpleVideoEncoder$throw RuntimeException("format changed twice")
+ TooGenericExceptionThrown:SimpleVideoEncoder.kt$SimpleVideoEncoder$throw RuntimeException("muxer hasn't started")
+ TooManyFunctions:BaseCaptureStrategy.kt$BaseCaptureStrategy : CaptureStrategy
+ TooManyFunctions:BufferCaptureStrategy.kt$BufferCaptureStrategy : BaseCaptureStrategy
+ TooManyFunctions:CanvasStrategy.kt$TextIgnoringDelegateCanvas : Canvas
+ TooManyFunctions:CaptureStrategy.kt$CaptureStrategy
+ TooManyFunctions:PixelCopyStrategy.kt$PixelCopyStrategy : ScreenshotStrategy
+ TooManyFunctions:ReplayIntegration.kt$ReplayIntegration : IntegrationCloseableScreenshotRecorderCallbackTouchRecorderCallbackReplayControllerIConnectionStatusObserverIRateLimitObserverWindowCallback
+ TooManyFunctions:WindowRecorder.kt$WindowRecorder : RecorderOnRootViewsChangedListenerExecutorProvider
+ UnusedPrivateProperty:DebugOverlayDrawable.kt$DebugOverlayDrawable$private val padding = 6f
+ UseCheckOrError:ReplayShadowMediaCodec.kt$ReplayShadowMediaCodec$throw IllegalStateException("Simulated codec start failure")
+ UtilityClassWithPublicConstructor:PixelCopyStrategyTest.kt$DeferredWindowPixelCopyShadow
+
+
diff --git a/sentry-android-sqlite/build.gradle.kts b/sentry-android-sqlite/build.gradle.kts
index 9637b91546a..fd15526fcbe 100644
--- a/sentry-android-sqlite/build.gradle.kts
+++ b/sentry-android-sqlite/build.gradle.kts
@@ -11,6 +11,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.sqlite"
defaultConfig {
diff --git a/sentry-android-timber/build.gradle.kts b/sentry-android-timber/build.gradle.kts
index 3c8ac1ea1e4..f66c1a8a96b 100644
--- a/sentry-android-timber/build.gradle.kts
+++ b/sentry-android-timber/build.gradle.kts
@@ -11,6 +11,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android.timber"
defaultConfig {
diff --git a/sentry-android/build.gradle.kts b/sentry-android/build.gradle.kts
index a2d92829b29..4c493e9e2d2 100644
--- a/sentry-android/build.gradle.kts
+++ b/sentry-android/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.android"
defaultConfig { minSdk = libs.versions.minSdk.get().toInt() }
diff --git a/sentry-apollo-3/build.gradle.kts b/sentry-apollo-3/build.gradle.kts
index 70f43d946ef..357d5224495 100644
--- a/sentry-apollo-3/build.gradle.kts
+++ b/sentry-apollo-3/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.detekt)
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.buildconfig)
diff --git a/sentry-apollo-3/detekt-baseline.xml b/sentry-apollo-3/detekt-baseline.xml
new file mode 100644
index 00000000000..6bb8edffb36
--- /dev/null
+++ b/sentry-apollo-3/detekt-baseline.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ ClassNaming:LaunchDetailsQuery_ResponseAdapter.kt$LaunchDetailsQuery_ResponseAdapter
+ ClassNaming:LaunchDetailsQuery_VariablesAdapter.kt$LaunchDetailsQuery_VariablesAdapter : Adapter
+ CyclomaticComplexMethod:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ LongMethod:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ LongParameterList:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$( span: ISpan?, request: HttpRequest, response: HttpResponse?, statusCode: Int?, operationName: String?, operationType: String?, operationId: String?, )
+ LongParameterList:SentryApollo3InterceptorClientErrors.kt$SentryApollo3InterceptorClientErrors.Fixture$( captureFailedRequests: Boolean = DEFAULT_CAPTURE_FAILED_REQUESTS, failedRequestTargets: List<String> = listOf(DEFAULT_PROPAGATION_TARGETS), httpStatusCode: Int = 200, responseBody: String = responseBodyOk, sendDefaultPii: Boolean = false, includeCookies: Boolean = sendDefaultPii, socketPolicy: SocketPolicy = SocketPolicy.KEEP_OPEN, configureOptions: SentryOptions.() -> Unit = {}, )
+ MaxLineLength:LaunchDetailsQuery.kt$LaunchDetailsQuery.Companion$"query LaunchDetails(${'$'}id: ID!) { launch(id: ${'$'}id) { id site mission { name missionPatch(size: LARGE) } rocket { name type } } }"
+ MaxLineLength:SentryApollo3InterceptorTest.kt$SentryApollo3InterceptorTest$"thirdPartyBaggage=someValue,secondThirdPartyBaggage=secondValue; property;propertyKey=propertyValue,anotherThirdPartyBaggage=anotherValue"
+ MaxLineLength:SentryApollo3InterceptorTest.kt$SentryApollo3InterceptorTest.Fixture$"secondThirdPartyBaggage=secondValue; property;propertyKey=propertyValue,anotherThirdPartyBaggage=anotherValue"
+ NestedBlockDepth:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ NestedBlockDepth:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$private fun finish( span: ISpan?, request: HttpRequest, response: HttpResponse?, statusCode: Int?, operationName: String?, operationType: String?, operationId: String?, )
+ NestedBlockDepth:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$private fun maybeAddTracingHeaders( scopes: IScopes, request: HttpRequest, span: ISpan?, ): HttpRequest
+ ReturnCount:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ SwallowedException:SentryApollo3InterceptorClientErrors.kt$SentryApollo3InterceptorClientErrors$e: ApolloException
+ SwallowedException:SentryApollo3InterceptorTest.kt$SentryApollo3InterceptorTest$e: ApolloException
+ SwallowedException:SentryApollo3InterceptorWithVariablesTest.kt$SentryApollo3InterceptorWithVariablesTest$e: ApolloException
+ TooGenericExceptionCaught:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor$e: Throwable
+ TooGenericExceptionThrown:SentryApollo3InterceptorTest.kt$SentryApollo3InterceptorTest$throw RuntimeException()
+ TooManyFunctions:SentryApollo3HttpInterceptor.kt$SentryApollo3HttpInterceptor : HttpInterceptor
+ UseCheckOrError:LaunchDetailsQuery_VariablesAdapter.kt$LaunchDetailsQuery_VariablesAdapter$throw IllegalStateException("Input type used in output position")
+ UtilityClassWithPublicConstructor:GraphQLBoolean.kt$GraphQLBoolean
+ UtilityClassWithPublicConstructor:GraphQLID.kt$GraphQLID
+ UtilityClassWithPublicConstructor:GraphQLString.kt$GraphQLString
+ UtilityClassWithPublicConstructor:Launch.kt$Launch
+ UtilityClassWithPublicConstructor:Mission.kt$Mission
+ UtilityClassWithPublicConstructor:Query.kt$Query
+ UtilityClassWithPublicConstructor:Rocket.kt$Rocket
+
+
diff --git a/sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt b/sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt
index 94ba52cb592..7aa8d693943 100644
--- a/sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt
+++ b/sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt
@@ -301,6 +301,7 @@ constructor(
return getHeaders(headers)
}
+ @Suppress("DEPRECATION")
private fun getHeaders(headers: List): MutableMap? {
// Headers are only sent if isSendDefaultPii is enabled due to PII
if (!scopes.options.isSendDefaultPii) {
diff --git a/sentry-apollo-4/build.gradle.kts b/sentry-apollo-4/build.gradle.kts
index 4f1276f0bf4..078d26b8ff8 100644
--- a/sentry-apollo-4/build.gradle.kts
+++ b/sentry-apollo-4/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.detekt)
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.buildconfig)
diff --git a/sentry-apollo-4/detekt-baseline.xml b/sentry-apollo-4/detekt-baseline.xml
new file mode 100644
index 00000000000..371c726cb9f
--- /dev/null
+++ b/sentry-apollo-4/detekt-baseline.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ ClassNaming:LaunchDetailsQuery_ResponseAdapter.kt$LaunchDetailsQuery_ResponseAdapter
+ ClassNaming:LaunchDetailsQuery_VariablesAdapter.kt$LaunchDetailsQuery_VariablesAdapter : Adapter
+ CyclomaticComplexMethod:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ LongMethod:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ LongParameterList:SentryApollo4BuilderExtensionsClientErrorsTest.kt$SentryApollo4BuilderExtensionsClientErrorsTest.Fixture$( captureFailedRequests: Boolean = DEFAULT_CAPTURE_FAILED_REQUESTS, failedRequestTargets: List<String> = listOf(DEFAULT_PROPAGATION_TARGETS), httpStatusCode: Int = 200, responseBody: String = responseBodyOk, sendDefaultPii: Boolean = false, includeCookies: Boolean = sendDefaultPii, socketPolicy: SocketPolicy = SocketPolicy.KEEP_OPEN, configureOptions: SentryOptions.() -> Unit = {}, )
+ LongParameterList:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$( span: ISpan?, request: HttpRequest, response: HttpResponse?, statusCode: Int?, operationName: String?, operationType: String?, operationId: String?, )
+ MaxLineLength:LaunchDetailsQuery.kt$LaunchDetailsQuery.Companion$"query LaunchDetails(${'$'}id: ID!) { launch(id: ${'$'}id) { id site mission { name missionPatch(size: LARGE) } rocket { name type } } }"
+ MaxLineLength:LaunchDetailsQuerySelections.kt$LaunchDetailsQuerySelections$CompiledArgument.Builder(CompiledArgumentDefinition.Builder("size").build()).value("LARGE").build()
+ MaxLineLength:SentryApollo4HttpInterceptorTest.kt$SentryApollo4HttpInterceptorTest$"thirdPartyBaggage=someValue,secondThirdPartyBaggage=secondValue; property;propertyKey=propertyValue,anotherThirdPartyBaggage=anotherValue"
+ MaxLineLength:SentryApollo4HttpInterceptorTest.kt$SentryApollo4HttpInterceptorTest.Fixture$"secondThirdPartyBaggage=secondValue; property;propertyKey=propertyValue,anotherThirdPartyBaggage=anotherValue"
+ NestedBlockDepth:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ NestedBlockDepth:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$private fun finish( span: ISpan?, request: HttpRequest, response: HttpResponse?, statusCode: Int?, operationName: String?, operationType: String?, operationId: String?, )
+ NestedBlockDepth:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$private fun maybeAddTracingHeaders( scopes: IScopes, request: HttpRequest, span: ISpan?, ): HttpRequest
+ ReturnCount:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$private fun captureEvent( request: HttpRequest, response: HttpResponse, operationName: String?, operationType: String?, )
+ SwallowedException:SentryApollo4BuilderExtensionsClientErrorsTest.kt$SentryApollo4BuilderExtensionsClientErrorsTest$e: ApolloException
+ SwallowedException:SentryApollo4BuilderExtensionsTest.kt$SentryApollo4BuilderExtensionsTest$e: ApolloException
+ SwallowedException:SentryApollo4HttpInterceptorTest.kt$SentryApollo4HttpInterceptorTest$e: ApolloException
+ TooGenericExceptionCaught:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor$e: Throwable
+ TooGenericExceptionThrown:SentryApollo4HttpInterceptorTest.kt$SentryApollo4HttpInterceptorTest$throw RuntimeException()
+ TooManyFunctions:SentryApollo4HttpInterceptor.kt$SentryApollo4HttpInterceptor : HttpInterceptor
+ UseCheckOrError:LaunchDetailsQuery_VariablesAdapter.kt$LaunchDetailsQuery_VariablesAdapter$throw IllegalStateException("Input type used in output position")
+ UtilityClassWithPublicConstructor:GraphQLBoolean.kt$GraphQLBoolean
+ UtilityClassWithPublicConstructor:GraphQLID.kt$GraphQLID
+ UtilityClassWithPublicConstructor:GraphQLString.kt$GraphQLString
+ UtilityClassWithPublicConstructor:Launch.kt$Launch
+ UtilityClassWithPublicConstructor:Mission.kt$Mission
+ UtilityClassWithPublicConstructor:Query.kt$Query
+ UtilityClassWithPublicConstructor:Rocket.kt$Rocket
+
+
diff --git a/sentry-apollo-4/src/main/java/io/sentry/apollo4/SentryApollo4HttpInterceptor.kt b/sentry-apollo-4/src/main/java/io/sentry/apollo4/SentryApollo4HttpInterceptor.kt
index 54ad1d50fdb..437edff82d7 100644
--- a/sentry-apollo-4/src/main/java/io/sentry/apollo4/SentryApollo4HttpInterceptor.kt
+++ b/sentry-apollo-4/src/main/java/io/sentry/apollo4/SentryApollo4HttpInterceptor.kt
@@ -300,6 +300,7 @@ constructor(
return getHeaders(headers)
}
+ @Suppress("DEPRECATION")
private fun getHeaders(headers: List): MutableMap? {
// Headers are only sent if isSendDefaultPii is enabled due to PII
if (!scopes.options.isSendDefaultPii) {
diff --git a/sentry-apollo/build.gradle.kts b/sentry-apollo/build.gradle.kts
index 2da8d8b20c1..570214e60b8 100644
--- a/sentry-apollo/build.gradle.kts
+++ b/sentry-apollo/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.detekt)
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.buildconfig)
diff --git a/sentry-apollo/detekt-baseline.xml b/sentry-apollo/detekt-baseline.xml
new file mode 100644
index 00000000000..d010e1a4336
--- /dev/null
+++ b/sentry-apollo/detekt-baseline.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ EmptyFunctionBlock:SentryApolloInterceptor.kt$SentryApolloInterceptor${}
+ SwallowedException:SentryApolloInterceptorTest.kt$SentryApolloInterceptorTest$e: ApolloException
+ TooGenericExceptionCaught:SentryApolloInterceptor.kt$SentryApolloInterceptor$e: Exception
+ TooGenericExceptionThrown:SentryApolloInterceptorTest.kt$SentryApolloInterceptorTest$throw RuntimeException()
+
+
diff --git a/sentry-compose/build.gradle.kts b/sentry-compose/build.gradle.kts
index 28d88f28a2b..394885df1b8 100644
--- a/sentry-compose/build.gradle.kts
+++ b/sentry-compose/build.gradle.kts
@@ -79,6 +79,7 @@ kotlin {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.compose"
defaultConfig {
@@ -120,6 +121,9 @@ android {
}
}
+// The default detekt source set is src/{main,test}, which doesn't exist in a multiplatform module.
+detekt { source.setFrom("src/androidMain/kotlin", "src/androidUnitTest/kotlin") }
+
tasks.withType().configureEach {
// Target version of the generated JVM bytecode. It is used for type resolution.
jvmTarget = JavaVersion.VERSION_1_8.toString()
diff --git a/sentry-compose/detekt-baseline.xml b/sentry-compose/detekt-baseline.xml
new file mode 100644
index 00000000000..b59189497e9
--- /dev/null
+++ b/sentry-compose/detekt-baseline.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ CyclomaticComplexMethod:ComposeGestureTargetLocator.kt$ComposeGestureTargetLocator$override fun locate(root: Any?, x: Float, y: Float, targetType: UiElement.Type): UiElement?
+ FunctionOnlyReturningConstant:SentryModifier.kt$SentryModifier.SentryTagModifierNode$@Suppress("unused") fun isImportantForBounds(): Boolean
+ LongMethod:ComposeGestureTargetLocator.kt$ComposeGestureTargetLocator$override fun locate(root: Any?, x: Float, y: Float, targetType: UiElement.Type): UiElement?
+ LongParameterList:ComposeGestureTargetLocatorTest.kt$ComposeGestureTargetLocatorTest$( isPlaced: Boolean, tag: String?, width: Int, height: Int, children: List<LayoutNode> = emptyList(), semanticsKeys: List<String> = emptyList(), nodeModifierClassName: String? = null, left: Float = 0f, top: Float = 0f, )
+ LongParameterList:SentryComposeTracing.kt$BucketSpans$( ownerSpan: ISpan, startTimestamp: SentryDate, cached: WeakReference<ISpan>?, operation: String, description: String, setCached: (WeakReference<ISpan>) -> Unit, )
+ NestedBlockDepth:ComposeGestureTargetLocator.kt$ComposeGestureTargetLocator$override fun locate(root: Any?, x: Float, y: Float, targetType: UiElement.Type): UiElement?
+ NestedBlockDepth:SentryComposeHelper.kt$SentryComposeHelper$fun extractTag(modifier: Modifier): String?
+ ReturnCount:ComposeGestureTargetLocator.kt$ComposeGestureTargetLocator$override fun locate(root: Any?, x: Float, y: Float, targetType: UiElement.Type): UiElement?
+ ReturnCount:ComposeIntegrationTests.kt$ComposeIntegrationTests$private fun locateAndroidComposeView(root: View?): Any?
+ ReturnCount:SentryComposeHelper.kt$SentryComposeHelper$fun extractTag(modifier: Modifier): String?
+ SwallowedException:SentryComposeHelper.kt$SentryComposeHelper$e: Throwable
+ SwallowedException:SentryComposeHelper.kt$SentryComposeHelper.Companion$e: Exception
+ TooGenericExceptionCaught:SentryComposeHelper.kt$SentryComposeHelper$e: Throwable
+ TooGenericExceptionCaught:SentryComposeHelper.kt$SentryComposeHelper.Companion$e: Exception
+
+
diff --git a/sentry-ktor-client/build.gradle.kts b/sentry-ktor-client/build.gradle.kts
index fefcdbfebaf..a1eb9150d6f 100644
--- a/sentry-ktor-client/build.gradle.kts
+++ b/sentry-ktor-client/build.gradle.kts
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`java-library`
alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.detekt)
id("io.sentry.javadoc")
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
diff --git a/sentry-ktor-client/detekt-baseline.xml b/sentry-ktor-client/detekt-baseline.xml
new file mode 100644
index 00000000000..23405d6fda1
--- /dev/null
+++ b/sentry-ktor-client/detekt-baseline.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ MagicNumber:SentryKtorClientUtils.kt$SentryKtorClientUtils$1000L
+ MaxLineLength:SentryKtorClientPluginTest.kt$SentryKtorClientPluginTest$fun
+ TooGenericExceptionCaught:SentryKtorClientPlugin.kt$t: Throwable
+ UnusedParameter:SentryKtorClientPluginTest.kt$SentryKtorClientPluginTest.Fixture$beforeSpan: SentryKtorClientPluginConfig.BeforeSpanCallback? = null
+ WildcardImport:SentryKtorClientPlugin.kt$import io.ktor.client.plugins.api.*
+ WildcardImport:SentryKtorClientPlugin.kt$import io.ktor.client.request.*
+ WildcardImport:SentryKtorClientPlugin.kt$import io.ktor.client.statement.*
+ WildcardImport:SentryKtorClientPlugin.kt$import io.ktor.util.*
+ WildcardImport:SentryKtorClientPlugin.kt$import io.ktor.util.pipeline.*
+
+
diff --git a/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientUtils.kt b/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientUtils.kt
index b8c1385ed92..6632efce93f 100644
--- a/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientUtils.kt
+++ b/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientUtils.kt
@@ -92,6 +92,7 @@ internal object SentryKtorClientUtils {
return getHeaders(scopes, headers)
}
+ @Suppress("DEPRECATION")
private fun getHeaders(scopes: IScopes, headers: Headers): MutableMap? {
// Headers are only sent if isSendDefaultPii is enabled due to PII
if (!scopes.options.isSendDefaultPii) {
diff --git a/sentry-launchdarkly-android/build.gradle.kts b/sentry-launchdarkly-android/build.gradle.kts
index f201c57b97d..ddc982e9d6a 100644
--- a/sentry-launchdarkly-android/build.gradle.kts
+++ b/sentry-launchdarkly-android/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.launchdarkly.android"
defaultConfig {
diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
index 5e08fee0134..722845e2d70 100644
--- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
+++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
@@ -200,6 +200,7 @@ protected void captureLog(@NotNull ILoggingEvent loggingEvent) {
Sentry.logger().log(sentryLevel, params, formattedMessage, arguments);
}
+ @SuppressWarnings("deprecation")
private boolean shouldIncludeUnencodedMessage() {
return encoder == null
|| includeUnencodedMessage
diff --git a/sentry-okhttp/build.gradle.kts b/sentry-okhttp/build.gradle.kts
index 94575f54984..5abd1cf26b9 100644
--- a/sentry-okhttp/build.gradle.kts
+++ b/sentry-okhttp/build.gradle.kts
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`java-library`
alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.detekt)
id("io.sentry.javadoc")
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
diff --git a/sentry-okhttp/detekt-baseline.xml b/sentry-okhttp/detekt-baseline.xml
new file mode 100644
index 00000000000..b0d75302aa0
--- /dev/null
+++ b/sentry-okhttp/detekt-baseline.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ CyclomaticComplexMethod:SentryOkHttpInterceptor.kt$SentryOkHttpInterceptor$@Suppress("LongMethod") @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response
+ NestedBlockDepth:SentryOkHttpInterceptor.kt$SentryOkHttpInterceptor$@Suppress("LongMethod") @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response
+ ReturnCount:SentryOkHttpInterceptor.kt$SentryOkHttpInterceptor$private fun shouldCaptureClientError(request: Request, response: Response): Boolean
+ TooGenericExceptionCaught:SentryOkHttpInterceptor.kt$SentryOkHttpInterceptor$e: Exception
+
+
diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt
index 1be993c9544..106baced8ca 100644
--- a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt
+++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt
@@ -102,6 +102,7 @@ internal object SentryOkHttpUtils {
return getHeaders(scopes, responseHeaders)
}
+ @Suppress("DEPRECATION")
private fun getHeaders(scopes: IScopes, requestHeaders: Headers): MutableMap? {
// Headers are only sent if isSendDefaultPii is enabled due to PII
if (!scopes.options.isSendDefaultPii) {
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
index 8612f1132f8..cf0fb06c11a 100644
--- 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
@@ -74,7 +74,7 @@ private void addRequestAttributesToScope(
}
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"deprecation", "unchecked"})
private static Map collectHeaders(
final @NotNull Attributes attributes, final @NotNull SentryOptions options) {
Map headers = new HashMap<>();
diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts
index 6cb2302d7c7..40e5bbc9822 100644
--- a/sentry-samples/sentry-samples-android/build.gradle.kts
+++ b/sentry-samples/sentry-samples-android/build.gradle.kts
@@ -43,6 +43,7 @@ plugins.withId("io.sentry.android.gradle") {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.compileSdkMinor.get().toInt()
namespace = "io.sentry.samples.android"
defaultConfig {
diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt
index c923282527e..31bf86c7db2 100644
--- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt
+++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt
@@ -76,6 +76,7 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -992,7 +993,7 @@ fun IntegrationsScreen() {
tagSampleAction("memory_limiter")
activity.startActivity(Intent(activity, MemoryLimiterActivity::class.java))
},
- modifier = Modifier,
+ modifier = Modifier.testTag("open_memory_limiter_demo"),
) {
Text("MemoryLimiter Demo", maxLines = 2, overflow = TextOverflow.Ellipsis)
}
diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/memory/MemoryLimiterActivity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/memory/MemoryLimiterActivity.kt
index 6f9c650590b..087cfdbb8a1 100644
--- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/memory/MemoryLimiterActivity.kt
+++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/memory/MemoryLimiterActivity.kt
@@ -47,6 +47,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@@ -307,13 +308,25 @@ private fun MemoryLimiterScreen(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
- Button(onClick = onStartAllocating, enabled = isSupported && !isAllocating) {
+ Button(
+ onClick = onStartAllocating,
+ enabled = isSupported && !isAllocating,
+ modifier = Modifier.testTag("memory_limiter_start_pressure"),
+ ) {
Text("Start memory pressure")
}
- OutlinedButton(onClick = onReleaseBuffers, enabled = allocatedMb > 0 || isAllocating) {
+ OutlinedButton(
+ onClick = onReleaseBuffers,
+ enabled = allocatedMb > 0 || isAllocating,
+ modifier = Modifier.testTag("memory_limiter_reset_run"),
+ ) {
Text("Reset run")
}
- OutlinedButton(onClick = onMoveToBackground, enabled = isSupported) {
+ OutlinedButton(
+ onClick = onMoveToBackground,
+ enabled = isSupported,
+ modifier = Modifier.testTag("memory_limiter_move_background"),
+ ) {
Text("Move app to background")
}
}
@@ -431,7 +444,7 @@ private fun RuntimeCard(
isAllocating: Boolean,
statusLine: String,
) {
- ElevatedCard {
+ ElevatedCard(modifier = Modifier.testTag("memory_limiter_runtime_card")) {
Column(modifier = Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
text = "Runtime",
@@ -445,6 +458,7 @@ private fun RuntimeCard(
RuntimeRow(label = "Retained", value = "$allocatedMb MiB")
HorizontalDivider()
Text(
+ modifier = Modifier.testTag("memory_limiter_status_line"),
text = statusLine,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
diff --git a/sentry-spring-7/src/main/java/io/sentry/spring7/SentryRequestResolver.java b/sentry-spring-7/src/main/java/io/sentry/spring7/SentryRequestResolver.java
index c3d96eb8115..4995d4bfe09 100644
--- a/sentry-spring-7/src/main/java/io/sentry/spring7/SentryRequestResolver.java
+++ b/sentry-spring-7/src/main/java/io/sentry/spring7/SentryRequestResolver.java
@@ -22,6 +22,7 @@
import org.jetbrains.annotations.Nullable;
@Open
+@SuppressWarnings("deprecation")
public class SentryRequestResolver {
protected static final @NotNull AutoClosableReentrantLock staticLock =
new AutoClosableReentrantLock();
diff --git a/sentry-spring-7/src/main/java/io/sentry/spring7/webflux/SentryRequestResolver.java b/sentry-spring-7/src/main/java/io/sentry/spring7/webflux/SentryRequestResolver.java
index 3a4366d481b..c9ea5c6c7de 100644
--- a/sentry-spring-7/src/main/java/io/sentry/spring7/webflux/SentryRequestResolver.java
+++ b/sentry-spring-7/src/main/java/io/sentry/spring7/webflux/SentryRequestResolver.java
@@ -19,6 +19,7 @@
import org.springframework.http.server.reactive.ServerHttpRequest;
@Open
+@SuppressWarnings("deprecation")
@ApiStatus.Experimental
public class SentryRequestResolver {
private final @NotNull IScopes scopes;
diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java
index d4f69cb8714..9fcb30665cb 100644
--- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java
+++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java
@@ -22,6 +22,7 @@
import org.jetbrains.annotations.Nullable;
@Open
+@SuppressWarnings("deprecation")
public class SentryRequestResolver {
protected static final @NotNull AutoClosableReentrantLock staticLock =
new AutoClosableReentrantLock();
diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryRequestResolver.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryRequestResolver.java
index 774de4d6b31..f188f7ec5a4 100644
--- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryRequestResolver.java
+++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryRequestResolver.java
@@ -19,6 +19,7 @@
import org.springframework.http.server.reactive.ServerHttpRequest;
@Open
+@SuppressWarnings("deprecation")
@ApiStatus.Experimental
public class SentryRequestResolver {
private final @NotNull IScopes scopes;
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java
index 607fb2b58be..1c8ec52ac88 100644
--- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java
@@ -22,6 +22,7 @@
import org.jetbrains.annotations.Nullable;
@Open
+@SuppressWarnings("deprecation")
public class SentryRequestResolver {
protected static final @NotNull AutoClosableReentrantLock staticLock =
new AutoClosableReentrantLock();
diff --git a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryRequestResolver.java b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryRequestResolver.java
index 76ad3ba1703..321f2a05d8d 100644
--- a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryRequestResolver.java
+++ b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryRequestResolver.java
@@ -19,6 +19,7 @@
import org.springframework.http.server.reactive.ServerHttpRequest;
@Open
+@SuppressWarnings("deprecation")
@ApiStatus.Experimental
public class SentryRequestResolver {
private final @NotNull IScopes scopes;
diff --git a/sentry/src/main/java/io/sentry/DataCollectionResolver.java b/sentry/src/main/java/io/sentry/DataCollectionResolver.java
index fe643d8317c..81ec4dd16b0 100644
--- a/sentry/src/main/java/io/sentry/DataCollectionResolver.java
+++ b/sentry/src/main/java/io/sentry/DataCollectionResolver.java
@@ -6,6 +6,7 @@
import org.jetbrains.annotations.Nullable;
/** Resolves effective Data Collection policies for SDK integrations. */
+@SuppressWarnings("deprecation")
@ApiStatus.Internal
public final class DataCollectionResolver {
diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java
index f37e00db0fb..6dacb527276 100644
--- a/sentry/src/main/java/io/sentry/SentryOptions.java
+++ b/sentry/src/main/java/io/sentry/SentryOptions.java
@@ -1755,10 +1755,24 @@ public void setSdkVersion(final @Nullable SdkVersion sdkVersion) {
this.sdkVersion = sdkVersion;
}
+ /**
+ * Returns whether personally identifiable information is sent by default.
+ *
+ * @deprecated use {@link #getDataCollection()} instead. This option will be removed in 9.0.
+ */
+ @ApiStatus.ScheduledForRemoval(inVersion = "9.0")
+ @Deprecated
public boolean isSendDefaultPii() {
return sendDefaultPii;
}
+ /**
+ * Sets whether personally identifiable information is sent by default.
+ *
+ * @deprecated use {@link #getDataCollection()} instead. This option will be removed in 9.0.
+ */
+ @ApiStatus.ScheduledForRemoval(inVersion = "9.0")
+ @Deprecated
public void setSendDefaultPii(boolean sendDefaultPii) {
this.sendDefaultPii = sendDefaultPii;
}
diff --git a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java
index d6137ae051a..8a097486db1 100644
--- a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java
+++ b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java
@@ -378,5 +378,6 @@ public void resetCache() {
delete(TAGS_FILENAME);
delete(TRACE_FILENAME);
delete(TRANSACTION_FILENAME);
+ delete(REPLAY_FILENAME);
}
}
diff --git a/sentry/src/main/java/io/sentry/util/CookieUtils.java b/sentry/src/main/java/io/sentry/util/CookieUtils.java
index 935f98aaf1a..0b12acc1768 100644
--- a/sentry/src/main/java/io/sentry/util/CookieUtils.java
+++ b/sentry/src/main/java/io/sentry/util/CookieUtils.java
@@ -14,6 +14,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+@SuppressWarnings("deprecation")
@ApiStatus.Internal
public final class CookieUtils {
diff --git a/sentry/src/test/java/io/sentry/cache/PersistingScopeObserverBatchingTest.kt b/sentry/src/test/java/io/sentry/cache/PersistingScopeObserverBatchingTest.kt
index eec38e151ee..d893e52d4ce 100644
--- a/sentry/src/test/java/io/sentry/cache/PersistingScopeObserverBatchingTest.kt
+++ b/sentry/src/test/java/io/sentry/cache/PersistingScopeObserverBatchingTest.kt
@@ -6,7 +6,9 @@ import io.sentry.ISentryExecutorService
import io.sentry.ISerializer
import io.sentry.SentryOptions
import io.sentry.cache.PersistingScopeObserver.BREADCRUMBS_FILENAME
+import io.sentry.cache.PersistingScopeObserver.REPLAY_FILENAME
import io.sentry.cache.PersistingScopeObserver.TRANSACTION_FILENAME
+import io.sentry.protocol.SentryId
import io.sentry.test.DeferredExecutorService
import java.io.Writer
import java.util.concurrent.atomic.AtomicBoolean
@@ -28,6 +30,9 @@ class PersistingScopeObserverBatchingTest {
private fun PersistingScopeObserver.readTransaction(): String? =
read(options, TRANSACTION_FILENAME, String::class.java)
+ private fun PersistingScopeObserver.readReplayId(): String? =
+ read(options, REPLAY_FILENAME, String::class.java)
+
@Suppress("UNCHECKED_CAST")
private fun PersistingScopeObserver.readBreadcrumbs(): List =
read(options, BREADCRUMBS_FILENAME, List::class.java) as List
@@ -118,6 +123,20 @@ class PersistingScopeObserverBatchingTest {
}
}
+ @Test
+ fun `resetCache clears the replay id left behind by the previous process`() {
+ val executor = DeferredExecutorService()
+ val sut = getSut(executor)
+
+ sut.setReplayId(SentryId("afcb46b1140ade5187c4bbb5daa804df"))
+ executor.runAll()
+ assertThat(sut.readReplayId()).isEqualTo("afcb46b1140ade5187c4bbb5daa804df")
+
+ sut.resetCache()
+
+ assertThat(sut.readReplayId()).isNull()
+ }
+
@Test
fun `resetCache keeps pending mutations from the current process`() {
val executor = DeferredExecutorService()