From 1683fbbc91cebff618029eab3959bd2023f27152 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 29 Jan 2025 11:51:56 +0100 Subject: [PATCH 01/10] Bump Spring Boot to `3.4.2` (#4081) * Bump Spring Boot to 3.4.1 * changelog * update to spring boot 3.4.2 * fix jdbc dependencies for jakarta samples * update readme * update logback dependency in jakarta samples --------- Co-authored-by: Lukas Bloder --- CHANGELOG.md | 4 ++++ buildSrc/src/main/java/Config.kt | 2 +- .../build.gradle.kts | 4 ++-- .../build.gradle.kts | 4 ++-- .../sentry-samples-spring-boot-jakarta/build.gradle.kts | 4 ++-- .../build.gradle.kts | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4608e9bdf7..e5d824a9c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. +### Dependencies + +- Bump Spring Boot to `3.4.2` ([#4081](https://github.com/getsentry/sentry-java/pull/4081)) + ## 8.0.0 ### Summary diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index c52cfdc642..aadf7b14f8 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -7,7 +7,7 @@ object Config { val kotlinStdLib = "stdlib-jdk8" val springBootVersion = "2.7.5" - val springBoot3Version = "3.4.0" + val springBoot3Version = "3.4.2" val kotlinCompatibleLanguageVersion = "1.4" val composeVersion = "1.5.3" diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts index eebb0dcd84..bd08b78c04 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { implementation(Config.Libs.aspectj) implementation(Config.Libs.springBoot3Starter) implementation(Config.Libs.kotlinReflect) - implementation(Config.Libs.springBootStarterJdbc) + implementation(Config.Libs.springBoot3StarterJdbc) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarterJakarta) implementation(projects.sentryLogback) @@ -62,7 +62,7 @@ dependencies { } testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(Config.TestLibs.kotlinTestJunit) - testImplementation("ch.qos.logback:logback-classic:1.3.5") + testImplementation("ch.qos.logback:logback-classic:1.5.16") testImplementation(Config.Libs.slf4jApi2) testImplementation(Config.Libs.apolloKotlin) } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts index fe5dec4415..acfc0d24d9 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts @@ -47,7 +47,7 @@ dependencies { implementation(Config.Libs.aspectj) implementation(Config.Libs.springBoot3Starter) implementation(Config.Libs.kotlinReflect) - implementation(Config.Libs.springBootStarterJdbc) + implementation(Config.Libs.springBoot3StarterJdbc) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarterJakarta) implementation(projects.sentryLogback) @@ -63,7 +63,7 @@ dependencies { } testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(Config.TestLibs.kotlinTestJunit) - testImplementation("ch.qos.logback:logback-classic:1.3.5") + testImplementation("ch.qos.logback:logback-classic:1.5.16") testImplementation(Config.Libs.slf4jApi2) testImplementation(Config.Libs.apolloKotlin) } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts index 2f00573628..242859656c 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { implementation(Config.Libs.aspectj) implementation(Config.Libs.springBoot3Starter) implementation(Config.Libs.kotlinReflect) - implementation(Config.Libs.springBootStarterJdbc) + implementation(Config.Libs.springBoot3StarterJdbc) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarterJakarta) implementation(projects.sentryLogback) @@ -61,7 +61,7 @@ dependencies { } testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(Config.TestLibs.kotlinTestJunit) - testImplementation("ch.qos.logback:logback-classic:1.3.5") + testImplementation("ch.qos.logback:logback-classic:1.5.16") testImplementation(Config.Libs.slf4jApi2) testImplementation(Config.Libs.apolloKotlin) testImplementation(projects.sentry) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts index 9b60918081..fcc34c8b5b 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { } testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(Config.TestLibs.kotlinTestJunit) - testImplementation("ch.qos.logback:logback-classic:1.3.5") + testImplementation("ch.qos.logback:logback-classic:1.5.16") testImplementation(Config.Libs.slf4jApi2) testImplementation(Config.Libs.apolloKotlin) } From 880dc4f322b938aa38f93258fd47598bd36ba754 Mon Sep 17 00:00:00 2001 From: Lorenzo Cian Date: Wed, 29 Jan 2025 16:17:26 +0100 Subject: [PATCH 02/10] Add JavaDoc to `SentryOptions.ignoredTransactions,ignoredSpanOrigins,ignoredCheckIns` (#4110) * Add JavaDoc to `SentryOptions.ignoredTransactions,ignoredSpanOrigins,ignoredCheckIns` * Update sentry/src/main/java/io/sentry/SentryOptions.java Co-authored-by: Alexander Dinauer * Apply suggestions from code review Co-authored-by: Alexander Dinauer --------- Co-authored-by: Alexander Dinauer --- .../main/java/io/sentry/SentryOptions.java | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 2617cd3dfa..79a0c188a1 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -471,12 +471,19 @@ public class SentryOptions { /** Whether to enable scope persistence so the scope values are preserved if the process dies */ private boolean enableScopePersistence = true; - /** Contains a list of monitor slugs for which check-ins should not be sent. */ + /** The monitor slugs for which captured check-ins should not be sent to Sentry. */ @ApiStatus.Experimental private @Nullable List ignoredCheckIns = null; - /** Contains a list of span origins for which spans / transactions should not be created. */ + /** + * Strings or regex patterns that the origin of a new span/transaction will be tested against. If + * there is a match, the span/transaction will not be created. + */ @ApiStatus.Experimental private @Nullable List ignoredSpanOrigins = null; + /** + * Strings or regex patterns that captured transaction names will be tested against. If there is a + * match, the transaction will not be sent to Sentry. + */ private @Nullable List ignoredTransactions = null; @ApiStatus.Experimental @@ -2238,11 +2245,23 @@ public void setSendModules(boolean sendModules) { this.sendModules = sendModules; } + /** + * Returns the list of strings/regex patterns the origin of a new span/transaction will be tested + * against to determine whether the span/transaction shall be created. + * + * @return the list of strings or regex patterns + */ @ApiStatus.Experimental public @Nullable List getIgnoredSpanOrigins() { return ignoredSpanOrigins; } + /** + * Adds an item to the list of strings/regex patterns the origin of a new span/transaction will be + * tested against to determine whether the span/transaction shall be created. + * + * @param ignoredSpanOrigin the string/regex pattern + */ @ApiStatus.Experimental public void addIgnoredSpanOrigin(String ignoredSpanOrigin) { if (ignoredSpanOrigins == null) { @@ -2251,6 +2270,12 @@ public void addIgnoredSpanOrigin(String ignoredSpanOrigin) { ignoredSpanOrigins.add(new FilterString(ignoredSpanOrigin)); } + /** + * Sets the list of strings/regex patterns the origin of a new span/transaction will be tested + * against to determine whether the span/transaction shall be created. + * + * @param ignoredSpanOrigins the list of strings/regex patterns + */ @ApiStatus.Experimental public void setIgnoredSpanOrigins(final @Nullable List ignoredSpanOrigins) { if (ignoredSpanOrigins == null) { @@ -2267,11 +2292,22 @@ public void setIgnoredSpanOrigins(final @Nullable List ignoredSpanOrigin } } + /** + * Returns the list of monitor slugs for which captured check-ins should not be sent to Sentry. + * + * @return the list of monitor slugs + */ @ApiStatus.Experimental public @Nullable List getIgnoredCheckIns() { return ignoredCheckIns; } + /** + * Adds a monitor slug to the list of slugs for which captured check-ins should not be sent to + * Sentry. + * + * @param ignoredCheckIn the monitor slug + */ @ApiStatus.Experimental public void addIgnoredCheckIn(String ignoredCheckIn) { if (ignoredCheckIns == null) { @@ -2280,6 +2316,11 @@ public void addIgnoredCheckIn(String ignoredCheckIn) { ignoredCheckIns.add(new FilterString(ignoredCheckIn)); } + /** + * Sets the list of monitor slugs for which captured check-ins should not be sent to Sentry. + * + * @param ignoredCheckIns the list of monitor slugs for which check-ins should not be sent + */ @ApiStatus.Experimental public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { if (ignoredCheckIns == null) { @@ -2296,10 +2337,22 @@ public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { } } + /** + * Returns the list of strings/regex patterns that captured transaction names are checked against + * to determine if a transaction shall be sent to Sentry or ignored. + * + * @return the list of strings/regex patterns + */ public @Nullable List getIgnoredTransactions() { return ignoredTransactions; } + /** + * Adds an element the list of strings/regex patterns that captured transaction names are checked + * against to determine if a transaction shall be sent to Sentry or ignored. + * + * @param ignoredTransaction the string/regex pattern + */ @ApiStatus.Experimental public void addIgnoredTransaction(String ignoredTransaction) { if (ignoredTransactions == null) { @@ -2308,6 +2361,12 @@ public void addIgnoredTransaction(String ignoredTransaction) { ignoredTransactions.add(new FilterString(ignoredTransaction)); } + /** + * Sets the list of strings/regex patterns that captured transaction names are checked against to + * determine if a transaction shall be sent to Sentry or ignored. + * + * @param ignoredTransactions the list of string/regex patterns + */ @ApiStatus.Experimental public void setIgnoredTransactions(final @Nullable List ignoredTransactions) { if (ignoredTransactions == null) { From 2fc1ed7633b3741bc3d8a49b65888cdb95ea5300 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 29 Jan 2025 19:42:58 +0100 Subject: [PATCH 03/10] Set mechanism `type` to `suppressed` for suppressed exceptions (#4125) * set mechanism type to suppressed for suppressed exceptions * changelog --- CHANGELOG.md | 2 + .../io/sentry/SentryExceptionFactory.java | 12 ++++-- .../io/sentry/SentryExceptionFactoryTest.kt | 39 ++++++++++++++----- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5d824a9c4..011259b2d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - Remove `java.lang.ClassNotFoundException` debug logs when searching for OpenTelemetry marker classes ([#4091](https://github.com/getsentry/sentry-java/pull/4091)) - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. +- Set mechanism `type` to `suppressed` for suppressed exceptions ([#4125](https://github.com/getsentry/sentry-java/pull/4125)) + - This helps to distinguish an exceptions cause from any suppressed exceptions in the Sentry UI ### Dependencies diff --git a/sentry/src/main/java/io/sentry/SentryExceptionFactory.java b/sentry/src/main/java/io/sentry/SentryExceptionFactory.java index a6138aa398..8b2b570946 100644 --- a/sentry/src/main/java/io/sentry/SentryExceptionFactory.java +++ b/sentry/src/main/java/io/sentry/SentryExceptionFactory.java @@ -137,14 +137,15 @@ public List getSentryExceptions(final @NotNull Throwable throwa @NotNull Deque extractExceptionQueue(final @NotNull Throwable throwable) { return extractExceptionQueueInternal( - throwable, new AtomicInteger(-1), new HashSet<>(), new ArrayDeque<>()); + throwable, new AtomicInteger(-1), new HashSet<>(), new ArrayDeque<>(), null); } Deque extractExceptionQueueInternal( final @NotNull Throwable throwable, final @NotNull AtomicInteger exceptionId, final @NotNull HashSet circularityDetector, - final @NotNull Deque exceptions) { + final @NotNull Deque exceptions, + @Nullable String mechanismTypeOverride) { Mechanism exceptionMechanism; Thread thread; @@ -154,6 +155,8 @@ Deque extractExceptionQueueInternal( // Stack the exceptions to send them in the reverse order while (currentThrowable != null && circularityDetector.add(currentThrowable)) { boolean snapshot = false; + final @NotNull String mechanismType = + mechanismTypeOverride == null ? "chained" : mechanismTypeOverride; if (currentThrowable instanceof ExceptionMechanismException) { // this is for ANR I believe ExceptionMechanismException exceptionMechanismThrowable = @@ -177,7 +180,7 @@ Deque extractExceptionQueueInternal( exceptions.addFirst(exception); if (exceptionMechanism.getType() == null) { - exceptionMechanism.setType("chained"); + exceptionMechanism.setType(mechanismType); } if (exceptionId.get() >= 0) { @@ -194,11 +197,12 @@ Deque extractExceptionQueueInternal( // exceptionMechanism.setExceptionGroup(true); for (Throwable suppressedThrowable : suppressed) { extractExceptionQueueInternal( - suppressedThrowable, exceptionId, circularityDetector, exceptions); + suppressedThrowable, exceptionId, circularityDetector, exceptions, "suppressed"); } } currentThrowable = currentThrowable.getCause(); parentId = currentExceptionId; + mechanismTypeOverride = null; } return exceptions; diff --git a/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt b/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt index 69a7721359..089680d40d 100644 --- a/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt @@ -212,7 +212,7 @@ class SentryExceptionFactoryTest { @Test fun `when exception with mechanism suppressed exceptions, add them and show as group`() { val exception = Exception("message") - val suppressedException = Exception("suppressed") + val suppressedException = Exception("suppressed exception") exception.addSuppressed(suppressedException) val mechanism = Mechanism() @@ -225,19 +225,21 @@ class SentryExceptionFactoryTest { val suppressedInQueue = queue.pop() val mainInQueue = queue.pop() - assertEquals("suppressed", suppressedInQueue.value) + assertEquals("suppressed exception", suppressedInQueue.value) assertEquals(1, suppressedInQueue.mechanism?.exceptionId) assertEquals(0, suppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", suppressedInQueue.mechanism?.type) assertEquals("message", mainInQueue.value) assertEquals(0, mainInQueue.mechanism?.exceptionId) + assertEquals("ANR", mainInQueue.mechanism?.type) // assertEquals(true, mainInQueue.mechanism?.isExceptionGroup) } @Test fun `nested exception that contains suppressed exceptions is marked as group`() { val exception = Exception("inner") - val suppressedException = Exception("suppressed") + val suppressedException = Exception("suppressed exception") exception.addSuppressed(suppressedException) val outerException = Exception("outer", exception) @@ -248,25 +250,28 @@ class SentryExceptionFactoryTest { val mainInQueue = queue.pop() val outerInQueue = queue.pop() - assertEquals("suppressed", suppressedInQueue.value) + assertEquals("suppressed exception", suppressedInQueue.value) assertEquals(2, suppressedInQueue.mechanism?.exceptionId) assertEquals(1, suppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", suppressedInQueue.mechanism?.type) assertEquals("inner", mainInQueue.value) assertEquals(1, mainInQueue.mechanism?.exceptionId) assertEquals(0, mainInQueue.mechanism?.parentId) + assertEquals("chained", mainInQueue.mechanism?.type) // assertEquals(true, mainInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) + assertEquals("chained", outerInQueue.mechanism?.type) // assertNull(outerInQueue.mechanism?.isExceptionGroup) } @Test fun `nested exception within Mechanism that contains suppressed exceptions is marked as group`() { val exception = Exception("inner") - val suppressedException = Exception("suppressed") + val suppressedException = Exception("suppressed exception") exception.addSuppressed(suppressedException) val mechanism = Mechanism() @@ -281,18 +286,21 @@ class SentryExceptionFactoryTest { val mainInQueue = queue.pop() val outerInQueue = queue.pop() - assertEquals("suppressed", suppressedInQueue.value) + assertEquals("suppressed exception", suppressedInQueue.value) assertEquals(2, suppressedInQueue.mechanism?.exceptionId) assertEquals(1, suppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", suppressedInQueue.mechanism?.type) assertEquals("inner", mainInQueue.value) assertEquals(1, mainInQueue.mechanism?.exceptionId) assertEquals(0, mainInQueue.mechanism?.parentId) + assertEquals("chained", mainInQueue.mechanism?.type) // assertEquals(true, mainInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) + assertEquals("ANR", outerInQueue.mechanism?.type) // assertNull(outerInQueue.mechanism?.isExceptionGroup) } @@ -303,7 +311,7 @@ class SentryExceptionFactoryTest { innerMostException.addSuppressed(innerMostSuppressed) val innerException = Exception("inner", innerMostException) - val innerSuppressed = Exception("suppressed") + val innerSuppressed = Exception("suppressed exception") innerException.addSuppressed(innerSuppressed) val outerException = Exception("outer", innerException) @@ -319,27 +327,32 @@ class SentryExceptionFactoryTest { assertEquals("innermostSuppressed", innerMostSuppressedInQueue.value) assertEquals(4, innerMostSuppressedInQueue.mechanism?.exceptionId) assertEquals(3, innerMostSuppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", innerMostSuppressedInQueue.mechanism?.type) assertNull(innerMostSuppressedInQueue.mechanism?.isExceptionGroup) assertEquals("innermost", innerMostExceptionInQueue.value) assertEquals(3, innerMostExceptionInQueue.mechanism?.exceptionId) assertEquals(1, innerMostExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerMostExceptionInQueue.mechanism?.type) // assertEquals(true, innerMostExceptionInQueue.mechanism?.isExceptionGroup) - assertEquals("suppressed", innerSuppressedInQueue.value) + assertEquals("suppressed exception", innerSuppressedInQueue.value) assertEquals(2, innerSuppressedInQueue.mechanism?.exceptionId) assertEquals(1, innerSuppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", innerSuppressedInQueue.mechanism?.type) assertNull(innerSuppressedInQueue.mechanism?.isExceptionGroup) assertEquals("inner", innerExceptionInQueue.value) assertEquals(1, innerExceptionInQueue.mechanism?.exceptionId) assertEquals(0, innerExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerExceptionInQueue.mechanism?.type) // assertEquals(true, innerExceptionInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) assertNull(outerInQueue.mechanism?.isExceptionGroup) + assertEquals("chained", outerInQueue.mechanism?.type) } @Test @@ -351,7 +364,7 @@ class SentryExceptionFactoryTest { innerMostException.addSuppressed(innerMostSuppressed) val innerException = Exception("inner", innerMostException) - val innerSuppressed = Exception("suppressed") + val innerSuppressed = Exception("suppressed exception") innerException.addSuppressed(innerSuppressed) val outerException = Exception("outer", innerException) @@ -369,31 +382,37 @@ class SentryExceptionFactoryTest { assertEquals(5, innerMostSuppressedNestedExceptionInQueue.mechanism?.exceptionId) assertEquals(4, innerMostSuppressedNestedExceptionInQueue.mechanism?.parentId) assertNull(innerMostSuppressedNestedExceptionInQueue.mechanism?.isExceptionGroup) + assertEquals("chained", innerMostSuppressedNestedExceptionInQueue.mechanism?.type) assertEquals("innermostSuppressed", innerMostSuppressedInQueue.value) assertEquals(4, innerMostSuppressedInQueue.mechanism?.exceptionId) assertEquals(3, innerMostSuppressedInQueue.mechanism?.parentId) assertNull(innerMostSuppressedInQueue.mechanism?.isExceptionGroup) + assertEquals("suppressed", innerMostSuppressedInQueue.mechanism?.type) assertEquals("innermost", innerMostExceptionInQueue.value) assertEquals(3, innerMostExceptionInQueue.mechanism?.exceptionId) assertEquals(1, innerMostExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerMostExceptionInQueue.mechanism?.type) // assertEquals(true, innerMostExceptionInQueue.mechanism?.isExceptionGroup) - assertEquals("suppressed", innerSuppressedInQueue.value) + assertEquals("suppressed exception", innerSuppressedInQueue.value) assertEquals(2, innerSuppressedInQueue.mechanism?.exceptionId) assertEquals(1, innerSuppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", innerSuppressedInQueue.mechanism?.type) assertNull(innerSuppressedInQueue.mechanism?.isExceptionGroup) assertEquals("inner", innerExceptionInQueue.value) assertEquals(1, innerExceptionInQueue.mechanism?.exceptionId) assertEquals(0, innerExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerExceptionInQueue.mechanism?.type) // assertEquals(true, innerExceptionInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) assertNull(outerInQueue.mechanism?.isExceptionGroup) + assertEquals("chained", outerInQueue.mechanism?.type) } internal class InnerClassThrowable constructor(cause: Throwable? = null) : Throwable(cause) From b68011a07c3c30e51f119a7d0b42d5e92e6d2d3f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 30 Jan 2025 14:25:52 +0100 Subject: [PATCH 04/10] Log debug messages giving info about OpenTelemetry related config settings (#4122) * Log debug messages giving info about OpenTelemetry related config settings * changelog --- CHANGELOG.md | 1 + sentry/api/sentry.api | 3 +- sentry/src/main/java/io/sentry/Sentry.java | 33 +++++++++---- .../opentelemetry/OpenTelemetryUtil.java | 46 ++++++++++++++----- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 011259b2d7..6fe8d6a07d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Can be set in `sentry.properties`, e.g. `ignored-errors=Some error,Another .*` - Can be set in environment variables, e.g. `SENTRY_IGNORED_ERRORS=Some error,Another .*` - For Spring Boot, it can be set in `application.properties`, e.g. `sentry.ignored-errors=Some error,Another .*` +- Log OpenTelemetry related Sentry config ([#4122](https://github.com/getsentry/sentry-java/pull/4122)) ### Fixes diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 642b67ec06..2b885bed95 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -4253,7 +4253,8 @@ public abstract interface class io/sentry/internal/viewhierarchy/ViewHierarchyEx public final class io/sentry/opentelemetry/OpenTelemetryUtil { public fun ()V - public static fun applyIgnoredSpanOrigins (Lio/sentry/SentryOptions;Lio/sentry/util/LoadClass;)V + public static fun applyIgnoredSpanOrigins (Lio/sentry/SentryOptions;)V + public static fun updateOpenTelemetryModeIfAuto (Lio/sentry/SentryOptions;Lio/sentry/util/LoadClass;)V } public final class io/sentry/profilemeasurements/ProfileMeasurement : io/sentry/JsonSerializable, io/sentry/JsonUnknown { diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index a00d145507..70519fb98f 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -322,9 +322,9 @@ private static void init(final @NotNull SentryOptions options, final boolean glo final IScope rootIsolationScope = new Scope(options); rootScopes = new Scopes(rootScope, rootIsolationScope, globalScope, "Sentry.init"); + initLogger(options); initForOpenTelemetryMaybe(options); getScopesStorage().set(rootScopes); - initConfigurations(options); globalScope.bindClient(new SentryClient(options)); @@ -348,6 +348,19 @@ private static void init(final @NotNull SentryOptions options, final boolean glo finalizePreviousSession(options, ScopesAdapter.getInstance()); handleAppStartProfilingConfig(options, options.getExecutorService()); + + options + .getLogger() + .log(SentryLevel.DEBUG, "Using openTelemetryMode %s", options.getOpenTelemetryMode()); + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Using span factory %s", + options.getSpanFactory().getClass().getName()); + options + .getLogger() + .log(SentryLevel.DEBUG, "Using scopes storage %s", scopesStorage.getClass().getName()); } else { options .getLogger() @@ -359,6 +372,7 @@ private static void init(final @NotNull SentryOptions options, final boolean glo } private static void initForOpenTelemetryMaybe(SentryOptions options) { + OpenTelemetryUtil.updateOpenTelemetryModeIfAuto(options, new LoadClass()); if (SentryOpenTelemetryMode.OFF == options.getOpenTelemetryMode()) { options.setSpanFactory(new DefaultSpanFactory()); // } else { @@ -367,7 +381,13 @@ private static void initForOpenTelemetryMaybe(SentryOptions options) { // NoOpLogger.getInstance())); } initScopesStorage(options); - OpenTelemetryUtil.applyIgnoredSpanOrigins(options, new LoadClass()); + OpenTelemetryUtil.applyIgnoredSpanOrigins(options); + } + + private static void initLogger(final @NotNull SentryOptions options) { + if (options.isDebug() && options.getLogger() instanceof NoOpLogger) { + options.setLogger(new SystemOutLogger()); + } } private static void initScopesStorage(SentryOptions options) { @@ -505,16 +525,9 @@ private static boolean preInitConfigurations(final @NotNull SentryOptions option @SuppressWarnings("FutureReturnValueIgnored") private static void initConfigurations(final @NotNull SentryOptions options) { - ILogger logger = options.getLogger(); - - if (options.isDebug() && logger instanceof NoOpLogger) { - options.setLogger(new SystemOutLogger()); - logger = options.getLogger(); - } + final @NotNull ILogger logger = options.getLogger(); logger.log(SentryLevel.INFO, "Initializing SDK with DSN: '%s'", options.getDsn()); - OpenTelemetryUtil.applyIgnoredSpanOrigins(options, new LoadClass()); - // TODO: read values from conf file, Build conf or system envs // eg release, distinctId, sentryClientName diff --git a/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java b/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java index 3bf7eac1a2..05dd7c76da 100644 --- a/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java +++ b/sentry/src/main/java/io/sentry/opentelemetry/OpenTelemetryUtil.java @@ -1,6 +1,7 @@ package io.sentry.opentelemetry; import io.sentry.NoOpLogger; +import io.sentry.SentryLevel; import io.sentry.SentryOpenTelemetryMode; import io.sentry.SentryOptions; import io.sentry.util.LoadClass; @@ -15,37 +16,60 @@ public final class OpenTelemetryUtil { @ApiStatus.Internal - public static void applyIgnoredSpanOrigins( - final @NotNull SentryOptions options, final @NotNull LoadClass loadClass) { + public static void applyIgnoredSpanOrigins(final @NotNull SentryOptions options) { if (Platform.isJvm()) { - final @NotNull List ignored = ignoredSpanOrigins(options, loadClass); + final @NotNull List ignored = ignoredSpanOrigins(options); for (String origin : ignored) { options.addIgnoredSpanOrigin(origin); } } } - private static @NotNull List ignoredSpanOrigins( + @ApiStatus.Internal + public static void updateOpenTelemetryModeIfAuto( final @NotNull SentryOptions options, final @NotNull LoadClass loadClass) { + if (!Platform.isJvm()) { + return; + } + final @NotNull SentryOpenTelemetryMode openTelemetryMode = options.getOpenTelemetryMode(); if (SentryOpenTelemetryMode.AUTO.equals(openTelemetryMode)) { if (loadClass.isClassAvailable( "io.sentry.opentelemetry.agent.AgentMarker", NoOpLogger.getInstance())) { - return SpanUtils.ignoredSpanOriginsForOpenTelemetry(SentryOpenTelemetryMode.AGENT); + options + .getLogger() + .log(SentryLevel.DEBUG, "openTelemetryMode has been inferred from AUTO to AGENT"); + options.setOpenTelemetryMode(SentryOpenTelemetryMode.AGENT); + return; } if (loadClass.isClassAvailable( "io.sentry.opentelemetry.agent.AgentlessMarker", NoOpLogger.getInstance())) { - return SpanUtils.ignoredSpanOriginsForOpenTelemetry(SentryOpenTelemetryMode.AGENTLESS); + options + .getLogger() + .log(SentryLevel.DEBUG, "openTelemetryMode has been inferred from AUTO to AGENTLESS"); + options.setOpenTelemetryMode(SentryOpenTelemetryMode.AGENTLESS); + return; } if (loadClass.isClassAvailable( "io.sentry.opentelemetry.agent.AgentlessSpringMarker", NoOpLogger.getInstance())) { - return SpanUtils.ignoredSpanOriginsForOpenTelemetry( - SentryOpenTelemetryMode.AGENTLESS_SPRING); + options + .getLogger() + .log( + SentryLevel.DEBUG, + "openTelemetryMode has been inferred from AUTO to AGENTLESS_SPRING"); + options.setOpenTelemetryMode(SentryOpenTelemetryMode.AGENTLESS_SPRING); + return; } - } else { - return SpanUtils.ignoredSpanOriginsForOpenTelemetry(openTelemetryMode); + } + } + + private static @NotNull List ignoredSpanOrigins(final @NotNull SentryOptions options) { + final @NotNull SentryOpenTelemetryMode openTelemetryMode = options.getOpenTelemetryMode(); + + if (SentryOpenTelemetryMode.OFF.equals(openTelemetryMode)) { + return Collections.emptyList(); } - return Collections.emptyList(); + return SpanUtils.ignoredSpanOriginsForOpenTelemetry(openTelemetryMode); } } From a9719c742f118a21086a3bb82dd9a7f22e9cafc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:14:41 +0000 Subject: [PATCH 05/10] chore(deps): update Native SDK to v0.7.19 (#4076) * chore: update scripts/update-sentry-native-ndk.sh to 0.7.19 * chore: update scripts/update-sentry-native-ndk.sh to 0.7.19 * Propagate NDK handler strategy to Ndk Options * Fix Changelog --------- Co-authored-by: GitHub Co-authored-by: Markus Hintersteiner Co-authored-by: Alexander Dinauer --- CHANGELOG.md | 3 +++ buildSrc/src/main/java/Config.kt | 2 +- .../main/java/io/sentry/android/ndk/SentryNdk.java | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fe8d6a07d..505ca050dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ ### Dependencies - Bump Spring Boot to `3.4.2` ([#4081](https://github.com/getsentry/sentry-java/pull/4081)) +- Bump Native SDK from v0.7.14 to v0.7.19 ([#4076](https://github.com/getsentry/sentry-java/pull/4076)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0719) + - [diff](https://github.com/getsentry/sentry-native/compare/v0.7.14...0.7.19) ## 8.0.0 diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index aadf7b14f8..59a54600cf 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -153,7 +153,7 @@ object Config { val apolloKotlin = "com.apollographql.apollo3:apollo-runtime:3.8.2" - val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.7.14" + val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.7.19" object OpenTelemetry { val otelVersion = "1.44.1" diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java index aafd66b059..cce8e35b82 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java @@ -1,5 +1,6 @@ package io.sentry.android.ndk; +import io.sentry.android.core.NdkHandlerStrategy; import io.sentry.android.core.SentryAndroidOptions; import io.sentry.ndk.NativeModuleListLoader; import io.sentry.ndk.NdkOptions; @@ -54,6 +55,16 @@ public static void init(@NotNull final SentryAndroidOptions options) { options.getMaxBreadcrumbs(), options.getNativeSdkName()); + final int handlerStrategy = options.getNdkHandlerStrategy(); + if (handlerStrategy == NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_DEFAULT.getValue()) { + ndkOptions.setNdkHandlerStrategy( + io.sentry.ndk.NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_DEFAULT); + } else if (handlerStrategy + == NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_CHAIN_AT_START.getValue()) { + ndkOptions.setNdkHandlerStrategy( + io.sentry.ndk.NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_CHAIN_AT_START); + } + //noinspection UnstableApiUsage io.sentry.ndk.SentryNdk.init(ndkOptions); From 2748875ce1e5e71c843a9e30a9e69f643229a80c Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 30 Jan 2025 16:30:05 +0100 Subject: [PATCH 06/10] Re-apply 7.x.x changes (#4124) * Various fixes to instrumentations running on the main thread (#4051) * Get rid of redundant requireNonNull * Do not instrument Window.Callback multiple times * Do not instrument FileIO if tracing is disabled * Do not traverse children if a touch event is not within view groups bounds * Add test for SentryFileOutputStream * Fix test * Fix test * Changelog * pr id * Fix api dump * Fix BroadcastReceivers (#4052) * Drop TempSesnorBreadcrumbIntegration * Drop PhoneStateBreadcrumbsIntegration * Reduce number of system events we're listening to and use RECEIVER_NOT_EXPORTED * Format code * Changelog * Update CHANGELOG.md Co-authored-by: Stefano * Update CHANGELOG.md Co-authored-by: Stefano --------- Co-authored-by: Sentry Github Bot Co-authored-by: Stefano * Only provide {{auto}} ip-address if sendDefaultPii is enabled * Update changelog * Reduce the number of IPC calls (#4058) * Remove binder call for external storage * Remove binder call for memory in profiler * Cache static values to avoid binder calls * Comment * Changelog * Formatting * Fix tests * Minor fixes * change protected method in final class to private --------- Co-authored-by: Markus Hintersteiner Co-authored-by: stefanosiano * Update Changelog * Fix tests --------- Co-authored-by: Roman Zavarnitsyn Co-authored-by: Sentry Github Bot Co-authored-by: Stefano --- CHANGELOG.md | 84 +++++++++ .../api/sentry-android-core.api | 27 ++- .../core/AndroidOptionsInitializer.java | 6 +- .../core/AndroidTransactionProfiler.java | 32 +--- .../android/core/AnrV2EventProcessor.java | 26 +-- .../io/sentry/android/core/ContextUtils.java | 177 +++++++++++++----- .../core/DefaultAndroidEventProcessor.java | 2 +- .../sentry/android/core/DeviceInfoUtil.java | 20 +- .../android/core/InternalSentrySdk.java | 2 +- .../android/core/ManifestMetadataReader.java | 11 +- .../PhoneStateBreadcrumbsIntegration.java | 138 -------------- .../SystemEventsBreadcrumbsIntegration.java | 49 +---- .../TempSensorBreadcrumbsIntegration.java | 146 --------------- .../core/UserInteractionIntegration.java | 5 + .../AndroidViewGestureTargetLocator.java | 26 +-- .../core/internal/gestures/ViewUtils.java | 36 +++- .../core/util/AndroidLazyEvaluator.java | 68 +++++++ .../core/ActivityLifecycleIntegrationTest.kt | 1 + .../core/AndroidOptionsInitializerTest.kt | 1 + .../android/core/AnrV2EventProcessorTest.kt | 1 + .../sentry/android/core/ContextUtilsTest.kt | 15 +- .../core/DefaultAndroidEventProcessorTest.kt | 5 +- .../core/ManifestMetadataReaderTest.kt | 6 + .../PhoneStateBreadcrumbsIntegrationTest.kt | 128 ------------- .../sentry/android/core/SentryAndroidTest.kt | 4 +- .../android/core/SentryInitProviderTest.kt | 1 + .../android/core/SentryLogcatAdapterTest.kt | 1 + .../TempSensorBreadcrumbsIntegrationTest.kt | 133 ------------- .../core/UserInteractionIntegrationTest.kt | 24 +++ .../SentryGestureListenerClickTest.kt | 22 ++- .../gestures/ComposeGestureTargetLocator.java | 2 +- .../src/main/AndroidManifest.xml | 2 - sentry/api/sentry.api | 1 + .../file/SentryFileInputStream.java | 27 ++- .../file/SentryFileOutputStream.java | 54 +++++- .../gestures/GestureTargetLocator.java | 3 +- .../file/SentryFileInputStreamTest.kt | 19 +- .../file/SentryFileOutputStreamTest.kt | 46 +++++ 38 files changed, 564 insertions(+), 787 deletions(-) delete mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java delete mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java delete mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt delete mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 505ca050dc..41610d1933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,28 @@ - Remove `java.lang.ClassNotFoundException` debug logs when searching for OpenTelemetry marker classes ([#4091](https://github.com/getsentry/sentry-java/pull/4091)) - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. +- Do not instrument File I/O operations if tracing is disabled ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Do not instrument User Interaction multiple times ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Speed up view traversal to find touched target in `UserInteractionIntegration` ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Reduce IPC/Binder calls performed by the SDK ([#4058](https://github.com/getsentry/sentry-java/pull/4058)) + +### Behavioural Changes + +- Reduce the number of broadcasts the SDK is subscribed for ([#4052](https://github.com/getsentry/sentry-java/pull/4052)) + - Drop `TempSensorBreadcrumbsIntegration` + - Drop `PhoneStateBreadcrumbsIntegration` + - Reduce number of broadcasts in `SystemEventsBreadcrumbsIntegration` + +Current list of the broadcast events can be found [here](https://github.com/getsentry/sentry-java/blob/9b8dc0a844d10b55ddeddf55d278c0ab0f86421c/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java#L131-L153). If you'd like to subscribe for more events, consider overriding the `SystemEventsBreadcrumbsIntegration` as follows: + +```kotlin +SentryAndroid.init(context) { options -> + options.integrations.removeAll { it is SystemEventsBreadcrumbsIntegration } + options.integrations.add(SystemEventsBreadcrumbsIntegration(context, SystemEventsBreadcrumbsIntegration.getDefaultActions() + listOf(/* your custom actions */))) +} +``` + +If you would like to keep some of the default broadcast events as breadcrumbs, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new). - Set mechanism `type` to `suppressed` for suppressed exceptions ([#4125](https://github.com/getsentry/sentry-java/pull/4125)) - This helps to distinguish an exceptions cause from any suppressed exceptions in the Sentry UI @@ -330,6 +352,68 @@ If you have been using `8.0.0-rc.4` of the Java SDK, here's the new changes that - We are planning to improve this in the future but opted for this fix first. - Fix swallow NDK loadLibrary errors ([#4082](https://github.com/getsentry/sentry-java/pull/4082)) +## 7.21.0 + +### Fixes + +- Do not instrument File I/O operations if tracing is disabled ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Do not instrument User Interaction multiple times ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Speed up view traversal to find touched target in `UserInteractionIntegration` ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Reduce IPC/Binder calls performed by the SDK ([#4058](https://github.com/getsentry/sentry-java/pull/4058)) + +### Behavioural Changes + +- Reduce the number of broadcasts the SDK is subscribed for ([#4052](https://github.com/getsentry/sentry-java/pull/4052)) + - Drop `TempSensorBreadcrumbsIntegration` + - Drop `PhoneStateBreadcrumbsIntegration` + - Reduce number of broadcasts in `SystemEventsBreadcrumbsIntegration` + +Current list of the broadcast events can be found [here](https://github.com/getsentry/sentry-java/blob/9b8dc0a844d10b55ddeddf55d278c0ab0f86421c/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java#L131-L153). If you'd like to subscribe for more events, consider overriding the `SystemEventsBreadcrumbsIntegration` as follows: + +```kotlin +SentryAndroid.init(context) { options -> + options.integrations.removeAll { it is SystemEventsBreadcrumbsIntegration } + options.integrations.add(SystemEventsBreadcrumbsIntegration(context, SystemEventsBreadcrumbsIntegration.getDefaultActions() + listOf(/* your custom actions */))) +} +``` + +If you would like to keep some of the default broadcast events as breadcrumbs, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new). + +## 7.21.0-beta.1 + +### Fixes + +- Do not instrument File I/O operations if tracing is disabled ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Do not instrument User Interaction multiple times ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Speed up view traversal to find touched target in `UserInteractionIntegration` ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) +- Reduce IPC/Binder calls performed by the SDK ([#4058](https://github.com/getsentry/sentry-java/pull/4058)) + +### Behavioural Changes + +- Reduce the number of broadcasts the SDK is subscribed for ([#4052](https://github.com/getsentry/sentry-java/pull/4052)) + - Drop `TempSensorBreadcrumbsIntegration` + - Drop `PhoneStateBreadcrumbsIntegration` + - Reduce number of broadcasts in `SystemEventsBreadcrumbsIntegration` + +Current list of the broadcast events can be found [here](https://github.com/getsentry/sentry-java/blob/9b8dc0a844d10b55ddeddf55d278c0ab0f86421c/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java#L131-L153). If you'd like to subscribe for more events, consider overriding the `SystemEventsBreadcrumbsIntegration` as follows: + +```kotlin +SentryAndroid.init(context) { options -> + options.integrations.removeAll { it is SystemEventsBreadcrumbsIntegration } + options.integrations.add(SystemEventsBreadcrumbsIntegration(context, SystemEventsBreadcrumbsIntegration.getDefaultActions() + listOf(/* your custom actions */))) +} +``` + +If you would like to keep some of the default broadcast events as breadcrumbs, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new). + +## 7.20.1 + +### Behavioural Changes + +- The user ip-address is now only set to `"{{auto}}"` if sendDefaultPii is enabled ([#4071](https://github.com/getsentry/sentry-java/pull/4071)) + - This change gives you control over IP address collection directly on the client + + ## 7.20.0 ### Features diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 3f34efa9d5..6832051aa9 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -195,6 +195,7 @@ public final class io/sentry/android/core/DeviceInfoUtil { public static fun getInstance (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/DeviceInfoUtil; public fun getOperatingSystem ()Lio/sentry/protocol/OperatingSystem; public fun getSideLoadedInfo ()Lio/sentry/android/core/ContextUtils$SideLoadedInfo; + public fun getTotalMemory ()Ljava/lang/Long; public static fun isCharging (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Boolean; public static fun resetInstance ()V } @@ -248,12 +249,6 @@ public final class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sen public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V } -public final class io/sentry/android/core/PhoneStateBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun close ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - public final class io/sentry/android/core/ScreenshotEventProcessor : io/sentry/EventProcessor { public fun (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V public fun getOrder ()Ljava/lang/Long; @@ -382,14 +377,7 @@ public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : i public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Ljava/util/List;)V public fun close ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/TempSensorBreadcrumbsIntegration : android/hardware/SensorEventListener, io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun close ()V - public fun onAccuracyChanged (Landroid/hardware/Sensor;I)V - public fun onSensorChanged (Landroid/hardware/SensorEvent;)V + public static fun getDefaultActions ()Ljava/util/List; public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V } @@ -535,3 +523,14 @@ public class io/sentry/android/core/performance/WindowContentChangedCallback : i public fun onContentChanged ()V } +public final class io/sentry/android/core/util/AndroidLazyEvaluator { + public fun (Lio/sentry/android/core/util/AndroidLazyEvaluator$AndroidEvaluator;)V + public fun getValue (Landroid/content/Context;)Ljava/lang/Object; + public fun resetValue ()V + public fun setValue (Ljava/lang/Object;)V +} + +public abstract interface class io/sentry/android/core/util/AndroidLazyEvaluator$AndroidEvaluator { + public abstract fun evaluate (Landroid/content/Context;)Ljava/lang/Object; +} + diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 5d4658bcf9..c694b3d4b0 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -305,8 +305,6 @@ static void installDefaultIntegrations( options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); options.addIntegration( new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger())); - options.addIntegration(new TempSensorBreadcrumbsIntegration(context)); - options.addIntegration(new PhoneStateBreadcrumbsIntegration(context)); if (isReplayAvailable) { final ReplayIntegration replay = new ReplayIntegration(context, CurrentDateProvider.getInstance()); @@ -326,8 +324,8 @@ private static void readDefaultOptionValues( final @NotNull SentryAndroidOptions options, final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { - final PackageInfo packageInfo = - ContextUtils.getPackageInfo(context, options.getLogger(), buildInfoProvider); + final @Nullable PackageInfo packageInfo = + ContextUtils.getPackageInfo(context, buildInfoProvider); if (packageInfo != null) { // Sets App's release if not set by Manifest if (options.getRelease() == null) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java index 3455ff8b8d..13ffe0bd66 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java @@ -1,10 +1,8 @@ package io.sentry.android.core; -import static android.content.Context.ACTIVITY_SERVICE; import static java.util.concurrent.TimeUnit.SECONDS; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.content.Context; import android.os.Build; import android.os.Process; @@ -258,9 +256,12 @@ public void bindTransaction(final @NotNull ITransaction transaction) { transactionsCounter = 0; String totalMem = "0"; - ActivityManager.MemoryInfo memInfo = getMemInfo(); - if (memInfo != null) { - totalMem = Long.toString(memInfo.totalMem); + final @Nullable Long memory = + (options instanceof SentryAndroidOptions) + ? DeviceInfoUtil.getInstance(context, (SentryAndroidOptions) options).getTotalMemory() + : null; + if (memory != null) { + totalMem = Long.toString(memory); } String[] abis = Build.SUPPORTED_ABIS; @@ -327,27 +328,6 @@ public void close() { } } - /** - * Get MemoryInfo object representing the memory state of the application. - * - * @return MemoryInfo object representing the memory state of the application - */ - private @Nullable ActivityManager.MemoryInfo getMemInfo() { - try { - ActivityManager actManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE); - ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); - if (actManager != null) { - actManager.getMemoryInfo(memInfo); - return memInfo; - } - logger.log(SentryLevel.INFO, "Error getting MemoryInfo."); - return null; - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting MemoryInfo.", e); - return null; - } - } - @TestOnly int getTransactionsCounter() { return transactionsCounter; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java index 76beb87840..0810f1ac38 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java @@ -374,14 +374,13 @@ private void setApp(final @NotNull SentryBaseEvent event, final @NotNull Object if (app == null) { app = new App(); } - app.setAppName(ContextUtils.getApplicationName(context, options.getLogger())); + app.setAppName(ContextUtils.getApplicationName(context)); // TODO: not entirely correct, because we define background ANRs as not the ones of // IMPORTANCE_FOREGROUND, but this doesn't mean the app was in foreground when an ANR happened // but it's our best effort for now. We could serialize AppState in theory. app.setInForeground(!isBackgroundAnr(hint)); - final PackageInfo packageInfo = - ContextUtils.getPackageInfo(context, options.getLogger(), buildInfoProvider); + final PackageInfo packageInfo = ContextUtils.getPackageInfo(context, buildInfoProvider); if (packageInfo != null) { app.setAppIdentifier(packageInfo.packageName); } @@ -597,8 +596,7 @@ private void mergeUser(final @NotNull SentryBaseEvent event) { private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) { try { final ContextUtils.SideLoadedInfo sideLoadedInfo = - ContextUtils.retrieveSideLoadedInfo(context, options.getLogger(), buildInfoProvider); - + DeviceInfoUtil.getInstance(context, options).getSideLoadedInfo(); if (sideLoadedInfo != null) { final @NotNull Map tags = sideLoadedInfo.asTags(); for (Map.Entry entry : tags.entrySet()) { @@ -667,7 +665,8 @@ private void setDevice(final @NotNull SentryBaseEvent event) { private void mergeOS(final @NotNull SentryBaseEvent event) { final OperatingSystem currentOS = event.getContexts().getOperatingSystem(); - final OperatingSystem androidOS = getOperatingSystem(); + final OperatingSystem androidOS = + DeviceInfoUtil.getInstance(context, options).getOperatingSystem(); // make Android OS the main OS using the 'os' key event.getContexts().setOperatingSystem(androidOS); @@ -683,20 +682,5 @@ private void mergeOS(final @NotNull SentryBaseEvent event) { event.getContexts().put(osNameKey, currentOS); } } - - private @NotNull OperatingSystem getOperatingSystem() { - OperatingSystem os = new OperatingSystem(); - os.setName("Android"); - os.setVersion(Build.VERSION.RELEASE); - os.setBuild(Build.DISPLAY); - - try { - os.setKernelVersion(ContextUtils.getKernelVersion(options.getLogger())); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting OperatingSystem.", e); - } - - return os; - } // endregion } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java index 7f3889e143..29b6a4e6fd 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java @@ -2,7 +2,6 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.content.Context.ACTIVITY_SERVICE; -import static android.content.Context.RECEIVER_EXPORTED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import android.annotation.SuppressLint; @@ -20,7 +19,9 @@ import io.sentry.ILogger; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.android.core.util.AndroidLazyEvaluator; import io.sentry.protocol.App; +import io.sentry.util.LazyEvaluator; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -30,6 +31,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; @ApiStatus.Internal public final class ContextUtils { @@ -63,6 +65,106 @@ public boolean isSideLoaded() { private ContextUtils() {} + // to avoid doing a bunch of Binder calls we use LazyEvaluator to cache the values that are static + // during the app process running + + private static final @NotNull AndroidLazyEvaluator deviceName = + new AndroidLazyEvaluator<>( + (context) -> Settings.Global.getString(context.getContentResolver(), "device_name")); + + private static final @NotNull LazyEvaluator isForegroundImportance = + new LazyEvaluator<>( + () -> { + try { + final ActivityManager.RunningAppProcessInfo appProcessInfo = + new ActivityManager.RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(appProcessInfo); + return appProcessInfo.importance == IMPORTANCE_FOREGROUND; + } catch (Throwable ignored) { + // should never happen + } + return false; + }); + + /** + * Since this packageInfo uses flags 0 we can assume it's static and cache it as the package name + * or version code cannot change during runtime, only after app update (which will spin up a new + * process). + */ + @SuppressLint("NewApi") + private static final @NotNull AndroidLazyEvaluator staticPackageInfo33 = + new AndroidLazyEvaluator<>( + context -> { + try { + return context + .getPackageManager() + .getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(0)); + } catch (Throwable e) { + return null; + } + }); + + private static final @NotNull AndroidLazyEvaluator staticPackageInfo = + new AndroidLazyEvaluator<>( + context -> { + try { + return context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + } catch (Throwable e) { + return null; + } + }); + + private static final @NotNull AndroidLazyEvaluator applicationName = + new AndroidLazyEvaluator<>( + context -> { + try { + final ApplicationInfo applicationInfo = context.getApplicationInfo(); + final int stringId = applicationInfo.labelRes; + if (stringId == 0) { + if (applicationInfo.nonLocalizedLabel != null) { + return applicationInfo.nonLocalizedLabel.toString(); + } + return context.getPackageManager().getApplicationLabel(applicationInfo).toString(); + } else { + return context.getString(stringId); + } + } catch (Throwable e) { + return null; + } + }); + + /** + * Since this applicationInfo uses the same flag (METADATA) we can assume it's static and cache it + * as the manifest metadata cannot change during runtime, only after app update (which will spin + * up a new process). + */ + @SuppressLint("NewApi") + private static final @NotNull AndroidLazyEvaluator staticAppInfo33 = + new AndroidLazyEvaluator<>( + context -> { + try { + return context + .getPackageManager() + .getApplicationInfo( + context.getPackageName(), + PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA)); + } catch (Throwable e) { + return null; + } + }); + + private static final @NotNull AndroidLazyEvaluator staticAppInfo = + new AndroidLazyEvaluator<>( + context -> { + try { + return context + .getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (Throwable e) { + return null; + } + }); + /** * Return the Application's PackageInfo if possible, or null. * @@ -70,10 +172,12 @@ private ContextUtils() {} */ @Nullable static PackageInfo getPackageInfo( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - return getPackageInfo(context, 0, logger, buildInfoProvider); + final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { + return staticPackageInfo33.getValue(context); + } else { + return staticPackageInfo.getValue(context); + } } /** @@ -110,22 +214,14 @@ static PackageInfo getPackageInfo( * @return the Application's ApplicationInfo if possible, or throws */ @SuppressLint("NewApi") - @NotNull + @Nullable @SuppressWarnings("deprecation") static ApplicationInfo getApplicationInfo( - final @NotNull Context context, - final long flag, - final @NotNull BuildInfoProvider buildInfoProvider) - throws PackageManager.NameNotFoundException { + final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { - return context - .getPackageManager() - .getApplicationInfo( - context.getPackageName(), PackageManager.ApplicationInfoFlags.of(flag)); + return staticAppInfo33.getValue(context); } else { - return context - .getPackageManager() - .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + return staticAppInfo.getValue(context); } } @@ -169,15 +265,7 @@ static String getVersionName(final @NotNull PackageInfo packageInfo) { */ @ApiStatus.Internal public static boolean isForegroundImportance() { - try { - final ActivityManager.RunningAppProcessInfo appProcessInfo = - new ActivityManager.RunningAppProcessInfo(); - ActivityManager.getMyMemoryState(appProcessInfo); - return appProcessInfo.importance == IMPORTANCE_FOREGROUND; - } catch (Throwable ignored) { - // should never happen - } - return false; + return isForegroundImportance.getValue(); } /** @@ -213,7 +301,7 @@ public static boolean isForegroundImportance() { final @NotNull BuildInfoProvider buildInfoProvider) { String packageName = null; try { - final PackageInfo packageInfo = getPackageInfo(context, logger, buildInfoProvider); + final PackageInfo packageInfo = getPackageInfo(context, buildInfoProvider); final PackageManager packageManager = context.getPackageManager(); if (packageInfo != null && packageManager != null) { @@ -239,24 +327,8 @@ public static boolean isForegroundImportance() { * * @return Application name */ - static @Nullable String getApplicationName( - final @NotNull Context context, final @NotNull ILogger logger) { - try { - final ApplicationInfo applicationInfo = context.getApplicationInfo(); - final int stringId = applicationInfo.labelRes; - if (stringId == 0) { - if (applicationInfo.nonLocalizedLabel != null) { - return applicationInfo.nonLocalizedLabel.toString(); - } - return context.getPackageManager().getApplicationLabel(applicationInfo).toString(); - } else { - return context.getString(stringId); - } - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting application name.", e); - } - - return null; + static @Nullable String getApplicationName(final @NotNull Context context) { + return applicationName.getValue(context); } /** @@ -290,7 +362,7 @@ public static boolean isForegroundImportance() { } static @Nullable String getDeviceName(final @NotNull Context context) { - return Settings.Global.getString(context.getContentResolver(), "device_name"); + return deviceName.getValue(context); } static @NotNull String[] getArchitectures() { @@ -341,7 +413,7 @@ public static boolean isForegroundImportance() { // If this receiver is listening for broadcasts sent from the system or from other apps, even // other apps that you own—use the RECEIVER_EXPORTED flag. If instead this receiver is // listening only for broadcasts sent by your app, use the RECEIVER_NOT_EXPORTED flag. - return context.registerReceiver(receiver, filter, RECEIVER_EXPORTED); + return context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); } else { return context.registerReceiver(receiver, filter); } @@ -390,4 +462,15 @@ public static Context getApplicationContext(final @NotNull Context context) { } return context; } + + @TestOnly + static void resetInstance() { + deviceName.resetValue(); + isForegroundImportance.resetValue(); + staticPackageInfo33.resetValue(); + staticPackageInfo.resetValue(); + applicationName.resetValue(); + staticAppInfo33.resetValue(); + staticAppInfo.resetValue(); + } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java index 9125c66da2..e077073e74 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java @@ -250,7 +250,7 @@ private void setDist(final @NotNull SentryBaseEvent event, final @NotNull String } private void setAppExtras(final @NotNull App app, final @NotNull Hint hint) { - app.setAppName(ContextUtils.getApplicationName(context, options.getLogger())); + app.setAppName(ContextUtils.getApplicationName(context)); final @NotNull TimeSpan appStartTimeSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); if (appStartTimeSpan.hasStarted()) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java index 25aad2d08c..2293f21f3a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java @@ -9,7 +9,6 @@ import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Build; -import android.os.Environment; import android.os.LocaleList; import android.os.StatFs; import android.os.SystemClock; @@ -158,8 +157,13 @@ public OperatingSystem getOperatingSystem() { return os; } + @Nullable + public Long getTotalMemory() { + return totalMem; + } + @NotNull - protected OperatingSystem retrieveOperatingSystemInformation() { + private OperatingSystem retrieveOperatingSystemInformation() { final OperatingSystem os = new OperatingSystem(); os.setName("Android"); @@ -386,15 +390,14 @@ private Long getUnusedInternalStorage(final @NotNull StatFs stat) { @Nullable private StatFs getExternalStorageStat(final @Nullable File internalStorage) { - if (!isExternalStorageMounted()) { + try { File path = getExternalStorageDep(internalStorage); if (path != null) { // && path.canRead()) { canRead() will read return false return new StatFs(path.getPath()); } + } catch (Throwable e) { options.getLogger().log(SentryLevel.INFO, "Not possible to read external files directory"); - return null; } - options.getLogger().log(SentryLevel.INFO, "External storage is not mounted or emulated."); return null; } @@ -446,13 +449,6 @@ private Long getTotalExternalStorage(final @NotNull StatFs stat) { } } - private boolean isExternalStorageMounted() { - final String storageState = Environment.getExternalStorageState(); - return (Environment.MEDIA_MOUNTED.equals(storageState) - || Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) - && !Environment.isExternalStorageEmulated(); - } - /** * Get the unused amount of external storage, in bytes, or null if no external storage is mounted. * diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index de1af477b2..2e03184bc4 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -110,7 +110,7 @@ public static Map serializeScope( if (app == null) { app = new App(); } - app.setAppName(ContextUtils.getApplicationName(context, options.getLogger())); + app.setAppName(ContextUtils.getApplicationName(context)); final @NotNull TimeSpan appStartTimeSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); 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 1bad409125..7c57eb3798 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 @@ -2,7 +2,6 @@ import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.os.Bundle; import io.sentry.ILogger; import io.sentry.InitPriority; @@ -521,18 +520,14 @@ static boolean isAutoInit(final @NotNull Context context, final @NotNull ILogger * * @param context the application context * @return the Bundle attached to the PackageManager - * @throws PackageManager.NameNotFoundException if the package name is non-existent */ private static @Nullable Bundle getMetadata( final @NotNull Context context, final @NotNull ILogger logger, - final @Nullable BuildInfoProvider buildInfoProvider) - throws PackageManager.NameNotFoundException { + final @Nullable BuildInfoProvider buildInfoProvider) { final ApplicationInfo app = ContextUtils.getApplicationInfo( - context, - PackageManager.GET_META_DATA, - buildInfoProvider != null ? buildInfoProvider : new BuildInfoProvider(logger)); - return app.metaData; + context, buildInfoProvider != null ? buildInfoProvider : new BuildInfoProvider(logger)); + return app != null ? app.metaData : null; } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java deleted file mode 100644 index a0111b3c0a..0000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.sentry.android.core; - -import static android.Manifest.permission.READ_PHONE_STATE; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.content.Context; -import android.telephony.TelephonyManager; -import io.sentry.Breadcrumb; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.util.Permissions; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -public final class PhoneStateBreadcrumbsIntegration implements Integration, Closeable { - - private final @NotNull Context context; - private @Nullable SentryAndroidOptions options; - @TestOnly @Nullable PhoneStateChangeListener listener; - private @Nullable TelephonyManager telephonyManager; - private boolean isClosed = false; - private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); - - public PhoneStateBreadcrumbsIntegration(final @NotNull Context context) { - this.context = - Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required"); - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "enableSystemEventBreadcrumbs enabled: %s", - this.options.isEnableSystemEventBreadcrumbs()); - - if (this.options.isEnableSystemEventBreadcrumbs() - && Permissions.hasPermission(context, READ_PHONE_STATE)) { - try { - options - .getExecutorService() - .submit( - () -> { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - if (!isClosed) { - startTelephonyListener(scopes, options); - } - } - }); - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Failed to start PhoneStateBreadcrumbsIntegration on executor thread.", - e); - } - } - } - - @SuppressWarnings("deprecation") - private void startTelephonyListener( - final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager != null) { - try { - listener = new PhoneStateChangeListener(scopes); - telephonyManager.listen(listener, android.telephony.PhoneStateListener.LISTEN_CALL_STATE); - - options.getLogger().log(SentryLevel.DEBUG, "PhoneStateBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion("PhoneStateBreadcrumbs"); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.INFO, e, "TelephonyManager is not available or ready to use."); - } - } else { - options.getLogger().log(SentryLevel.INFO, "TelephonyManager is not available"); - } - } - - @SuppressWarnings("deprecation") - @Override - public void close() throws IOException { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - isClosed = true; - } - if (telephonyManager != null && listener != null) { - telephonyManager.listen(listener, android.telephony.PhoneStateListener.LISTEN_NONE); - listener = null; - - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "PhoneStateBreadcrumbsIntegration removed."); - } - } - } - - @SuppressWarnings("deprecation") - static final class PhoneStateChangeListener extends android.telephony.PhoneStateListener { - - private final @NotNull IScopes scopes; - - PhoneStateChangeListener(final @NotNull IScopes scopes) { - this.scopes = scopes; - } - - @SuppressWarnings("deprecation") - @Override - public void onCallStateChanged(int state, String incomingNumber) { - // incomingNumber is never used and it's always empty if you don't have permission: - // android.permission.READ_CALL_LOG - if (state == TelephonyManager.CALL_STATE_RINGING) { - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("system"); - breadcrumb.setCategory("device.event"); - breadcrumb.setData("action", "CALL_STATE_RINGING"); - breadcrumb.setMessage("Device ringing"); - breadcrumb.setLevel(SentryLevel.INFO); - scopes.addBreadcrumb(breadcrumb); - } - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index 04988c9a98..29446f2e71 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -1,31 +1,17 @@ package io.sentry.android.core; -import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED; -import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DISABLED; -import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_ENABLED; -import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE; import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED; -import static android.content.Intent.ACTION_APP_ERROR; import static android.content.Intent.ACTION_BATTERY_CHANGED; -import static android.content.Intent.ACTION_BATTERY_LOW; -import static android.content.Intent.ACTION_BATTERY_OKAY; -import static android.content.Intent.ACTION_BOOT_COMPLETED; -import static android.content.Intent.ACTION_BUG_REPORT; import static android.content.Intent.ACTION_CAMERA_BUTTON; import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.content.Intent.ACTION_DATE_CHANGED; import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW; import static android.content.Intent.ACTION_DEVICE_STORAGE_OK; import static android.content.Intent.ACTION_DOCK_EVENT; +import static android.content.Intent.ACTION_DREAMING_STARTED; +import static android.content.Intent.ACTION_DREAMING_STOPPED; import static android.content.Intent.ACTION_INPUT_METHOD_CHANGED; import static android.content.Intent.ACTION_LOCALE_CHANGED; -import static android.content.Intent.ACTION_MEDIA_BAD_REMOVAL; -import static android.content.Intent.ACTION_MEDIA_MOUNTED; -import static android.content.Intent.ACTION_MEDIA_UNMOUNTABLE; -import static android.content.Intent.ACTION_MEDIA_UNMOUNTED; -import static android.content.Intent.ACTION_POWER_CONNECTED; -import static android.content.Intent.ACTION_POWER_DISCONNECTED; -import static android.content.Intent.ACTION_REBOOT; import static android.content.Intent.ACTION_SCREEN_OFF; import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.ACTION_SHUTDOWN; @@ -144,52 +130,27 @@ private void startSystemEventsReceiver( } @SuppressWarnings("deprecation") - private static @NotNull List getDefaultActions() { + public static @NotNull List getDefaultActions() { final List actions = new ArrayList<>(); - actions.add(ACTION_APPWIDGET_DELETED); - actions.add(ACTION_APPWIDGET_DISABLED); - actions.add(ACTION_APPWIDGET_ENABLED); - actions.add("android.appwidget.action.APPWIDGET_HOST_RESTORED"); - actions.add("android.appwidget.action.APPWIDGET_RESTORED"); - actions.add(ACTION_APPWIDGET_UPDATE); - actions.add("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"); - actions.add(ACTION_POWER_CONNECTED); - actions.add(ACTION_POWER_DISCONNECTED); actions.add(ACTION_SHUTDOWN); actions.add(ACTION_AIRPLANE_MODE_CHANGED); - actions.add(ACTION_BATTERY_LOW); - actions.add(ACTION_BATTERY_OKAY); actions.add(ACTION_BATTERY_CHANGED); - actions.add(ACTION_BOOT_COMPLETED); actions.add(ACTION_CAMERA_BUTTON); actions.add(ACTION_CONFIGURATION_CHANGED); - actions.add("android.intent.action.CONTENT_CHANGED"); actions.add(ACTION_DATE_CHANGED); actions.add(ACTION_DEVICE_STORAGE_LOW); actions.add(ACTION_DEVICE_STORAGE_OK); actions.add(ACTION_DOCK_EVENT); - actions.add("android.intent.action.DREAMING_STARTED"); - actions.add("android.intent.action.DREAMING_STOPPED"); + actions.add(ACTION_DREAMING_STARTED); + actions.add(ACTION_DREAMING_STOPPED); actions.add(ACTION_INPUT_METHOD_CHANGED); actions.add(ACTION_LOCALE_CHANGED); - actions.add(ACTION_REBOOT); actions.add(ACTION_SCREEN_OFF); actions.add(ACTION_SCREEN_ON); actions.add(ACTION_TIMEZONE_CHANGED); actions.add(ACTION_TIME_CHANGED); actions.add("android.os.action.DEVICE_IDLE_MODE_CHANGED"); actions.add("android.os.action.POWER_SAVE_MODE_CHANGED"); - // The user pressed the "Report" button in the crash/ANR dialog. - actions.add(ACTION_APP_ERROR); - // Show activity for reporting a bug. - actions.add(ACTION_BUG_REPORT); - - // consider if somebody mounted or ejected a sdcard - actions.add(ACTION_MEDIA_BAD_REMOVAL); - actions.add(ACTION_MEDIA_MOUNTED); - actions.add(ACTION_MEDIA_UNMOUNTABLE); - actions.add(ACTION_MEDIA_UNMOUNTED); - return actions; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java deleted file mode 100644 index 1d72fa2239..0000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java +++ /dev/null @@ -1,146 +0,0 @@ -package io.sentry.android.core; - -import static android.content.Context.SENSOR_SERVICE; -import static io.sentry.TypeCheckHint.ANDROID_SENSOR_EVENT; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import io.sentry.Breadcrumb; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -public final class TempSensorBreadcrumbsIntegration - implements Integration, Closeable, SensorEventListener { - - private final @NotNull Context context; - private @Nullable IScopes scopes; - private @Nullable SentryAndroidOptions options; - - @TestOnly @Nullable SensorManager sensorManager; - private boolean isClosed = false; - private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); - - public TempSensorBreadcrumbsIntegration(final @NotNull Context context) { - this.context = - Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required"); - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "enableSystemEventsBreadcrumbs enabled: %s", - this.options.isEnableSystemEventBreadcrumbs()); - - if (this.options.isEnableSystemEventBreadcrumbs()) { - - try { - options - .getExecutorService() - .submit( - () -> { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - if (!isClosed) { - startSensorListener(options); - } - } - }); - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Failed to start TempSensorBreadcrumbsIntegration on executor thread.", - e); - } - } - } - - private void startSensorListener(final @NotNull SentryOptions options) { - try { - sensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE); - if (sensorManager != null) { - final Sensor defaultSensor = - sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE); - if (defaultSensor != null) { - sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); - - options.getLogger().log(SentryLevel.DEBUG, "TempSensorBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion("TempSensorBreadcrumbs"); - } else { - options.getLogger().log(SentryLevel.INFO, "TYPE_AMBIENT_TEMPERATURE is not available."); - } - } else { - options.getLogger().log(SentryLevel.INFO, "SENSOR_SERVICE is not available."); - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, e, "Failed to init. the SENSOR_SERVICE."); - } - } - - @Override - public void close() throws IOException { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - isClosed = true; - } - if (sensorManager != null) { - sensorManager.unregisterListener(this); - sensorManager = null; - - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "TempSensorBreadcrumbsIntegration removed."); - } - } - } - - @Override - public void onSensorChanged(final @NotNull SensorEvent event) { - final float[] values = event.values; - // return if data is not available or zero'ed - if (values == null || values.length == 0 || values[0] == 0f) { - return; - } - - if (scopes != null) { - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("system"); - breadcrumb.setCategory("device.event"); - breadcrumb.setData("action", "TYPE_AMBIENT_TEMPERATURE"); - breadcrumb.setData("accuracy", event.accuracy); - breadcrumb.setData("timestamp", event.timestamp); - breadcrumb.setLevel(SentryLevel.INFO); - breadcrumb.setData("degree", event.values[0]); // Celsius - - final Hint hint = new Hint(); - hint.set(ANDROID_SENSOR_EVENT, event); - - scopes.addBreadcrumb(breadcrumb, hint); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) {} -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java index 46275504cb..653cfd2ee1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java @@ -50,6 +50,11 @@ private void startTracking(final @NotNull Activity activity) { delegate = new NoOpWindowCallback(); } + if (delegate instanceof SentryWindowCallback) { + // already instrumented + return; + } + final SentryGestureListener gestureListener = new SentryGestureListener(activity, scopes, options); window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options)); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java index 945ebeef64..f271c3da9e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java @@ -18,7 +18,6 @@ public final class AndroidViewGestureTargetLocator implements GestureTargetLocat private static final String ORIGIN = "old_view_system"; private final boolean isAndroidXAvailable; - private final int[] coordinates = new int[2]; public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) { this.isAndroidXAvailable = isAndroidXAvailable; @@ -26,18 +25,16 @@ public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) { @Override public @Nullable UiElement locate( - @NotNull Object root, float x, float y, UiElement.Type targetType) { + @Nullable Object root, float x, float y, UiElement.Type targetType) { if (!(root instanceof View)) { return null; } final View view = (View) root; - if (touchWithinBounds(view, x, y)) { - if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) { - return createUiElement(view); - } else if (targetType == UiElement.Type.SCROLLABLE - && isViewScrollable(view, isAndroidXAvailable)) { - return createUiElement(view); - } + if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) { + return createUiElement(view); + } else if (targetType == UiElement.Type.SCROLLABLE + && isViewScrollable(view, isAndroidXAvailable)) { + return createUiElement(view); } return null; } @@ -52,17 +49,6 @@ private UiElement createUiElement(final @NotNull View targetView) { } } - private boolean touchWithinBounds(final @NotNull View view, final float x, final float y) { - view.getLocationOnScreen(coordinates); - int vx = coordinates[0]; - int vy = coordinates[1]; - - int w = view.getWidth(); - int h = view.getHeight(); - - return !(x < vx || x > vx + w || y < vy || y > vy + h); - } - private static boolean isViewTappable(final @NotNull View view) { return view.isClickable() && view.getVisibility() == View.VISIBLE; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java index 6e7dab2ef5..8aa8e89d69 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java @@ -7,7 +7,6 @@ import io.sentry.android.core.SentryAndroidOptions; import io.sentry.internal.gestures.GestureTargetLocator; import io.sentry.internal.gestures.UiElement; -import io.sentry.util.Objects; import java.util.LinkedList; import java.util.Queue; import org.jetbrains.annotations.ApiStatus; @@ -17,6 +16,32 @@ @ApiStatus.Internal public final class ViewUtils { + private static final int[] coordinates = new int[2]; + + /** + * Verifies if the given touch coordinates are within the bounds of the given view. + * + * @param view the view to check if the touch coordinates are within its bounds + * @param x - the x coordinate of a {@link MotionEvent} + * @param y - the y coordinate of {@link MotionEvent} + * @return true if the touch coordinates are within the bounds of the view, false otherwise + */ + private static boolean touchWithinBounds( + final @Nullable View view, final float x, final float y) { + if (view == null) { + return false; + } + + view.getLocationOnScreen(coordinates); + int vx = coordinates[0]; + int vy = coordinates[1]; + + int w = view.getWidth(); + int h = view.getHeight(); + + return !(x < vx || x > vx + w || y < vy || y > vy + h); + } + /** * Finds a target view, that has been selected/clicked by the given coordinates x and y and the * given {@code viewTargetSelector}. @@ -40,7 +65,12 @@ public final class ViewUtils { @Nullable UiElement target = null; while (queue.size() > 0) { - final View view = Objects.requireNonNull(queue.poll(), "view is required"); + final View view = queue.poll(); + + if (!touchWithinBounds(view, x, y)) { + // if the touch is not hitting the view, skip traversal of its children + continue; + } if (view instanceof ViewGroup) { final ViewGroup viewGroup = (ViewGroup) view; @@ -54,7 +84,7 @@ public final class ViewUtils { if (newTarget != null) { if (targetType == UiElement.Type.CLICKABLE) { target = newTarget; - } else { + } else if (targetType == UiElement.Type.SCROLLABLE) { return newTarget; } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java new file mode 100644 index 0000000000..beb9ff8e8e --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java @@ -0,0 +1,68 @@ +package io.sentry.android.core.util; + +import android.content.Context; +import io.sentry.util.LazyEvaluator; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Class that evaluates a function lazily. It means the evaluator function is called only when + * getValue is called, and it's cached. Same as {@link LazyEvaluator} but accepts Context as an + * argument for {@link AndroidLazyEvaluator#getValue}. + */ +@ApiStatus.Internal +public final class AndroidLazyEvaluator { + + private volatile @Nullable T value = null; + private final @NotNull AndroidEvaluator evaluator; + + /** + * Class that evaluates a function lazily. It means the evaluator function is called only when + * getValue is called, and it's cached. + * + * @param evaluator The function to evaluate. + */ + public AndroidLazyEvaluator(final @NotNull AndroidEvaluator evaluator) { + this.evaluator = evaluator; + } + + /** + * Executes the evaluator function and caches its result, so that it's called only once, unless + * resetValue is called. + * + * @return The result of the evaluator function. + */ + public @Nullable T getValue(final @NotNull Context context) { + if (value == null) { + synchronized (this) { + if (value == null) { + value = evaluator.evaluate(context); + } + } + } + + return value; + } + + public void setValue(final @Nullable T value) { + synchronized (this) { + this.value = value; + } + } + + /** + * Resets the internal value and forces the evaluator function to be called the next time + * getValue() is called. + */ + public void resetValue() { + synchronized (this) { + this.value = null; + } + } + + public interface AndroidEvaluator { + @Nullable + T evaluate(@NotNull Context context); + } +} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt index 1a1d1dc7fe..eae6e9cc1c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt @@ -132,6 +132,7 @@ class ActivityLifecycleIntegrationTest { @BeforeTest fun `reset instance`() { AppStartMetrics.getInstance().clear() + ContextUtils.resetInstance() context = ApplicationProvider.getApplicationContext() val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index 95ca59a06e..bb5efe3aab 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -163,6 +163,7 @@ class AndroidOptionsInitializerTest { @BeforeTest fun `set up`() { + ContextUtils.resetInstance() val appContext = ApplicationProvider.getApplicationContext() fixture = Fixture(appContext, appContext.cacheDir) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt index 02044c81bf..fe69a3157d 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt @@ -172,6 +172,7 @@ class AnrV2EventProcessorTest { @BeforeTest fun `set up`() { + DeviceInfoUtil.resetInstance() fixture.context = ApplicationProvider.getApplicationContext() } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt index 04702dc0ee..d2657bf189 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt @@ -42,6 +42,7 @@ class ContextUtilsTest { @BeforeTest fun `set up`() { + ContextUtils.resetInstance() context = ApplicationProvider.getApplicationContext() logger = NoOpLogger.getInstance() ShadowBuild.reset() @@ -51,20 +52,20 @@ class ContextUtilsTest { @Test fun `Given a valid context, returns a valid PackageInfo`() { - val packageInfo = ContextUtils.getPackageInfo(context, mock(), mock()) + val packageInfo = ContextUtils.getPackageInfo(context, mock()) assertNotNull(packageInfo) } @Test fun `Given an invalid context, do not throw Error`() { // as Context is not fully mocked, it'll throw NPE but catch it and return null - val packageInfo = ContextUtils.getPackageInfo(mock(), mock(), mock()) + val packageInfo = ContextUtils.getPackageInfo(mock(), mock()) assertNull(packageInfo) } @Test fun `Given a valid PackageInfo, returns a valid versionCode`() { - val packageInfo = ContextUtils.getPackageInfo(context, mock(), mock()) + val packageInfo = ContextUtils.getPackageInfo(context, mock()) val versionCode = ContextUtils.getVersionCode(packageInfo!!, mock()) assertNotNull(versionCode) @@ -73,7 +74,7 @@ class ContextUtilsTest { @Test fun `Given a valid PackageInfo, returns a valid versionName`() { // VersionName is null during tests, so we mock it the second time - val packageInfo = ContextUtils.getPackageInfo(context, mock(), mock())!! + val packageInfo = ContextUtils.getPackageInfo(context, mock())!! val versionName = ContextUtils.getVersionName(packageInfo) assertNull(versionName) val mockedPackageInfo = spy(packageInfo) { it.versionName = "" } @@ -83,13 +84,13 @@ class ContextUtilsTest { @Test fun `when context is valid, getApplicationName returns application name`() { - val appName = ContextUtils.getApplicationName(context, logger) + val appName = ContextUtils.getApplicationName(context) assertEquals("io.sentry.android.core.test", appName) } @Test fun `when context is invalid, getApplicationName returns null`() { - val appName = ContextUtils.getApplicationName(mock(), logger) + val appName = ContextUtils.getApplicationName(mock()) assertNull(appName) } @@ -193,7 +194,7 @@ class ContextUtilsTest { val context = mock() whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.TIRAMISU) ContextUtils.registerReceiver(context, buildInfo, receiver, filter) - verify(context).registerReceiver(eq(receiver), eq(filter), eq(Context.RECEIVER_EXPORTED)) + verify(context).registerReceiver(eq(receiver), eq(filter), eq(Context.RECEIVER_NOT_EXPORTED)) } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt index d3523dd1b5..f981e9d096 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt @@ -66,10 +66,7 @@ class DefaultAndroidEventProcessorTest { lateinit var sentryTracer: SentryTracer - fun getSut( - context: Context, - isSendDefaultPii: Boolean = false - ): DefaultAndroidEventProcessor { + fun getSut(context: Context, isSendDefaultPii: Boolean = false): DefaultAndroidEventProcessor { options.isSendDefaultPii = isSendDefaultPii whenever(scopes.options).thenReturn(options) sentryTracer = SentryTracer(TransactionContext("", ""), scopes) 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 fd71fa08a8..1ab36e8fc9 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 @@ -13,6 +13,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -37,6 +38,11 @@ class ManifestMetadataReaderTest { private val fixture = Fixture() + @BeforeTest + fun `set up`() { + ContextUtils.resetInstance() + } + @Test fun `isAutoInit won't throw exception and is enabled by default`() { fixture.options.setDebug(true) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt deleted file mode 100644 index c764d11c2d..0000000000 --- a/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package io.sentry.android.core - -import android.content.Context -import android.telephony.PhoneStateListener -import android.telephony.TelephonyManager -import io.sentry.Breadcrumb -import io.sentry.IScopes -import io.sentry.ISentryExecutorService -import io.sentry.SentryLevel -import io.sentry.test.DeferredExecutorService -import io.sentry.test.ImmediateExecutorService -import org.mockito.kotlin.any -import org.mockito.kotlin.check -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.never -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull - -class PhoneStateBreadcrumbsIntegrationTest { - - private class Fixture { - val context = mock() - val manager = mock() - val options = SentryAndroidOptions() - - fun getSut(executorService: ISentryExecutorService = ImmediateExecutorService()): PhoneStateBreadcrumbsIntegration { - options.executorService = executorService - whenever(context.getSystemService(eq(Context.TELEPHONY_SERVICE))).thenReturn(manager) - - return PhoneStateBreadcrumbsIntegration(context) - } - } - - private val fixture = Fixture() - - @Test - fun `When system events breadcrumb is enabled, it registers callback`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - verify(fixture.manager).listen(any(), eq(PhoneStateListener.LISTEN_CALL_STATE)) - assertNotNull(sut.listener) - } - - @Test - fun `Phone state callback is registered in the executorService`() { - val sut = fixture.getSut(mock()) - val scopes = mock() - sut.register(scopes, fixture.options) - - assertNull(sut.listener) - } - - @Test - fun `When system events breadcrumb is disabled, it doesn't register callback`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register( - scopes, - fixture.options.apply { - isEnableSystemEventBreadcrumbs = false - } - ) - verify(fixture.manager, never()).listen(any(), any()) - assertNull(sut.listener) - } - - @Test - fun `When ActivityBreadcrumbsIntegration is closed, it should unregister the callback`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - sut.close() - verify(fixture.manager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)) - assertNull(sut.listener) - } - - @Test - fun `when scopes is closed right after start, integration is not registered`() { - val deferredExecutorService = DeferredExecutorService() - val sut = fixture.getSut(executorService = deferredExecutorService) - sut.register(mock(), fixture.options) - assertNull(sut.listener) - sut.close() - deferredExecutorService.runAll() - assertNull(sut.listener) - } - - @Test - fun `When on call state received, added breadcrumb with type and category`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - sut.listener!!.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null) - - verify(scopes).addBreadcrumb( - check { - assertEquals("device.event", it.category) - assertEquals("system", it.type) - assertEquals(SentryLevel.INFO, it.level) - // cant assert data, its not a public API - } - ) - } - - @Test - fun `When on idle state received, added breadcrumb with type and category`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - sut.listener!!.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE, null) - verify(scopes, never()).addBreadcrumb(any()) - } - - @Test - fun `When on offhook state received, added breadcrumb with type and category`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - sut.listener!!.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null) - verify(scopes, never()).addBreadcrumb(any()) - } -} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt index bcba616905..a3a0f7d4f9 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt @@ -465,7 +465,7 @@ class SentryAndroidTest { fixture.initSut(context = mock()) { options -> optionsRef = options options.dsn = "https://key@sentry.io/123" - assertEquals(21, options.integrations.size) + assertEquals(19, options.integrations.size) options.integrations.removeAll { it is UncaughtExceptionHandlerIntegration || it is ShutdownHookIntegration || @@ -483,8 +483,6 @@ class SentryAndroidTest { it is AppComponentsBreadcrumbsIntegration || it is SystemEventsBreadcrumbsIntegration || it is NetworkBreadcrumbsIntegration || - it is TempSensorBreadcrumbsIntegration || - it is PhoneStateBreadcrumbsIntegration || it is SpotlightIntegration || it is ReplayIntegration } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt index 5b546523d0..927e879237 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt @@ -20,6 +20,7 @@ class SentryInitProviderTest { @BeforeTest fun `set up`() { Sentry.close() + ContextUtils.resetInstance() } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index 857cc658c2..72e3d68dc0 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -42,6 +42,7 @@ class SentryLogcatAdapterTest { fun `set up`() { Sentry.close() AppStartMetrics.getInstance().clear() + ContextUtils.resetInstance() breadcrumbs.clear() fixture.initSut { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt deleted file mode 100644 index 5d049e3dad..0000000000 --- a/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt +++ /dev/null @@ -1,133 +0,0 @@ -package io.sentry.android.core - -import android.content.Context -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import io.sentry.Breadcrumb -import io.sentry.Hint -import io.sentry.IScopes -import io.sentry.ISentryExecutorService -import io.sentry.SentryLevel -import io.sentry.TypeCheckHint -import io.sentry.test.DeferredExecutorService -import io.sentry.test.ImmediateExecutorService -import io.sentry.test.getDeclaredCtor -import io.sentry.test.injectForField -import org.mockito.kotlin.any -import org.mockito.kotlin.check -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.never -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull - -class TempSensorBreadcrumbsIntegrationTest { - private class Fixture { - val context = mock() - val manager = mock() - val sensor = mock() - val options = SentryAndroidOptions() - - fun getSut(executorService: ISentryExecutorService = ImmediateExecutorService()): TempSensorBreadcrumbsIntegration { - options.executorService = executorService - whenever(context.getSystemService(Context.SENSOR_SERVICE)).thenReturn(manager) - whenever(manager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE)).thenReturn(sensor) - return TempSensorBreadcrumbsIntegration(context) - } - } - - private val fixture = Fixture() - - @Test - fun `When system events breadcrumb is enabled, it registers callback`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - verify(fixture.manager).registerListener(any(), any(), eq(SensorManager.SENSOR_DELAY_NORMAL)) - assertNotNull(sut.sensorManager) - } - - @Test - fun `temp sensor listener is registered in the executorService`() { - val sut = fixture.getSut(executorService = mock()) - val scopes = mock() - sut.register(scopes, fixture.options) - - assertNull(sut.sensorManager) - } - - @Test - fun `When system events breadcrumb is disabled, it should not register a callback`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register( - scopes, - fixture.options.apply { - isEnableSystemEventBreadcrumbs = false - } - ) - verify(fixture.manager, never()).registerListener(any(), any(), any()) - assertNull(sut.sensorManager) - } - - @Test - fun `When TempSensorBreadcrumbsIntegration is closed, it should unregister the callback`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - sut.close() - verify(fixture.manager).unregisterListener(any()) - assertNull(sut.sensorManager) - } - - @Test - fun `when scopes is closed right after start, integration is not registered`() { - val deferredExecutorService = DeferredExecutorService() - val sut = fixture.getSut(executorService = deferredExecutorService) - sut.register(mock(), fixture.options) - assertNull(sut.sensorManager) - sut.close() - deferredExecutorService.runAll() - assertNull(sut.sensorManager) - } - - @Test - fun `When onSensorChanged received, add a breadcrumb with type and category`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - val sensorCtor = "android.hardware.SensorEvent".getDeclaredCtor(emptyArray()) - val sensorEvent: SensorEvent = sensorCtor.newInstance() as SensorEvent - sensorEvent.injectForField("values", FloatArray(2) { 1F }) - sut.onSensorChanged(sensorEvent) - - verify(scopes).addBreadcrumb( - check { - assertEquals("device.event", it.category) - assertEquals("system", it.type) - assertEquals(SentryLevel.INFO, it.level) - }, - check { - assertEquals(sensorEvent, it.get(TypeCheckHint.ANDROID_SENSOR_EVENT)) - } - ) - } - - @Test - fun `When onSensorChanged received and null values, do not add a breadcrumb`() { - val sut = fixture.getSut() - val scopes = mock() - sut.register(scopes, fixture.options) - val event = mock() - assertNull(event.values) - sut.onSensorChanged(event) - - verify(scopes, never()).addBreadcrumb(any()) - } -} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt index 379e3db353..97e4d46845 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt @@ -140,6 +140,7 @@ class UserInteractionIntegrationTest { ) ) + sut.register(fixture.scopes, fixture.options) sut.onActivityPaused(fixture.activity) verify(fixture.window).callback = null @@ -160,6 +161,7 @@ class UserInteractionIntegrationTest { ) ) + sut.register(fixture.scopes, fixture.options) sut.onActivityPaused(fixture.activity) verify(fixture.window).callback = delegate @@ -170,8 +172,30 @@ class UserInteractionIntegrationTest { val callback = mock() val sut = fixture.getSut(callback) + sut.register(fixture.scopes, fixture.options) sut.onActivityPaused(fixture.activity) verify(callback).stopTracking() } + + @Test + fun `does not instrument if the callback is already ours`() { + val delegate = mock() + val context = mock() + val resources = Fixture.mockResources() + whenever(context.resources).thenReturn(resources) + val existingCallback = SentryWindowCallback( + delegate, + context, + mock(), + mock() + ) + val sut = fixture.getSut(existingCallback) + + sut.register(fixture.scopes, fixture.options) + sut.onActivityResumed(fixture.activity) + + val argumentCaptor = argumentCaptor() + verify(fixture.window, never()).callback = argumentCaptor.capture() + } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt index 74edfb4302..6c20e9eb04 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt @@ -185,7 +185,7 @@ class SentryGestureListenerClickTest { sut.onSingleTapUp(event) - verify(fixture.scopes, never()).addBreadcrumb(any()) + verify(fixture.scopes, never()).addBreadcrumb(any(), anyOrNull()) } @Test @@ -214,7 +214,7 @@ class SentryGestureListenerClickTest { sut.onSingleTapUp(event) - verify(fixture.scopes, never()).addBreadcrumb(any()) + verify(fixture.scopes, never()).addBreadcrumb(any(), anyOrNull()) } @Test @@ -223,7 +223,7 @@ class SentryGestureListenerClickTest { val event = mock() val sut = fixture.getSut(event, attachViewsToRoot = false) - fixture.window.mockDecorView(event = event, touchWithinBounds = false) { + fixture.window.mockDecorView(event = event, touchWithinBounds = true) { whenever(it.childCount).thenReturn(1) whenever(it.getChildAt(0)).thenReturn(fixture.target) } @@ -244,7 +244,7 @@ class SentryGestureListenerClickTest { val event = mock() val sut = fixture.getSut(event, attachViewsToRoot = false) - fixture.window.mockDecorView(event = event, touchWithinBounds = false) { + fixture.window.mockDecorView(event = event, touchWithinBounds = true) { whenever(it.childCount).thenReturn(1) whenever(it.getChildAt(0)).thenReturn(fixture.target) } @@ -253,4 +253,18 @@ class SentryGestureListenerClickTest { verify(fixture.scope).propagationContext = any() } + + @Test + fun `if touch is not within view group bounds does not traverse its children`() { + val event = mock() + val sut = fixture.getSut(event, attachViewsToRoot = false) + fixture.window.mockDecorView(event = event, touchWithinBounds = false) { + whenever(it.childCount).thenReturn(1) + whenever(it.getChildAt(0)).thenReturn(fixture.target) + } + + sut.onSingleTapUp(event) + + verify(fixture.scopes, never()).addBreadcrumb(any(), anyOrNull()) + } } diff --git a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java index bd056aeb91..68e32269a1 100644 --- a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java +++ b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java @@ -42,7 +42,7 @@ public ComposeGestureTargetLocator(final @NotNull ILogger logger) { @Override public @Nullable UiElement locate( - @NotNull Object root, float x, float y, UiElement.Type targetType) { + @Nullable Object root, float x, float y, UiElement.Type targetType) { // lazy init composeHelper as it's using some reflection under the hood if (composeHelper == null) { diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index 84372b7766..482787ec5a 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -6,8 +6,6 @@ - - diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 2b885bed95..ecf75c6b7b 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -4152,6 +4152,7 @@ public final class io/sentry/instrumentation/file/SentryFileOutputStream : java/ public final class io/sentry/instrumentation/file/SentryFileOutputStream$Factory { public fun ()V public static fun create (Ljava/io/FileOutputStream;Ljava/io/File;)Ljava/io/FileOutputStream; + public static fun create (Ljava/io/FileOutputStream;Ljava/io/File;Lio/sentry/IScopes;)Ljava/io/FileOutputStream; public static fun create (Ljava/io/FileOutputStream;Ljava/io/File;Z)Ljava/io/FileOutputStream; public static fun create (Ljava/io/FileOutputStream;Ljava/io/FileDescriptor;)Ljava/io/FileOutputStream; public static fun create (Ljava/io/FileOutputStream;Ljava/lang/String;)Ljava/io/FileOutputStream; diff --git a/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileInputStream.java b/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileInputStream.java index ea7d7f09a5..0ee5df6d79 100644 --- a/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileInputStream.java +++ b/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileInputStream.java @@ -3,6 +3,7 @@ import io.sentry.IScopes; import io.sentry.ISpan; import io.sentry.ScopesAdapter; +import io.sentry.SentryOptions; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -127,20 +128,27 @@ public static final class Factory { public static FileInputStream create( final @NotNull FileInputStream delegate, final @Nullable String name) throws FileNotFoundException { - return new SentryFileInputStream( - init(name != null ? new File(name) : null, delegate, ScopesAdapter.getInstance())); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileInputStream(init(name != null ? new File(name) : null, delegate, scopes)) + : delegate; } public static FileInputStream create( final @NotNull FileInputStream delegate, final @Nullable File file) throws FileNotFoundException { - return new SentryFileInputStream(init(file, delegate, ScopesAdapter.getInstance())); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileInputStream(init(file, delegate, scopes)) + : delegate; } public static FileInputStream create( final @NotNull FileInputStream delegate, final @NotNull FileDescriptor descriptor) { - return new SentryFileInputStream( - init(descriptor, delegate, ScopesAdapter.getInstance()), descriptor); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileInputStream(init(descriptor, delegate, scopes), descriptor) + : delegate; } static FileInputStream create( @@ -148,7 +156,14 @@ static FileInputStream create( final @Nullable File file, final @NotNull IScopes scopes) throws FileNotFoundException { - return new SentryFileInputStream(init(file, delegate, scopes)); + return isTracingEnabled(scopes) + ? new SentryFileInputStream(init(file, delegate, scopes)) + : delegate; + } + + private static boolean isTracingEnabled(final @NotNull IScopes scopes) { + final @NotNull SentryOptions options = scopes.getOptions(); + return options.isTracingEnabled(); } } } diff --git a/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileOutputStream.java b/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileOutputStream.java index 4ef5022e1c..483d6e9781 100644 --- a/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileOutputStream.java +++ b/sentry/src/main/java/io/sentry/instrumentation/file/SentryFileOutputStream.java @@ -3,6 +3,7 @@ import io.sentry.IScopes; import io.sentry.ISpan; import io.sentry.ScopesAdapter; +import io.sentry.SentryOptions; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; @@ -134,33 +135,70 @@ public static final class Factory { public static FileOutputStream create( final @NotNull FileOutputStream delegate, final @Nullable String name) throws FileNotFoundException { - return new SentryFileOutputStream( - init(name != null ? new File(name) : null, false, delegate, ScopesAdapter.getInstance())); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileOutputStream( + init( + name != null ? new File(name) : null, + false, + delegate, + ScopesAdapter.getInstance())) + : delegate; } public static FileOutputStream create( final @NotNull FileOutputStream delegate, final @Nullable String name, final boolean append) throws FileNotFoundException { - return new SentryFileOutputStream( - init( - name != null ? new File(name) : null, append, delegate, ScopesAdapter.getInstance())); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileOutputStream( + init( + name != null ? new File(name) : null, + append, + delegate, + ScopesAdapter.getInstance())) + : delegate; } public static FileOutputStream create( final @NotNull FileOutputStream delegate, final @Nullable File file) throws FileNotFoundException { - return new SentryFileOutputStream(init(file, false, delegate, ScopesAdapter.getInstance())); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileOutputStream(init(file, false, delegate, ScopesAdapter.getInstance())) + : delegate; } public static FileOutputStream create( final @NotNull FileOutputStream delegate, final @Nullable File file, final boolean append) throws FileNotFoundException { - return new SentryFileOutputStream(init(file, append, delegate, ScopesAdapter.getInstance())); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileOutputStream(init(file, append, delegate, ScopesAdapter.getInstance())) + : delegate; } public static FileOutputStream create( final @NotNull FileOutputStream delegate, final @NotNull FileDescriptor fdObj) { - return new SentryFileOutputStream(init(fdObj, delegate, ScopesAdapter.getInstance()), fdObj); + final @NotNull IScopes scopes = ScopesAdapter.getInstance(); + return isTracingEnabled(scopes) + ? new SentryFileOutputStream(init(fdObj, delegate, ScopesAdapter.getInstance()), fdObj) + : delegate; + } + + public static FileOutputStream create( + final @NotNull FileOutputStream delegate, + final @Nullable File file, + final @NotNull IScopes scopes) + throws FileNotFoundException { + return isTracingEnabled(scopes) + ? new SentryFileOutputStream(init(file, false, delegate, scopes)) + : delegate; + } + + private static boolean isTracingEnabled(final @NotNull IScopes scopes) { + final @NotNull SentryOptions options = scopes.getOptions(); + return options.isTracingEnabled(); } } } diff --git a/sentry/src/main/java/io/sentry/internal/gestures/GestureTargetLocator.java b/sentry/src/main/java/io/sentry/internal/gestures/GestureTargetLocator.java index 79109f70ff..3fbfa1ab98 100644 --- a/sentry/src/main/java/io/sentry/internal/gestures/GestureTargetLocator.java +++ b/sentry/src/main/java/io/sentry/internal/gestures/GestureTargetLocator.java @@ -1,11 +1,10 @@ package io.sentry.internal.gestures; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public interface GestureTargetLocator { @Nullable UiElement locate( - final @NotNull Object root, final float x, final float y, final UiElement.Type targetType); + final @Nullable Object root, final float x, final float y, final UiElement.Type targetType); } diff --git a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt index def4e8c555..db52296471 100644 --- a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt +++ b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt @@ -39,7 +39,7 @@ class SentryFileInputStreamTest { activeTransaction: Boolean = true, fileDescriptor: FileDescriptor? = null, sendDefaultPii: Boolean = false - ): SentryFileInputStream { + ): FileInputStream { tmpFile?.writeText("Text") whenever(scopes.options).thenReturn( options.apply { @@ -61,8 +61,10 @@ class SentryFileInputStreamTest { internal fun getSut( tmpFile: File? = null, - delegate: FileInputStream - ): SentryFileInputStream { + delegate: FileInputStream, + tracesSampleRate: Double? = 1.0 + ): FileInputStream { + options.tracesSampleRate = tracesSampleRate whenever(scopes.options).thenReturn(options) sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes) whenever(scopes.span).thenReturn(sentryTracer) @@ -70,7 +72,7 @@ class SentryFileInputStreamTest { delegate, tmpFile, scopes - ) as SentryFileInputStream + ) } } @@ -274,6 +276,15 @@ class SentryFileInputStreamTest { assertEquals(false, fileIOSpan.data[SpanDataConvention.BLOCKED_MAIN_THREAD_KEY]) assertNull(fileIOSpan.data[SpanDataConvention.CALL_STACK_KEY]) } + + @Test + fun `when tracing is disabled does not instrument the stream`() { + val file = tmpFile + val delegate = ThrowingFileInputStream(file) + val stream = fixture.getSut(file, delegate = delegate, tracesSampleRate = null) + + assertTrue { stream is ThrowingFileInputStream } + } } class ThrowingFileInputStream(file: File) : FileInputStream(file) { diff --git a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt index f1826e5544..df66a3b6c3 100644 --- a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt +++ b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt @@ -14,6 +14,8 @@ import org.junit.rules.TemporaryFolder import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import java.io.File +import java.io.FileOutputStream +import java.io.IOException import java.util.concurrent.atomic.AtomicBoolean import kotlin.concurrent.thread import kotlin.test.Test @@ -26,6 +28,8 @@ import kotlin.test.assertTrue class SentryFileOutputStreamTest { class Fixture { val scopes = mock() + val options = SentryOptions() + lateinit var sentryTracer: SentryTracer internal fun getSut( @@ -44,8 +48,29 @@ class SentryFileOutputStreamTest { if (activeTransaction) { whenever(scopes.span).thenReturn(sentryTracer) } + whenever(scopes.options).thenReturn(options) + sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes) + if (activeTransaction) { + whenever(scopes.span).thenReturn(sentryTracer) + } return SentryFileOutputStream(tmpFile, append, scopes) } + + internal fun getSut( + tmpFile: File? = null, + delegate: FileOutputStream, + tracesSampleRate: Double? = 1.0 + ): FileOutputStream { + options.tracesSampleRate = tracesSampleRate + whenever(scopes.options).thenReturn(options) + sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes) + whenever(scopes.span).thenReturn(sentryTracer) + return SentryFileOutputStream.Factory.create( + delegate, + tmpFile, + scopes + ) + } } @get:Rule @@ -197,4 +222,25 @@ class SentryFileOutputStreamTest { assertEquals(false, fileIOSpan.data[SpanDataConvention.BLOCKED_MAIN_THREAD_KEY]) assertNull(fileIOSpan.data[SpanDataConvention.CALL_STACK_KEY]) } + + @Test + fun `when tracing is disabled does not instrument the stream`() { + val file = tmpFile + val delegate = ThrowingFileOutputStream(file) + val stream = fixture.getSut(file, delegate = delegate, tracesSampleRate = null) + + assertTrue { stream is ThrowingFileOutputStream } + } +} + +class ThrowingFileOutputStream(file: File) : FileOutputStream(file) { + val throwable = IOException("Oops!") + + override fun write(b: Int) { + throw throwable + } + + override fun close() { + throw throwable + } } From c322afef5a90cd104404079eba064e31e73ca1b0 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 30 Jan 2025 16:45:39 +0000 Subject: [PATCH 07/10] release: 8.1.0 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41610d1933..d89f0ae054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 8.1.0 ### Features diff --git a/gradle.properties b/gradle.properties index 8d0a3090f0..cdaf2c4f44 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ android.useAndroidX=true android.defaults.buildfeatures.buildconfig=true # Release information -versionName=8.0.0 +versionName=8.1.0 # Override the SDK name on native crashes on Android sentryAndroidSdkName=sentry.native.android From 3b43bd2bc107ea6279ee664debec4a39f3fc5eb2 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 3 Feb 2025 06:11:56 +0100 Subject: [PATCH 08/10] Do not log if `OtelContextScopesStorage` cannot be found (#4127) * Do not log if OtelContextScopesStorage cannot be found * changelog --- CHANGELOG.md | 8 ++++++++ sentry/src/main/java/io/sentry/Sentry.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d89f0ae054..3803bc2a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Fixes + +- Do not log if `OtelContextScopesStorage` cannot be found ([#4127](https://github.com/getsentry/sentry-java/pull/4127)) + - Previously `java.lang.ClassNotFoundException: io.sentry.opentelemetry.OtelContextScopesStorage` was shown in the log if the class could not be found. + - This is just a lookup the SDK performs to configure itself. The SDK also works without OpenTelemetry. + ## 8.1.0 ### Features diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 70519fb98f..94007e42c5 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -395,7 +395,7 @@ private static void initScopesStorage(SentryOptions options) { if (SentryOpenTelemetryMode.OFF == options.getOpenTelemetryMode()) { scopesStorage = new DefaultScopesStorage(); } else { - scopesStorage = ScopesStorageFactory.create(new LoadClass(), options.getLogger()); + scopesStorage = ScopesStorageFactory.create(new LoadClass(), NoOpLogger.getInstance()); } } From a4fd36f38a9b4d4e275ba30a8f282b12fd21299f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:49:29 +0100 Subject: [PATCH 09/10] chore(deps): update Native SDK to v0.7.20 (#4128) Co-authored-by: GitHub --- CHANGELOG.md | 6 ++++++ buildSrc/src/main/java/Config.kt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3803bc2a7e..287d56e3f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ - Previously `java.lang.ClassNotFoundException: io.sentry.opentelemetry.OtelContextScopesStorage` was shown in the log if the class could not be found. - This is just a lookup the SDK performs to configure itself. The SDK also works without OpenTelemetry. +### Dependencies + +- Bump Native SDK from v0.7.19 to v0.7.20 ([#4128](https://github.com/getsentry/sentry-java/pull/4128)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0720) + - [diff](https://github.com/getsentry/sentry-native/compare/v0.7.19...0.7.20) + ## 8.1.0 ### Features diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 59a54600cf..e77417d7bb 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -153,7 +153,7 @@ object Config { val apolloKotlin = "com.apollographql.apollo3:apollo-runtime:3.8.2" - val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.7.19" + val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.7.20" object OpenTelemetry { val otelVersion = "1.44.1" From c2e65f0bf78ca1180bff89e9803853f64f98d0d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:01:34 +0000 Subject: [PATCH 10/10] Bump codecov/codecov-action from 5.1.2 to 5.3.1 (#4108) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.2 to 5.3.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/1e68e06f1dbfde0e4cefc87efeba9e4643565303...13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Roman Zavarnitsyn --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9160d5c8ac..c5f2cf9f19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: run: make preMerge - name: Upload coverage to Codecov - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # pin@v4 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # pin@v4 with: name: sentry-java fail_ci_if_error: false