From f79c9c10fa6dd6663639c38ea94bc71374d955a3 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 10 Oct 2024 08:51:07 +0200 Subject: [PATCH 01/40] Deprecate `enableTracing` in v7 (#3777) * deprecate enableTracing * changelog --- CHANGELOG.md | 6 ++++++ .../io/sentry/android/core/ManifestMetadataReader.java | 2 +- sentry/src/main/java/io/sentry/SentryOptions.java | 9 ++++++++- sentry/src/main/java/io/sentry/TracesSampler.java | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3037df38d5..f6db963eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) + ## 7.15.0 ### Features 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 96d54d98de..e42f68cab0 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 @@ -56,7 +56,7 @@ final class ManifestMetadataReader { static final String UNCAUGHT_EXCEPTION_HANDLER_ENABLE = "io.sentry.uncaught-exception-handler.enable"; - static final String TRACING_ENABLE = "io.sentry.traces.enable"; + @Deprecated static final String TRACING_ENABLE = "io.sentry.traces.enable"; static final String TRACES_SAMPLE_RATE = "io.sentry.traces.sample-rate"; static final String TRACES_ACTIVITY_ENABLE = "io.sentry.traces.activity.enable"; static final String TRACES_ACTIVITY_AUTO_FINISH_ENABLE = diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 3873bc9380..0eb3bace91 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -913,14 +913,21 @@ public void setSampleRate(Double sampleRate) { *

NOTE: There is also {@link SentryOptions#isTracingEnabled()} which checks other options as * well. * + * @deprecated We're removing enableTracing in 8.0 * @return true if enabled, false if disabled, null can mean enabled if {@link * SentryOptions#getTracesSampleRate()} or {@link SentryOptions#getTracesSampler()} are set. */ + @Deprecated public @Nullable Boolean getEnableTracing() { return enableTracing; } - /** Enables generation of transactions and propagation of trace data. */ + /** + * Enables generation of transactions and propagation of trace data. + * + * @deprecated We're removing enableTracing in 8.0 + */ + @Deprecated public void setEnableTracing(@Nullable Boolean enableTracing) { this.enableTracing = enableTracing; } diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 3b83a815cf..e0ce111037 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -22,6 +22,7 @@ public TracesSampler(final @NotNull SentryOptions options) { this.random = random; } + @SuppressWarnings("deprecation") @NotNull TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final TracesSamplingDecision samplingContextSamplingDecision = From 2ab34ebca967b5bdac6e49a3b12f4b7e982b72be Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Mon, 14 Oct 2024 09:00:54 +0200 Subject: [PATCH 02/40] [QA] Replace SecureRandom with vendored Random (#3783) * Replace SecureRandom with vendored Random * Changelog * Fix failing test --- CHANGELOG.md | 1 + .../android/core/AnrV2EventProcessor.java | 8 +- .../android/replay/ReplayIntegration.kt | 4 +- .../replay/capture/BufferCaptureStrategy.kt | 4 +- .../io/sentry/android/replay/util/Sampling.kt | 4 +- .../capture/BufferCaptureStrategyTest.kt | 4 +- sentry/api/sentry.api | 13 + .../src/main/java/io/sentry/SentryClient.java | 6 +- .../main/java/io/sentry/TracesSampler.java | 8 +- .../sentry/cache/PersistingScopeObserver.java | 6 + .../java/io/sentry/metrics/MetricsHelper.java | 4 +- .../src/main/java/io/sentry/util/Random.java | 466 ++++++++++++++++++ .../test/java/io/sentry/TracesSamplerTest.kt | 4 +- 13 files changed, 509 insertions(+), 23 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/Random.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f6db963eb3..84040594a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) +- Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) ## 7.15.0 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 d58d04b7f8..0399b634fb 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 @@ -54,8 +54,8 @@ import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.User; import io.sentry.util.HintUtils; +import io.sentry.util.Random; import java.io.File; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -83,7 +83,7 @@ public final class AnrV2EventProcessor implements BackfillingEventProcessor { private final @NotNull SentryExceptionFactory sentryExceptionFactory; - private final @Nullable SecureRandom random; + private final @Nullable Random random; public AnrV2EventProcessor( final @NotNull Context context, @@ -96,7 +96,7 @@ public AnrV2EventProcessor( final @NotNull Context context, final @NotNull SentryAndroidOptions options, final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable SecureRandom random) { + final @Nullable Random random) { this.context = ContextUtils.getApplicationContext(context); this.options = options; this.buildInfoProvider = buildInfoProvider; @@ -180,7 +180,7 @@ private boolean sampleReplay(final @NotNull SentryEvent event) { try { // we have to sample here with the old sample rate, because it may change between app launches - final @NotNull SecureRandom random = this.random != null ? this.random : new SecureRandom(); + final @NotNull Random random = this.random != null ? this.random : new Random(); final double replayErrorSampleRateDouble = Double.parseDouble(replayErrorSampleRate); if (replayErrorSampleRateDouble < random.nextDouble()) { options diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt index 82d20cf3c1..ee9223cc80 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt @@ -35,9 +35,9 @@ import io.sentry.transport.ICurrentDateProvider import io.sentry.util.FileUtils import io.sentry.util.HintUtils import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion +import io.sentry.util.Random import java.io.Closeable import java.io.File -import java.security.SecureRandom import java.util.LinkedList import java.util.concurrent.atomic.AtomicBoolean import kotlin.LazyThreadSafetyMode.NONE @@ -78,7 +78,7 @@ public class ReplayIntegration( private var hub: IHub? = null private var recorder: Recorder? = null private var gestureRecorder: GestureRecorder? = null - private val random by lazy { SecureRandom() } + private val random by lazy { Random() } private val rootViewsSpy by lazy(NONE) { RootViewsSpy.install() } // TODO: probably not everything has to be thread-safe here diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt index 247888f47d..2dbb2a1746 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt @@ -18,8 +18,8 @@ import io.sentry.android.replay.util.submitSafely import io.sentry.protocol.SentryId import io.sentry.transport.ICurrentDateProvider import io.sentry.util.FileUtils +import io.sentry.util.Random import java.io.File -import java.security.SecureRandom import java.util.Date import java.util.concurrent.ScheduledExecutorService @@ -27,7 +27,7 @@ internal class BufferCaptureStrategy( private val options: SentryOptions, private val hub: IHub?, private val dateProvider: ICurrentDateProvider, - private val random: SecureRandom, + private val random: Random, executor: ScheduledExecutorService? = null, replayCacheProvider: ((replayId: SentryId, recorderConfig: ScreenshotRecorderConfig) -> ReplayCache)? = null ) : BaseCaptureStrategy(options, hub, dateProvider, executor = executor, replayCacheProvider = replayCacheProvider) { diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt index 8acb6b00a6..5ec46ea962 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt @@ -1,8 +1,8 @@ package io.sentry.android.replay.util -import java.security.SecureRandom +import io.sentry.util.Random -internal fun SecureRandom.sample(rate: Double?): Boolean { +internal fun Random.sample(rate: Double?): Boolean { if (rate != null) { return !(rate < this.nextDouble()) // bad luck } diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt index 337ba525fc..625306cb8e 100644 --- a/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt +++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt @@ -20,6 +20,7 @@ import io.sentry.android.replay.capture.BufferCaptureStrategyTest.Fixture.Compan import io.sentry.protocol.SentryId import io.sentry.transport.CurrentDateProvider import io.sentry.transport.ICurrentDateProvider +import io.sentry.util.Random import org.awaitility.kotlin.await import org.junit.Rule import org.junit.rules.TemporaryFolder @@ -35,7 +36,6 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.io.File -import java.security.SecureRandom import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -97,7 +97,7 @@ class BufferCaptureStrategyTest { options, hub, dateProvider, - SecureRandom(), + Random(), mock { doAnswer { invocation -> (invocation.arguments[0] as Runnable).run() diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ac1eb2bc8a..89684c7ae5 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5808,6 +5808,19 @@ public final class io/sentry/util/PropagationTargetsUtils { public static fun contain (Ljava/util/List;Ljava/net/URI;)Z } +public final class io/sentry/util/Random : java/io/Serializable { + public fun ()V + public fun (J)V + public fun nextBoolean ()Z + public fun nextBytes ([B)V + public fun nextDouble ()D + public fun nextFloat ()F + public fun nextInt ()I + public fun nextInt (I)I + public fun nextLong ()J + public fun setSeed (J)V +} + public final class io/sentry/util/SampleRateUtils { public fun ()V public static fun isValidProfilesSampleRate (Ljava/lang/Double;)Z diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 6868894340..27529d100b 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -17,10 +17,10 @@ import io.sentry.util.CheckInUtils; import io.sentry.util.HintUtils; import io.sentry.util.Objects; +import io.sentry.util.Random; import io.sentry.util.TracingUtils; import java.io.Closeable; import java.io.IOException; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -40,7 +40,7 @@ public final class SentryClient implements ISentryClient, IMetricsClient { private final @NotNull SentryOptions options; private final @NotNull ITransport transport; - private final @Nullable SecureRandom random; + private final @Nullable Random random; private final @NotNull SortBreadcrumbsByDate sortBreadcrumbsByDate = new SortBreadcrumbsByDate(); private final @NotNull IMetricsAggregator metricsAggregator; @@ -67,7 +67,7 @@ public boolean isEnabled() { ? new MetricsAggregator(options, this) : NoopMetricsAggregator.getInstance(); - this.random = options.getSampleRate() == null ? null : new SecureRandom(); + this.random = options.getSampleRate() == null ? null : new Random(); } private boolean shouldApplyScopeData( diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index e0ce111037..5e5b808333 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -1,7 +1,7 @@ package io.sentry; import io.sentry.util.Objects; -import java.security.SecureRandom; +import io.sentry.util.Random; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -10,14 +10,14 @@ final class TracesSampler { private static final @NotNull Double DEFAULT_TRACES_SAMPLE_RATE = 1.0; private final @NotNull SentryOptions options; - private final @NotNull SecureRandom random; + private final @NotNull Random random; public TracesSampler(final @NotNull SentryOptions options) { - this(Objects.requireNonNull(options, "options are required"), new SecureRandom()); + this(Objects.requireNonNull(options, "options are required"), new Random()); } @TestOnly - TracesSampler(final @NotNull SentryOptions options, final @NotNull SecureRandom random) { + TracesSampler(final @NotNull SentryOptions options, final @NotNull Random random) { this.options = options; this.random = random; } diff --git a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java index 7c186cf99d..908e2c66e4 100644 --- a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java +++ b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java @@ -133,6 +133,12 @@ public void setReplayId(@NotNull SentryId replayId) { @SuppressWarnings("FutureReturnValueIgnored") private void serializeToDisk(final @NotNull Runnable task) { + if (Thread.currentThread().getName().contains("SentryExecutor")) { + // we're already on the sentry executor thread, so we can just execute it directly + task.run(); + return; + } + try { options .getExecutorService() diff --git a/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java b/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java index c4353c53a3..37fd4523a4 100644 --- a/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java +++ b/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java @@ -1,7 +1,7 @@ package io.sentry.metrics; import io.sentry.MeasurementUnit; -import java.security.SecureRandom; +import io.sentry.util.Random; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -27,7 +27,7 @@ public final class MetricsHelper { private static final char TAGS_ESCAPE_CHAR = '\\'; private static long FLUSH_SHIFT_MS = - (long) (new SecureRandom().nextFloat() * (ROLLUP_IN_SECONDS * 1000f)); + (long) (new Random().nextFloat() * (ROLLUP_IN_SECONDS * 1000f)); public static long getTimeBucketKey(final long timestampMs) { final long seconds = timestampMs / 1000; diff --git a/sentry/src/main/java/io/sentry/util/Random.java b/sentry/src/main/java/io/sentry/util/Random.java new file mode 100644 index 0000000000..cbd81824df --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/Random.java @@ -0,0 +1,466 @@ +/* + * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package io.sentry.util; + +import java.util.concurrent.atomic.AtomicLong; +import org.jetbrains.annotations.ApiStatus; + +/** + * A simplified version of {@link java.util.Random} that we use for sampling, which is much faster + * than {@link java.security.SecureRandom}. This is necessary so that some security tools do not + * flag our Random usage as potentially insecure. + */ +@ApiStatus.Internal +public final class Random implements java.io.Serializable { + /** use serialVersionUID from JDK 1.1 for interoperability */ + private static final long serialVersionUID = 3905348978240129619L; + + /** + * The internal state associated with this pseudorandom number generator. (The specs for the + * methods in this class describe the ongoing computation of this value.) + */ + private final AtomicLong seed; + + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) + + // IllegalArgumentException messages + static final String BadBound = "bound must be positive"; + + /** + * Creates a new random number generator. This constructor sets the seed of the random number + * generator to a value very likely to be distinct from any other invocation of this constructor. + */ + public Random() { + this(seedUniquifier() ^ System.nanoTime()); + } + + private static long seedUniquifier() { + // L'Ecuyer, "Tables of Linear Congruential Generators of + // Different Sizes and Good Lattice Structure", 1999 + for (; ; ) { + long current = seedUniquifier.get(); + long next = current * 1181783497276652981L; + if (seedUniquifier.compareAndSet(current, next)) return next; + } + } + + private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L); + + /** + * Creates a new random number generator using a single {@code long} seed. The seed is the initial + * value of the internal state of the pseudorandom number generator which is maintained by method + * {@link #next}. + * + *

The invocation {@code new Random(seed)} is equivalent to: + * + *

{@code
+   * Random rnd = new Random();
+   * rnd.setSeed(seed);
+   * }
+ * + * @param seed the initial seed + * @see #setSeed(long) + */ + public Random(long seed) { + if (getClass() == Random.class) this.seed = new AtomicLong(initialScramble(seed)); + else { + // subclass might have overriden setSeed + this.seed = new AtomicLong(); + setSeed(seed); + } + } + + private static long initialScramble(long seed) { + return (seed ^ multiplier) & mask; + } + + /** + * Sets the seed of this random number generator using a single {@code long} seed. The general + * contract of {@code setSeed} is that it alters the state of this random number generator object + * so as to be in exactly the same state as if it had just been created with the argument {@code + * seed} as a seed. The method {@code setSeed} is implemented by class {@code Random} by + * atomically updating the seed to + * + *
{@code (seed ^ 0x5DEECE66DL) & ((1L << 48) - 1)}
+ * + *

The implementation of {@code setSeed} by class {@code Random} happens to use only 48 bits of + * the given seed. In general, however, an overriding method may use all 64 bits of the {@code + * long} argument as a seed value. + * + * @param seed the initial seed + */ + public synchronized void setSeed(long seed) { + this.seed.set(initialScramble(seed)); + } + + /** + * Generates the next pseudorandom number. Subclasses should override this, as this is used by all + * other methods. + * + *

The general contract of {@code next} is that it returns an {@code int} value and if the + * argument {@code bits} is between {@code 1} and {@code 32} (inclusive), then that many low-order + * bits of the returned value will be (approximately) independently chosen bit values, each of + * which is (approximately) equally likely to be {@code 0} or {@code 1}. The method {@code next} + * is implemented by class {@code Random} by atomically updating the seed to + * + *

{@code (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)}
+ * + * and returning + * + *
{@code (int)(seed >>> (48 - bits))}.
+ * + * This is a linear congruential pseudorandom number generator, as defined by D. H. Lehmer and + * described by Donald E. Knuth in The Art of Computer Programming, Volume 2: + * Seminumerical Algorithms, section 3.2.1. + * + * @param bits random bits + * @return the next pseudorandom value from this random number generator's sequence + * @since 1.1 + */ + private int next(int bits) { + long oldseed, nextseed; + AtomicLong seed = this.seed; + do { + oldseed = seed.get(); + nextseed = (oldseed * multiplier + addend) & mask; + } while (!seed.compareAndSet(oldseed, nextseed)); + return (int) (nextseed >>> (48 - bits)); + } + + /** + * Generates random bytes and places them into a user-supplied byte array. The number of random + * bytes produced is equal to the length of the byte array. + * + *

The method {@code nextBytes} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public void nextBytes(byte[] bytes) {
+   *   for (int i = 0; i < bytes.length; )
+   *     for (int rnd = nextInt(), n = Math.min(bytes.length - i, 4);
+   *          n-- > 0; rnd >>= 8)
+   *       bytes[i++] = (byte)rnd;
+   * }
+   * }
+ * + * @param bytes the byte array to fill with random bytes + * @throws NullPointerException if the byte array is null + * @since 1.1 + */ + public void nextBytes(byte[] bytes) { + for (int i = 0, len = bytes.length; i < len; ) + for (int rnd = nextInt(), n = Math.min(len - i, Integer.SIZE / Byte.SIZE); + n-- > 0; + rnd >>= Byte.SIZE) bytes[i++] = (byte) rnd; + } + + /** + * The form of nextLong used by LongStream Spliterators. If origin is greater than bound, acts as + * unbounded form of nextLong, else as bounded form. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final long internalNextLong(long origin, long bound) { + long r = nextLong(); + if (origin < bound) { + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + r = (r & m) + origin; + else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = nextLong() >>> 1) // retry + ; + r += origin; + } else { // range not representable as long + while (r < origin || r >= bound) r = nextLong(); + } + } + return r; + } + + /** + * The form of nextInt used by IntStream Spliterators. For the unbounded case: uses nextInt(). For + * the bounded case with representable range: uses nextInt(int bound) For the bounded case with + * unrepresentable range: uses nextInt() + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final int internalNextInt(int origin, int bound) { + if (origin < bound) { + int n = bound - origin; + if (n > 0) { + return nextInt(n) + origin; + } else { // range not representable as int + int r; + do { + r = nextInt(); + } while (r < origin || r >= bound); + return r; + } + } else { + return nextInt(); + } + } + + /** + * The form of nextDouble used by DoubleStream Spliterators. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final double internalNextDouble(double origin, double bound) { + double r = nextDouble(); + if (origin < bound) { + r = r * (bound - origin) + origin; + if (r >= bound) // correct for rounding + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code int} value from this random number + * generator's sequence. The general contract of {@code nextInt} is that one {@code int} value is + * pseudorandomly generated and returned. All 232 possible {@code int} values are + * produced with (approximately) equal probability. + * + *

The method {@code nextInt} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public int nextInt() {
+   *   return next(32);
+   * }
+   * }
+ * + * @return the next pseudorandom, uniformly distributed {@code int} value from this random number + * generator's sequence + */ + public int nextInt() { + return next(32); + } + + /** + * Returns a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and the + * specified value (exclusive), drawn from this random number generator's sequence. The general + * contract of {@code nextInt} is that one {@code int} value in the specified range is + * pseudorandomly generated and returned. All {@code bound} possible {@code int} values are + * produced with (approximately) equal probability. The method {@code nextInt(int bound)} is + * implemented by class {@code Random} as if by: + * + *
{@code
+   * public int nextInt(int bound) {
+   *   if (bound <= 0)
+   *     throw new IllegalArgumentException("bound must be positive");
+   *
+   *   if ((bound & -bound) == bound)  // i.e., bound is a power of 2
+   *     return (int)((bound * (long)next(31)) >> 31);
+   *
+   *   int bits, val;
+   *   do {
+   *       bits = next(31);
+   *       val = bits % bound;
+   *   } while (bits - val + (bound-1) < 0);
+   *   return val;
+   * }
+   * }
+ * + *

The hedge "approximately" is used in the foregoing description only because the next method + * is only approximately an unbiased source of independently chosen bits. If it were a perfect + * source of randomly chosen bits, then the algorithm shown would choose {@code int} values from + * the stated range with perfect uniformity. + * + *

The algorithm is slightly tricky. It rejects values that would result in an uneven + * distribution (due to the fact that 2^31 is not divisible by n). The probability of a value + * being rejected depends on n. The worst case is n=2^30+1, for which the probability of a reject + * is 1/2, and the expected number of iterations before the loop terminates is 2. + * + *

The algorithm treats the case where n is a power of two specially: it returns the correct + * number of high-order bits from the underlying pseudo-random number generator. In the absence of + * special treatment, the correct number of low-order bits would be returned. Linear + * congruential pseudo-random number generators such as the one implemented by this class are + * known to have short periods in the sequence of values of their low-order bits. Thus, this + * special case greatly increases the length of the sequence of values returned by successive + * calls to this method if n is a small power of two. + * + * @param bound the upper bound (exclusive). Must be positive. + * @return the next pseudorandom, uniformly distributed {@code int} value between zero (inclusive) + * and {@code bound} (exclusive) from this random number generator's sequence + * @throws IllegalArgumentException if bound is not positive + * @since 1.2 + */ + public int nextInt(int bound) { + if (bound <= 0) throw new IllegalArgumentException(BadBound); + + int r = next(31); + int m = bound - 1; + if ((bound & m) == 0) // i.e., bound is a power of 2 + r = (int) ((bound * (long) r) >> 31); + else { + for (int u = r; u - (r = u % bound) + m < 0; u = next(31)) + ; + } + return r; + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code long} value from this random number + * generator's sequence. The general contract of {@code nextLong} is that one {@code long} value + * is pseudorandomly generated and returned. + * + *

The method {@code nextLong} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public long nextLong() {
+   *   return ((long)next(32) << 32) + next(32);
+   * }
+   * }
+ * + * Because class {@code Random} uses a seed with only 48 bits, this algorithm will not return all + * possible {@code long} values. + * + * @return the next pseudorandom, uniformly distributed {@code long} value from this random number + * generator's sequence + */ + @SuppressWarnings("UnnecessaryParentheses") + public long nextLong() { + // it's okay that the bottom word remains signed. + return ((long) (next(32)) << 32) + next(32); + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code boolean} value from this random + * number generator's sequence. The general contract of {@code nextBoolean} is that one {@code + * boolean} value is pseudorandomly generated and returned. The values {@code true} and {@code + * false} are produced with (approximately) equal probability. + * + *

The method {@code nextBoolean} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public boolean nextBoolean() {
+   *   return next(1) != 0;
+   * }
+   * }
+ * + * @return the next pseudorandom, uniformly distributed {@code boolean} value from this random + * number generator's sequence + * @since 1.2 + */ + public boolean nextBoolean() { + return next(1) != 0; + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code float} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence. + * + *

The general contract of {@code nextFloat} is that one {@code float} value, chosen + * (approximately) uniformly from the range {@code 0.0f} (inclusive) to {@code 1.0f} (exclusive), + * is pseudorandomly generated and returned. All 224 possible {@code float} values of + * the form m x 2-24, where m is a positive integer less than + * 224, are produced with (approximately) equal probability. + * + *

The method {@code nextFloat} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public float nextFloat() {
+   *   return next(24) / ((float)(1 << 24));
+   * }
+   * }
+ * + *

The hedge "approximately" is used in the foregoing description only because the next method + * is only approximately an unbiased source of independently chosen bits. If it were a perfect + * source of randomly chosen bits, then the algorithm shown would choose {@code float} values from + * the stated range with perfect uniformity. + * + *

[In early versions of Java, the result was incorrectly calculated as: + * + *

{@code
+   * return next(30) / ((float)(1 << 30));
+   * }
+ * + * This might seem to be equivalent, if not better, but in fact it introduced a slight + * nonuniformity because of the bias in the rounding of floating-point numbers: it was slightly + * more likely that the low-order bit of the significand would be 0 than that it would be 1.] + * + * @return the next pseudorandom, uniformly distributed {@code float} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence + */ + public float nextFloat() { + return next(24) / ((float) (1 << 24)); + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code double} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence. + * + *

The general contract of {@code nextDouble} is that one {@code double} value, chosen + * (approximately) uniformly from the range {@code 0.0d} (inclusive) to {@code 1.0d} (exclusive), + * is pseudorandomly generated and returned. + * + *

The method {@code nextDouble} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public double nextDouble() {
+   *   return (((long)next(26) << 27) + next(27))
+   *     / (double)(1L << 53);
+   * }
+   * }
+ * + *

The hedge "approximately" is used in the foregoing description only because the {@code next} + * method is only approximately an unbiased source of independently chosen bits. If it were a + * perfect source of randomly chosen bits, then the algorithm shown would choose {@code double} + * values from the stated range with perfect uniformity. + * + *

[In early versions of Java, the result was incorrectly calculated as: + * + *

{@code
+   * return (((long)next(27) << 27) + next(27))
+   *   / (double)(1L << 54);
+   * }
+ * + * This might seem to be equivalent, if not better, but in fact it introduced a large + * nonuniformity because of the bias in the rounding of floating-point numbers: it was three times + * as likely that the low-order bit of the significand would be 0 than that it would be 1! This + * nonuniformity probably doesn't matter much in practice, but we strive for perfection.] + * + * @return the next pseudorandom, uniformly distributed {@code double} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence + * @see Math#random + */ + @SuppressWarnings("UnnecessaryParentheses") + public double nextDouble() { + return (((long) (next(26)) << 27) + next(27)) * DOUBLE_UNIT; + } +} diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index 11d58766d6..52e294c54d 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -1,11 +1,11 @@ package io.sentry +import io.sentry.util.Random import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import java.security.SecureRandom import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -23,7 +23,7 @@ class TracesSamplerTest { profilesSamplerCallback: SentryOptions.ProfilesSamplerCallback? = null, logger: ILogger? = null ): TracesSampler { - val random = mock() + val random = mock() if (randomResult != null) { whenever(random.nextDouble()).thenReturn(randomResult) } From a9c767ee938ced7114056047fc5e2ebe0e9fe6e1 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 14 Oct 2024 13:32:51 +0200 Subject: [PATCH 03/40] [QA] Move NDK scope sync to background thread (#3754) * Move NDK scope sync to background thread * Update Changelog * Cleanup code --- CHANGELOG.md | 1 + sentry-android-ndk/build.gradle.kts | 2 +- .../sentry/android/ndk/NdkScopeObserver.java | 70 ++++++++++++------- .../android/ndk/NdkScopeObserverTest.kt | 38 ++++++++++ 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84040594a6..a18600c608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) - Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) +- Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) ## 7.15.0 diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts index f6564cd97f..f1c4873053 100644 --- a/sentry-android-ndk/build.gradle.kts +++ b/sentry-android-ndk/build.gradle.kts @@ -105,6 +105,6 @@ dependencies { testImplementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) testImplementation(Config.TestLibs.kotlinTestJunit) - testImplementation(Config.TestLibs.mockitoKotlin) + testImplementation(projects.sentryTestSupport) } diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java index 009bba9b81..2350c419be 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java @@ -31,12 +31,18 @@ public NdkScopeObserver(final @NotNull SentryOptions options) { @Override public void setUser(final @Nullable User user) { try { - if (user == null) { - // remove user if its null - nativeScope.removeUser(); - } else { - nativeScope.setUser(user.getId(), user.getEmail(), user.getIpAddress(), user.getUsername()); - } + options + .getExecutorService() + .submit( + () -> { + if (user == null) { + // remove user if its null + nativeScope.removeUser(); + } else { + nativeScope.setUser( + user.getId(), user.getEmail(), user.getIpAddress(), user.getUsername()); + } + }); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setUser has an error."); } @@ -45,24 +51,36 @@ public void setUser(final @Nullable User user) { @Override public void addBreadcrumb(final @NotNull Breadcrumb crumb) { try { - String level = null; - if (crumb.getLevel() != null) { - level = crumb.getLevel().name().toLowerCase(Locale.ROOT); - } - final String timestamp = DateUtils.getTimestamp(crumb.getTimestamp()); + options + .getExecutorService() + .submit( + () -> { + String level = null; + if (crumb.getLevel() != null) { + level = crumb.getLevel().name().toLowerCase(Locale.ROOT); + } + final String timestamp = DateUtils.getTimestamp(crumb.getTimestamp()); - String data = null; - try { - final Map dataRef = crumb.getData(); - if (!dataRef.isEmpty()) { - data = options.getSerializer().serialize(dataRef); - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, e, "Breadcrumb data is not serializable."); - } + String data = null; + try { + final Map dataRef = crumb.getData(); + if (!dataRef.isEmpty()) { + data = options.getSerializer().serialize(dataRef); + } + } catch (Throwable e) { + options + .getLogger() + .log(SentryLevel.ERROR, e, "Breadcrumb data is not serializable."); + } - nativeScope.addBreadcrumb( - level, crumb.getMessage(), crumb.getCategory(), crumb.getType(), timestamp, data); + nativeScope.addBreadcrumb( + level, + crumb.getMessage(), + crumb.getCategory(), + crumb.getType(), + timestamp, + data); + }); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, e, "Scope sync addBreadcrumb has an error."); } @@ -71,7 +89,7 @@ public void addBreadcrumb(final @NotNull Breadcrumb crumb) { @Override public void setTag(final @NotNull String key, final @NotNull String value) { try { - nativeScope.setTag(key, value); + options.getExecutorService().submit(() -> nativeScope.setTag(key, value)); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setTag(%s) has an error.", key); } @@ -80,7 +98,7 @@ public void setTag(final @NotNull String key, final @NotNull String value) { @Override public void removeTag(final @NotNull String key) { try { - nativeScope.removeTag(key); + options.getExecutorService().submit(() -> nativeScope.removeTag(key)); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, e, "Scope sync removeTag(%s) has an error.", key); } @@ -89,7 +107,7 @@ public void removeTag(final @NotNull String key) { @Override public void setExtra(final @NotNull String key, final @NotNull String value) { try { - nativeScope.setExtra(key, value); + options.getExecutorService().submit(() -> nativeScope.setExtra(key, value)); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setExtra(%s) has an error.", key); } @@ -98,7 +116,7 @@ public void setExtra(final @NotNull String key, final @NotNull String value) { @Override public void removeExtra(final @NotNull String key) { try { - nativeScope.removeExtra(key); + options.getExecutorService().submit(() -> nativeScope.removeExtra(key)); } catch (Throwable e) { options .getLogger() diff --git a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt index 335a7679e1..110f794cf9 100644 --- a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt +++ b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt @@ -6,8 +6,13 @@ import io.sentry.JsonSerializer import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.protocol.User +import io.sentry.test.DeferredExecutorService +import io.sentry.test.ImmediateExecutorService +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import kotlin.test.Test @@ -17,6 +22,7 @@ class NdkScopeObserverTest { val nativeScope = mock() val options = SentryOptions().apply { setSerializer(JsonSerializer(mock())) + executorService = ImmediateExecutorService() } fun getSut(): NdkScopeObserver { @@ -111,4 +117,36 @@ class NdkScopeObserverTest { eq(data) ) } + + @Test + fun `scope sync utilizes executor service`() { + val executorService = DeferredExecutorService() + fixture.options.executorService = executorService + val sut = fixture.getSut() + + sut.setTag("a", "b") + sut.removeTag("a") + sut.setExtra("a", "b") + sut.removeExtra("a") + sut.setUser(User()) + sut.addBreadcrumb(Breadcrumb()) + + // as long as the executor service is not run, the scope sync is not called + verify(fixture.nativeScope, never()).setTag(any(), any()) + verify(fixture.nativeScope, never()).removeTag(any()) + verify(fixture.nativeScope, never()).setExtra(any(), any()) + verify(fixture.nativeScope, never()).removeExtra(any()) + verify(fixture.nativeScope, never()).setUser(any(), any(), any(), any()) + verify(fixture.nativeScope, never()).addBreadcrumb(any(), any(), any(), any(), any(), any()) + + // when the executor service is run, the scope sync is called + executorService.runAll() + + verify(fixture.nativeScope).setTag(any(), any()) + verify(fixture.nativeScope).removeTag(any()) + verify(fixture.nativeScope).setExtra(any(), any()) + verify(fixture.nativeScope).removeExtra(any()) + verify(fixture.nativeScope).setUser(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + verify(fixture.nativeScope).addBreadcrumb(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + } } From 6259a9fdb9ec4b75001c93401e1d0b1c18541dfb Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 14 Oct 2024 15:53:13 +0200 Subject: [PATCH 04/40] [QA] Offload System.loadLibrary call to background thread (#3670) * Offload System.loadLibrary call to background thread * Update Changelog * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../java/io/sentry/android/ndk/SentryNdk.java | 54 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a18600c608..cf0d89cca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) - Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) - Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) +- Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) ## 7.15.0 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 1ddc04c524..7245516b49 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,23 +1,40 @@ package io.sentry.android.ndk; import io.sentry.android.core.SentryAndroidOptions; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @ApiStatus.Internal public final class SentryNdk { + private static final @NotNull CountDownLatch loadLibraryLatch = new CountDownLatch(1); + private SentryNdk() {} static { - // On older Android versions, it was necessary to manually call "`System.loadLibrary` on all - // transitive dependencies before loading [the] main library." - // The dependencies of `libsentry.so` are currently `lib{c,m,dl,log}.so`. - // See - // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#changes-to-library-dependency-resolution - System.loadLibrary("log"); - System.loadLibrary("sentry"); - System.loadLibrary("sentry-android"); + new Thread( + () -> { + // On older Android versions, it was necessary to manually call "`System.loadLibrary` + // on all + // transitive dependencies before loading [the] main library." + // The dependencies of `libsentry.so` are currently `lib{c,m,dl,log}.so`. + // See + // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#changes-to-library-dependency-resolution + try { + System.loadLibrary("log"); + System.loadLibrary("sentry"); + System.loadLibrary("sentry-android"); + } catch (Throwable t) { + // ignored + // if loadLibrary() fails, the later init() will throw an exception anyway + } finally { + loadLibraryLatch.countDown(); + } + }, + "SentryNdkLoadLibs") + .start(); } private static native void initSentryNative(@NotNull final SentryAndroidOptions options); @@ -31,14 +48,23 @@ private SentryNdk() {} */ public static void init(@NotNull final SentryAndroidOptions options) { SentryNdkUtil.addPackage(options.getSdkVersion()); - initSentryNative(options); + try { + if (loadLibraryLatch.await(2000, TimeUnit.MILLISECONDS)) { + initSentryNative(options); - // only add scope sync observer if the scope sync is enabled. - if (options.isEnableScopeSync()) { - options.addScopeObserver(new NdkScopeObserver(options)); - } + // only add scope sync observer if the scope sync is enabled. + if (options.isEnableScopeSync()) { + options.addScopeObserver(new NdkScopeObserver(options)); + } - options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader())); + options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader())); + } else { + throw new IllegalStateException("Timeout waiting for Sentry NDK library to load"); + } + } catch (InterruptedException e) { + throw new IllegalStateException( + "Thread interrupted while waiting for NDK libs to be loaded", e); + } } /** Closes the NDK integration */ From 9c193193a14a18f407e55d14eb051c0a4e55eec6 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 14 Oct 2024 21:36:37 +0200 Subject: [PATCH 05/40] Add meta option to attach ANR thread dumps (#3791) * Add meta option to attach ANR thread dumps * Update Changelog --- CHANGELOG.md | 4 +++ .../android/core/ManifestMetadataReader.java | 5 +++- .../core/ManifestMetadataReaderTest.kt | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf0d89cca0..09d00018de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add meta option to attach ANR thread dumps ([#3791](https://github.com/getsentry/sentry-java/pull/3791)) + ### Fixes - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) 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 e42f68cab0..56cd506fe6 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 @@ -26,8 +26,8 @@ final class ManifestMetadataReader { static final String SAMPLE_RATE = "io.sentry.sample-rate"; static final String ANR_ENABLE = "io.sentry.anr.enable"; static final String ANR_REPORT_DEBUG = "io.sentry.anr.report-debug"; - static final String ANR_TIMEOUT_INTERVAL_MILLIS = "io.sentry.anr.timeout-interval-millis"; + static final String ANR_ATTACH_THREAD_DUMPS = "io.sentry.anr.attach-thread-dumps"; static final String AUTO_INIT = "io.sentry.auto-init"; static final String NDK_ENABLE = "io.sentry.ndk.enable"; @@ -176,6 +176,9 @@ static void applyMetadata( ANR_TIMEOUT_INTERVAL_MILLIS, options.getAnrTimeoutIntervalMillis())); + options.setAttachAnrThreadDump( + readBool(metadata, logger, ANR_ATTACH_THREAD_DUMPS, options.isAttachAnrThreadDump())); + final String dsn = readString(metadata, logger, DSN, options.getDsn()); final boolean enabled = readBool(metadata, logger, ENABLE_SENTRY, options.isEnabled()); 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 e068af7b1c..25b2e0191c 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 @@ -246,6 +246,31 @@ class ManifestMetadataReaderTest { assertEquals(5000.toLong(), fixture.options.anrTimeoutIntervalMillis) } + @Test + fun `applyMetadata reads anr attach thread dump to options`() { + // Arrange + val bundle = bundleOf(ManifestMetadataReader.ANR_ATTACH_THREAD_DUMPS to true) + val context = fixture.getContext(metaData = bundle) + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertEquals(true, fixture.options.isAttachAnrThreadDump) + } + + @Test + fun `applyMetadata reads anr attach thread dump to options and keeps default`() { + // Arrange + val context = fixture.getContext() + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertEquals(false, fixture.options.isAttachAnrThreadDump) + } + @Test fun `applyMetadata reads activity breadcrumbs to options`() { // Arrange From 8fdee54dfe034a0d21efbddc1613263d542fdf1c Mon Sep 17 00:00:00 2001 From: Stefano Date: Tue, 15 Oct 2024 12:39:29 +0200 Subject: [PATCH 06/40] fix invalid profiles when the transaction name is empty (#3747) * when the transaction name is empty, the profiles default to "unknown" --- CHANGELOG.md | 1 + .../android/core/AndroidTransactionProfilerTest.kt | 11 +++++++++++ .../src/main/java/io/sentry/ProfilingTraceData.java | 2 +- .../main/java/io/sentry/ProfilingTransactionData.java | 2 +- sentry/src/test/java/io/sentry/JsonSerializerTest.kt | 4 ++-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09d00018de..3f3171a03b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixes +- fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) - Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) - Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt index 02cda7d23b..df64e20b16 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt @@ -360,6 +360,17 @@ class AndroidTransactionProfilerTest { verify(mockExecutorService, never()).submit(any>()) } + @Test + fun `profiling transaction with empty name fallbacks to unknown`() { + val profiler = fixture.getSut(context) + profiler.start() + profiler.bindTransaction(fixture.transaction1) + val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null, fixture.options) + assertNotNull(profilingTraceData) + assertEquals("unknown", profilingTraceData.transactionName) + assertEquals("unknown", profilingTraceData.transactions.first().name) + } + @Test fun `profiler does not throw if traces cannot be written to disk`() { File(fixture.options.profilingTracesDirPath!!).setWritable(false) diff --git a/sentry/src/main/java/io/sentry/ProfilingTraceData.java b/sentry/src/main/java/io/sentry/ProfilingTraceData.java index 17332b5931..3998735917 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTraceData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTraceData.java @@ -144,7 +144,7 @@ public ProfilingTraceData( // Transaction info this.transactions = transactions; - this.transactionName = transactionName; + this.transactionName = transactionName.isEmpty() ? "unknown" : transactionName; this.durationNs = durationNanos; // App info diff --git a/sentry/src/main/java/io/sentry/ProfilingTransactionData.java b/sentry/src/main/java/io/sentry/ProfilingTransactionData.java index 045b859f05..84d149ef15 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTransactionData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTransactionData.java @@ -29,7 +29,7 @@ public ProfilingTransactionData( @NotNull ITransaction transaction, @NotNull Long startNs, @NotNull Long startCpuMs) { this.id = transaction.getEventId().toString(); this.traceId = transaction.getSpanContext().getTraceId().toString(); - this.name = transaction.getName(); + this.name = transaction.getName().isEmpty() ? "unknown" : transaction.getName(); this.relativeStartNs = startNs; this.relativeStartCpuMs = startCpuMs; } diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index ba8ee84d51..37c1870288 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -569,7 +569,7 @@ class JsonSerializerTest { mapOf( "trace_id" to "00000000000000000000000000000000", "relative_cpu_end_ms" to null, - "name" to "", + "name" to "unknown", "relative_start_ns" to 1, "relative_end_ns" to null, "id" to "00000000000000000000000000000000", @@ -578,7 +578,7 @@ class JsonSerializerTest { mapOf( "trace_id" to "00000000000000000000000000000000", "relative_cpu_end_ms" to null, - "name" to "", + "name" to "unknown", "relative_start_ns" to 2, "relative_end_ns" to null, "id" to "00000000000000000000000000000000", From 274c2956310c25995e412e743b2187b4664d1e29 Mon Sep 17 00:00:00 2001 From: Karl Heinz Struggl Date: Tue, 15 Oct 2024 06:23:20 -0700 Subject: [PATCH 07/40] chore: Add action to warn about potentially risky PR changes (#3726) * adds config+action to warn about risky PR changes * updates wording of warning PR comment * added risky files --- .github/file-filters.yml | 12 +++++ .../workflows/changes-in-high-risk-code.yml | 49 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 .github/file-filters.yml create mode 100644 .github/workflows/changes-in-high-risk-code.yml diff --git a/.github/file-filters.yml b/.github/file-filters.yml new file mode 100644 index 0000000000..2b81e2f0b6 --- /dev/null +++ b/.github/file-filters.yml @@ -0,0 +1,12 @@ +# This is used by the action https://github.com/dorny/paths-filter + +high_risk_code: &high_risk_code + # Transport classes + - "sentry/src/main/java/io/sentry/transport/AsyncHttpTransport.java" + - "sentry/src/main/java/io/sentry/transport/HttpConnection.java" + - "sentry/src/main/java/io/sentry/transport/QueuedThreadPoolExecutor.java" + - "sentry/src/main/java/io/sentry/transport/RateLimiter.java" + - "sentry-apache-http-client-5/src/main/java/io/sentry/transport/apache/ApacheHttpClientTransport.java" + + # Class used by hybrid SDKs + - "sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java" diff --git a/.github/workflows/changes-in-high-risk-code.yml b/.github/workflows/changes-in-high-risk-code.yml new file mode 100644 index 0000000000..64decbe48f --- /dev/null +++ b/.github/workflows/changes-in-high-risk-code.yml @@ -0,0 +1,49 @@ +name: Changes In High Risk Code +on: + pull_request: + +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + files-changed: + name: Detect changed files + runs-on: ubuntu-latest + # Map a step output to a job output + outputs: + high_risk_code: ${{ steps.changes.outputs.high_risk_code }} + high_risk_code_files: ${{ steps.changes.outputs.high_risk_code_files }} + steps: + - uses: actions/checkout@v4 + - name: Get changed files + id: changes + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + token: ${{ github.token }} + filters: .github/file-filters.yml + + # Enable listing of files matching each filter. + # Paths to files will be available in `${FILTER_NAME}_files` output variable. + list-files: csv + + validate-high-risk-code: + if: needs.files-changed.outputs.high_risk_code == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - name: Comment on PR to notify of changes in high risk files + uses: actions/github-script@v7 + env: + high_risk_code: ${{ needs.files-changed.outputs.high_risk_code_files }} + with: + script: | + const highRiskFiles = process.env.high_risk_code; + const fileList = highRiskFiles.split(',').map(file => `- [ ] ${file}`).join('\n'); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `### 🚨 Detected changes in high risk code 🚨 \n High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:\n ${fileList}` + }) From 654c2dc2576fa848240bbeefad3f2c6d7665f7cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:50:51 +0000 Subject: [PATCH 08/40] Bump reactivecircus/android-emulator-runner from 2.32.0 to 2.33.0 (#3786) Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.32.0 to 2.33.0. - [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases) - [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md) - [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/f0d1ed2dcad93c7479e8b2f2226c83af54494915...62dbb605bba737720e10b196cb4220d374026a6d) --- updated-dependencies: - dependency-name: reactivecircus/android-emulator-runner dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/agp-matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index b43d40697a..fdb3137209 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -59,7 +59,7 @@ jobs: # We tried to use the cache action to cache gradle stuff, but it made tests slower and timeout - name: Run instrumentation tests - uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915 # pin@v2 + uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2 with: api-level: 30 force-avd-creation: false From 55ea3ccbe309f398d71562c57513d9f16e17a240 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 16 Oct 2024 12:14:52 +0200 Subject: [PATCH 09/40] [QA] Make logging faster on startup (#3793) * Make logging faster on startup * Changelog --- CHANGELOG.md | 1 + .../io/sentry/android/core/AndroidLogger.java | 12 ++++++++++-- .../android/core/ManifestMetadataReader.java | 17 ++++++++--------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3171a03b..1fe35c8e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) - Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) - Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) +- Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) ## 7.15.0 diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java index b0f6b8ac92..ef943c1696 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java @@ -26,7 +26,11 @@ public void log( final @NotNull SentryLevel level, final @NotNull String message, final @Nullable Object... args) { - Log.println(toLogcatLevel(level), tag, String.format(message, args)); + if (args == null || args.length == 0) { + Log.println(toLogcatLevel(level), tag, message); + } else { + Log.println(toLogcatLevel(level), tag, String.format(message, args)); + } } @SuppressWarnings("AnnotateFormatMethod") @@ -36,7 +40,11 @@ public void log( final @Nullable Throwable throwable, final @NotNull String message, final @Nullable Object... args) { - log(level, String.format(message, args), throwable); + if (args == null || args.length == 0) { + log(level, message, throwable); + } else { + log(level, String.format(message, args), throwable); + } } @Override 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 56cd506fe6..babcfdfc98 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 @@ -437,7 +437,7 @@ private static boolean readBool( final @NotNull String key, final boolean defaultValue) { final boolean value = metadata.getBoolean(key, defaultValue); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, value); + logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } @@ -450,10 +450,10 @@ private static boolean readBool( if (metadata.getSerializable(key) != null) { final boolean nonNullDefault = defaultValue == null ? false : true; final boolean bool = metadata.getBoolean(key, nonNullDefault); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, bool); + logger.log(SentryLevel.DEBUG, key + " read: " + bool); return bool; } else { - logger.log(SentryLevel.DEBUG, "%s used default %s", key, defaultValue); + logger.log(SentryLevel.DEBUG, key + " used default " + defaultValue); return defaultValue; } } @@ -464,7 +464,7 @@ private static boolean readBool( final @NotNull String key, final @Nullable String defaultValue) { final String value = metadata.getString(key, defaultValue); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, value); + logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } @@ -474,14 +474,14 @@ private static boolean readBool( final @NotNull String key, final @NotNull String defaultValue) { final String value = metadata.getString(key, defaultValue); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, value); + logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } private static @Nullable List readList( final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { final String value = metadata.getString(key); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, value); + logger.log(SentryLevel.DEBUG, key + " read: " + value); if (value != null) { return Arrays.asList(value.split(",", -1)); } else { @@ -493,7 +493,7 @@ private static boolean readBool( final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { // manifest meta-data only reads float final Double value = ((Float) metadata.getFloat(key, -1)).doubleValue(); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, value); + logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } @@ -504,7 +504,7 @@ private static long readLong( final long defaultValue) { // manifest meta-data only reads int if the value is not big enough final long value = metadata.getInt(key, (int) defaultValue); - logger.log(SentryLevel.DEBUG, "%s read: %s", key, value); + logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } @@ -524,7 +524,6 @@ static boolean isAutoInit(final @NotNull Context context, final @NotNull ILogger if (metadata != null) { autoInit = readBool(metadata, logger, AUTO_INIT, true); } - logger.log(SentryLevel.INFO, "Retrieving auto-init from AndroidManifest.xml"); } catch (Throwable e) { logger.log(SentryLevel.ERROR, "Failed to read auto-init from android manifest metadata.", e); } From 94071dac205bde6a3126cae0a3e35b559e5e915d Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 16 Oct 2024 14:10:55 +0200 Subject: [PATCH 10/40] [QA] Hardcode integration names (#3794) * Hardcode integration names for tracking * Changelog * Fix tests --- CHANGELOG.md | 1 + .../android/core/ActivityBreadcrumbsIntegration.java | 2 +- .../android/core/ActivityLifecycleIntegration.java | 2 +- .../java/io/sentry/android/core/AnrIntegration.java | 2 +- .../java/io/sentry/android/core/AnrV2Integration.java | 2 +- .../core/AppComponentsBreadcrumbsIntegration.java | 2 +- .../sentry/android/core/AppLifecycleIntegration.java | 2 +- .../java/io/sentry/android/core/NdkIntegration.java | 2 +- .../android/core/NetworkBreadcrumbsIntegration.java | 2 +- .../android/core/PhoneStateBreadcrumbsIntegration.java | 2 +- .../sentry/android/core/ScreenshotEventProcessor.java | 2 +- .../android/core/SendCachedEnvelopeIntegration.java | 3 +++ .../core/SystemEventsBreadcrumbsIntegration.java | 2 +- .../android/core/TempSensorBreadcrumbsIntegration.java | 2 +- .../android/core/UserInteractionIntegration.java | 2 +- .../android/core/ViewHierarchyEventProcessor.java | 2 +- .../android/fragment/FragmentLifecycleIntegration.kt | 2 +- .../android/navigation/SentryNavigationListener.kt | 2 +- .../sentry/android/okhttp/SentryOkHttpInterceptor.kt | 2 +- .../java/io/sentry/android/replay/ReplayIntegration.kt | 2 +- .../sentry/android/timber/SentryTimberIntegration.kt | 2 +- .../java/io/sentry/apollo/SentryApolloInterceptor.kt | 2 +- .../java/io/sentry/okhttp/SentryOkHttpInterceptor.kt | 2 +- sentry/api/sentry.api | 1 - .../SendCachedEnvelopeFireAndForgetIntegration.java | 2 +- .../main/java/io/sentry/ShutdownHookIntegration.java | 2 +- .../io/sentry/UncaughtExceptionHandlerIntegration.java | 2 +- .../src/main/java/io/sentry/util/IntegrationUtils.java | 10 ---------- 28 files changed, 28 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe35c8e0f..b2ec56c00c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) - Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) - Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) +- Fix slow Integration name parsing ([#3794](https://github.com/getsentry/sentry-java/pull/3794)) ## 7.15.0 diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java index dc03abe808..d9bfb5cea3 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java @@ -46,7 +46,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio if (enabled) { application.registerActivityLifecycleCallbacks(this); options.getLogger().log(SentryLevel.DEBUG, "ActivityBreadcrumbIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("ActivityBreadcrumbs"); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 6e7a22ac05..14b7ec98fb 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -118,7 +118,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio application.registerActivityLifecycleCallbacks(this); this.options.getLogger().log(SentryLevel.DEBUG, "ActivityLifecycleIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("ActivityLifecycle"); } private boolean isPerformanceEnabled(final @NotNull SentryAndroidOptions options) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java index 1c7b0f2eaf..14fac4753d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java @@ -59,7 +59,7 @@ private void register(final @NotNull IHub hub, final @NotNull SentryAndroidOptio .log(SentryLevel.DEBUG, "AnrIntegration enabled: %s", options.isAnrEnabled()); if (options.isAnrEnabled()) { - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("Anr"); try { options .getExecutorService() diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java index b6be55e90a..618f53554f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java @@ -95,7 +95,7 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) { options.getLogger().log(SentryLevel.DEBUG, "Failed to start AnrProcessor.", e); } options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("AnrV2"); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java index f2d9f6a2a7..e11bd5d3b9 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java @@ -55,7 +55,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio options .getLogger() .log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("AppComponentsBreadcrumbs"); } catch (Throwable e) { this.options.setEnableAppComponentBreadcrumbs(false); options.getLogger().log(SentryLevel.INFO, e, "ComponentCallbacks2 is not available."); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java index 3e8fe6383f..f730f4bc76 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java @@ -96,7 +96,7 @@ private void addObserver(final @NotNull IHub hub) { try { ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher); options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("AppLifecycle"); } catch (Throwable e) { // This is to handle a potential 'AbstractMethodError' gracefully. The error is triggered in // connection with conflicting dependencies of the androidx.lifecycle. diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java index 3a4a91498e..78bcadeade 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java @@ -55,7 +55,7 @@ public final void register(final @NotNull IHub hub, final @NotNull SentryOptions method.invoke(null, args); this.options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("Ndk"); } catch (NoSuchMethodException e) { disableNdkIntegration(this.options); this.options diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java index fa5724e8c5..7610a804f3 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java @@ -96,7 +96,7 @@ public void run() { context, logger, buildInfoProvider, networkCallback); if (registered) { logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("NetworkBreadcrumbs"); } else { logger.log( SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration not installed."); 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 index 2da0452698..249904fd16 100644 --- 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 @@ -81,7 +81,7 @@ private void startTelephonyListener( telephonyManager.listen(listener, android.telephony.PhoneStateListener.LISTEN_CALL_STATE); options.getLogger().log(SentryLevel.DEBUG, "PhoneStateBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("PhoneStateBreadcrumbs"); } catch (Throwable e) { options .getLogger() diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java index 5e07a44078..8cdc2461d2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java @@ -46,7 +46,7 @@ public ScreenshotEventProcessor( DEBOUNCE_MAX_EXECUTIONS); if (options.isAttachScreenshot()) { - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("Screenshot"); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java index 66e534bb7d..6d24508c12 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java @@ -1,5 +1,7 @@ package io.sentry.android.core; +import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; + import io.sentry.DataCategory; import io.sentry.IConnectionStatusProvider; import io.sentry.IHub; @@ -54,6 +56,7 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) { options.getLogger().log(SentryLevel.ERROR, "No cache dir path is defined in options."); return; } + addIntegrationToSdkVersion("SendCachedEnvelope"); sendCachedEnvelopes(hub, this.options); } 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 76422fdf6e..ea838975cd 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 @@ -132,7 +132,7 @@ private void startSystemEventsReceiver( // registerReceiver can throw SecurityException but it's not documented in the official docs ContextUtils.registerReceiver(context, options, receiver, filter); options.getLogger().log(SentryLevel.DEBUG, "SystemEventsBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("SystemEventsBreadcrumbs"); } catch (Throwable e) { options.setEnableSystemEventBreadcrumbs(false); options 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 index 41e1860184..b94a06b976 100644 --- 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 @@ -87,7 +87,7 @@ private void startSensorListener(final @NotNull SentryOptions options) { sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); options.getLogger().log(SentryLevel.DEBUG, "TempSensorBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("TempSensorBreadcrumbs"); } else { options.getLogger().log(SentryLevel.INFO, "TYPE_AMBIENT_TEMPERATURE is not available."); } 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 c361529671..a0ad359166 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 @@ -121,7 +121,7 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) { if (isAndroidXAvailable) { application.registerActivityLifecycleCallbacks(this); this.options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("UserInteraction"); } else { options .getLogger() diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java index 30e9f8de11..eaa9aaa560 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java @@ -55,7 +55,7 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options) DEBOUNCE_MAX_EXECUTIONS); if (options.isAttachViewHierarchy()) { - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("ViewHierarchy"); } } diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt index 4129ea4356..30aa306dc1 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt @@ -49,7 +49,7 @@ class FragmentLifecycleIntegration( application.registerActivityLifecycleCallbacks(this) options.logger.log(DEBUG, "FragmentLifecycleIntegration installed.") - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("FragmentLifecycle") SentryIntegrationPackageStorage.getInstance() .addPackage("maven:io.sentry:sentry-android-fragment", BuildConfig.VERSION_NAME) } diff --git a/sentry-android-navigation/src/main/java/io/sentry/android/navigation/SentryNavigationListener.kt b/sentry-android-navigation/src/main/java/io/sentry/android/navigation/SentryNavigationListener.kt index dac8e54e80..3d020f5a56 100644 --- a/sentry-android-navigation/src/main/java/io/sentry/android/navigation/SentryNavigationListener.kt +++ b/sentry-android-navigation/src/main/java/io/sentry/android/navigation/SentryNavigationListener.kt @@ -49,7 +49,7 @@ class SentryNavigationListener @JvmOverloads constructor( private var activeTransaction: ITransaction? = null init { - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("NavigationListener") SentryIntegrationPackageStorage.getInstance() .addPackage("maven:io.sentry:sentry-android-navigation", BuildConfig.VERSION_NAME) } diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt index 28c242c82c..6e58b7bb10 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt @@ -54,7 +54,7 @@ class SentryOkHttpInterceptor( constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) init { - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("OkHttp") SentryIntegrationPackageStorage.getInstance() .addPackage("maven:io.sentry:sentry-android-okhttp", BuildConfig.VERSION_NAME) } diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt index ee9223cc80..90832585cd 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt @@ -119,7 +119,7 @@ public class ReplayIntegration( options.logger.log(INFO, "ComponentCallbacks is not available, orientation changes won't be handled by Session replay", e) } - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("Replay") SentryIntegrationPackageStorage.getInstance() .addPackage("maven:io.sentry:sentry-android-replay", BuildConfig.VERSION_NAME) diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt index d043faa5f6..f5350512a5 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt @@ -29,7 +29,7 @@ class SentryTimberIntegration( logger.log(SentryLevel.DEBUG, "SentryTimberIntegration installed.") SentryIntegrationPackageStorage.getInstance().addPackage("maven:io.sentry:sentry-android-timber", VERSION_NAME) - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("Timber") } override fun close() { diff --git a/sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt b/sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt index faa8a549a9..8191e48e4a 100644 --- a/sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt +++ b/sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt @@ -40,7 +40,7 @@ class SentryApolloInterceptor( constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) init { - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("Apollo") SentryIntegrationPackageStorage.getInstance().addPackage("maven:io.sentry:sentry-apollo", BuildConfig.VERSION_NAME) } diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt index 5bf93be060..66a9fcc75c 100644 --- a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt @@ -54,7 +54,7 @@ public open class SentryOkHttpInterceptor( public constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) init { - addIntegrationToSdkVersion(javaClass) + addIntegrationToSdkVersion("OkHttp") SentryIntegrationPackageStorage.getInstance() .addPackage("maven:io.sentry:sentry-okhttp", BuildConfig.VERSION_NAME) } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 89684c7ae5..5ca8c00da1 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5690,7 +5690,6 @@ public final class io/sentry/util/HttpUtils { public final class io/sentry/util/IntegrationUtils { public fun ()V - public static fun addIntegrationToSdkVersion (Ljava/lang/Class;)V public static fun addIntegrationToSdkVersion (Ljava/lang/String;)V } diff --git a/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java b/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java index bc813fdd1e..c9e208e6c2 100644 --- a/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java +++ b/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java @@ -79,7 +79,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio options .getLogger() .log(SentryLevel.DEBUG, "SendCachedEventFireAndForgetIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("SendCachedEnvelopeFireAndForget"); sendCachedEnvelopes(hub, options); } diff --git a/sentry/src/main/java/io/sentry/ShutdownHookIntegration.java b/sentry/src/main/java/io/sentry/ShutdownHookIntegration.java index 4ffb47d7d1..d08cbc6ee4 100644 --- a/sentry/src/main/java/io/sentry/ShutdownHookIntegration.java +++ b/sentry/src/main/java/io/sentry/ShutdownHookIntegration.java @@ -37,7 +37,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio () -> { runtime.addShutdownHook(thread); options.getLogger().log(SentryLevel.DEBUG, "ShutdownHookIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("ShutdownHook"); }); } else { options.getLogger().log(SentryLevel.INFO, "enableShutdownHook is disabled."); diff --git a/sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java b/sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java index da594e4cb4..1eddb762aa 100644 --- a/sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java +++ b/sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java @@ -90,7 +90,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio this.options .getLogger() .log(SentryLevel.DEBUG, "UncaughtExceptionHandlerIntegration installed."); - addIntegrationToSdkVersion(getClass()); + addIntegrationToSdkVersion("UncaughtExceptionHandler"); } } diff --git a/sentry/src/main/java/io/sentry/util/IntegrationUtils.java b/sentry/src/main/java/io/sentry/util/IntegrationUtils.java index 6d504c1451..4edbb7aedd 100644 --- a/sentry/src/main/java/io/sentry/util/IntegrationUtils.java +++ b/sentry/src/main/java/io/sentry/util/IntegrationUtils.java @@ -6,16 +6,6 @@ @ApiStatus.Internal public final class IntegrationUtils { - public static void addIntegrationToSdkVersion(final @NotNull Class clazz) { - final String name = - clazz - .getSimpleName() - .replace("Sentry", "") - .replace("Integration", "") - .replace("Interceptor", "") - .replace("EventProcessor", ""); - addIntegrationToSdkVersion(name); - } public static void addIntegrationToSdkVersion(final @NotNull String name) { SentryIntegrationPackageStorage.getInstance().addIntegration(name); From bd82483aef2b10e164d6978b2b51201d2b0a2abd Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 17 Oct 2024 00:17:03 +0200 Subject: [PATCH 11/40] Fix ANRv2 test flakyness (#3798) * try to fix ANRv2 test flakyness * try to fix ANRv2 test flakyness --- .../test/java/io/sentry/android/core/SentryAndroidTest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 d75e0f88a2..c31076d1ff 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 @@ -6,6 +6,7 @@ import android.app.ApplicationExitInfo import android.content.Context import android.os.Build import android.os.Bundle +import android.os.Looper import android.os.SystemClock import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -55,6 +56,7 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.robolectric.Shadows import org.robolectric.annotation.Config import org.robolectric.shadow.api.Shadow import org.robolectric.shadows.ShadowActivityManager @@ -439,8 +441,10 @@ class SentryAndroidTest { await.withAlias("Failed because of BeforeSend callback above, but we swallow BeforeSend exceptions, hence the timeout") .untilTrue(asserted) + // Execute all posted tasks + Shadows.shadowOf(Looper.getMainLooper()).idle() + // assert that persisted values have changed - options.executorService.close(10000L) // finalizes all enqueued persisting tasks assertEquals( "TestActivity", PersistingScopeObserver.read(options, TRANSACTION_FILENAME, String::class.java) From ee6ab9509af7490dc71c5d3d88ad6afe221f2b5a Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 17 Oct 2024 00:26:13 +0200 Subject: [PATCH 12/40] [QA] Make replay lazy and faster (#3799) * Make replay lazy and faster * Changelog --- CHANGELOG.md | 1 + .../DefaultReplayBreadcrumbConverter.kt | 18 ++++++++------- .../android/replay/ScreenshotRecorder.kt | 23 +++++++++++-------- .../replay/capture/BaseCaptureStrategy.kt | 4 ---- .../viewhierarchy/ComposeViewHierarchyNode.kt | 16 ++++++------- .../replay/viewhierarchy/ViewHierarchyNode.kt | 4 ++-- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ec56c00c..32cf654261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) - Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) - Fix slow Integration name parsing ([#3794](https://github.com/getsentry/sentry-java/pull/3794)) +- Session Replay: Reduce startup and capture overhead ([#3799](https://github.com/getsentry/sentry-java/pull/3799)) ## 7.15.0 diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt index c95b72088a..1a6f3fed37 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt @@ -12,14 +12,16 @@ import kotlin.LazyThreadSafetyMode.NONE public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { internal companion object { private val snakecasePattern by lazy(NONE) { "_[a-z]".toRegex() } - private val supportedNetworkData = setOf( - "status_code", - "method", - "response_content_length", - "request_content_length", - "http.response_content_length", - "http.request_content_length" - ) + private val supportedNetworkData by lazy(NONE) { + setOf( + "status_code", + "method", + "response_content_length", + "request_content_length", + "http.response_content_length", + "http.request_content_length" + ) + } } private var lastConnectivityState: String? = null diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt index 8f823fa17c..54f92a8958 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt @@ -36,6 +36,7 @@ import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference +import kotlin.LazyThreadSafetyMode.NONE import kotlin.math.roundToInt @TargetApi(26) @@ -51,15 +52,19 @@ internal class ScreenshotRecorder( } private var rootView: WeakReference? = null private val pendingViewHierarchy = AtomicReference() - private val maskingPaint = Paint() - private val singlePixelBitmap: Bitmap = Bitmap.createBitmap( - 1, - 1, - Bitmap.Config.ARGB_8888 - ) - private val singlePixelBitmapCanvas: Canvas = Canvas(singlePixelBitmap) - private val prescaledMatrix = Matrix().apply { - preScale(config.scaleFactorX, config.scaleFactorY) + private val maskingPaint by lazy(NONE) { Paint() } + private val singlePixelBitmap: Bitmap by lazy(NONE) { + Bitmap.createBitmap( + 1, + 1, + Bitmap.Config.ARGB_8888 + ) + } + private val singlePixelBitmapCanvas: Canvas by lazy(NONE) { Canvas(singlePixelBitmap) } + private val prescaledMatrix by lazy(NONE) { + Matrix().apply { + preScale(config.scaleFactorX, config.scaleFactorY) + } } private val contentChanged = AtomicBoolean(false) private val isCapturing = AtomicBoolean(true) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt index fcd2d11293..4b37eafc78 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt @@ -209,10 +209,6 @@ internal abstract class BaseCaptureStrategy( } } - init { - runInBackground { onChange(propertyName, initialValue, initialValue) } - } - override fun getValue(thisRef: Any?, property: KProperty<*>): T? = value.get() override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt index 888528f769..5640fbc96f 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.findRootCoordinates -import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.node.LayoutNode import androidx.compose.ui.node.Owner import androidx.compose.ui.semantics.SemanticsActions @@ -87,12 +86,13 @@ internal object ComposeViewHierarchyNode { (semantics == null || !semantics.contains(SemanticsProperties.InvisibleToUser)) && visibleRect.height() > 0 && visibleRect.width() > 0 val isEditable = semantics?.contains(SemanticsActions.SetText) == true - val positionInWindow = node.coordinates.positionInWindow() return when { semantics?.contains(SemanticsProperties.Text) == true || isEditable -> { val shouldMask = isVisible && node.shouldMask(isImage = false, options) parent?.setImportantForCaptureToAncestors(true) + // TODO: if we get reports that it's slow, we can drop this, and just mask + // TODO: the whole view instead of per-line val textLayoutResults = mutableListOf() semantics?.getOrNull(SemanticsActions.GetTextLayoutResult) ?.action @@ -108,8 +108,8 @@ internal object ComposeViewHierarchyNode { TextViewHierarchyNode( layout = if (textLayoutResults.isNotEmpty() && !isEditable) ComposeTextLayout(textLayoutResults.first(), hasFillModifier) else null, dominantColor = textColor?.toArgb()?.toOpaque(), - x = positionInWindow.x, - y = positionInWindow.y, + x = visibleRect.left.toFloat(), + y = visibleRect.top.toFloat(), width = node.width, height = node.height, elevation = (parent?.elevation ?: 0f), @@ -128,8 +128,8 @@ internal object ComposeViewHierarchyNode { parent?.setImportantForCaptureToAncestors(true) ImageViewHierarchyNode( - x = positionInWindow.x, - y = positionInWindow.y, + x = visibleRect.left.toFloat(), + y = visibleRect.top.toFloat(), width = node.width, height = node.height, elevation = (parent?.elevation ?: 0f), @@ -147,8 +147,8 @@ internal object ComposeViewHierarchyNode { // TODO: traverse the ViewHierarchyNode here again. For now we can recommend // TODO: using custom modifiers to obscure the entire node if it's sensitive GenericViewHierarchyNode( - x = positionInWindow.x, - y = positionInWindow.y, + x = visibleRect.left.toFloat(), + y = visibleRect.top.toFloat(), width = node.width, height = node.height, elevation = (parent?.elevation ?: 0f), diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt index ef05ecb029..03cb37ad3e 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt @@ -239,8 +239,8 @@ sealed class ViewHierarchyNode( private fun Class<*>.isAssignableFrom(set: Set): Boolean { var cls: Class<*>? = this while (cls != null) { - val canonicalName = cls.canonicalName - if (canonicalName != null && set.contains(canonicalName)) { + val canonicalName = cls.name + if (set.contains(canonicalName)) { return true } cls = cls.superclass From 31f96ce3eb4e57cd0fd8591dc8d90c38590bdf56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:20:59 +0000 Subject: [PATCH 13/40] Bump codecov/codecov-action from 4.5.0 to 4.6.0 (#3768) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.5.0 to 4.6.0. - [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/e28ff129e5465c2c0dcc6f003fc735cb6ae0c673...b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238) --- 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> --- .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 f4b8d8431c..2ddd821739 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@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v4 with: name: sentry-java fail_ci_if_error: false From 2a8b4fef3ab168d7505cdc0f80c5dfbc485c34e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:28:56 +0000 Subject: [PATCH 14/40] Bump github/codeql-action from 3.26.8 to 3.26.12 (#3787) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.8 to 3.26.12. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/294a9d92911152fe08befb9ec03e240add280cb3...c36620d31ac7c881962c3d9dd939c40ec9434f2b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fa0bbbff64..10c73810e7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: gradle-home-cache-cleanup: true - name: Initialize CodeQL - uses: github/codeql-action/init@294a9d92911152fe08befb9ec03e240add280cb3 # pin@v2 + uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # pin@v2 with: languages: ${{ matrix.language }} @@ -55,4 +55,4 @@ jobs: ./gradlew buildForCodeQL - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@294a9d92911152fe08befb9ec03e240add280cb3 # pin@v2 + uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # pin@v2 From 87bdc75724133fcfb97153e3d674fad90582d2c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:35:41 +0000 Subject: [PATCH 15/40] Bump gradle/actions (#3788) Bumps [gradle/actions](https://github.com/gradle/actions) from 0d30c9111cf47a838eb69c06d13f3f51ab2ed76f to bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/0d30c9111cf47a838eb69c06d13f3f51ab2ed76f...bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b) --- updated-dependencies: - dependency-name: gradle/actions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/agp-matrix.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/enforce-license-compliance.yml | 2 +- .github/workflows/generate-javadocs.yml | 2 +- .github/workflows/integration-tests-benchmarks.yml | 4 ++-- .github/workflows/integration-tests-ui.yml | 2 +- .github/workflows/release-build.yml | 2 +- .github/workflows/system-tests-backend.yml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index fdb3137209..182c0fa19b 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -38,7 +38,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ddd821739..6b885942e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 10c73810e7..7ae4f25c65 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index c2ddec5865..2b87c1d278 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/generate-javadocs.yml b/.github/workflows/generate-javadocs.yml index dd171af5a2..2a167621f3 100644 --- a/.github/workflows/generate-javadocs.yml +++ b/.github/workflows/generate-javadocs.yml @@ -20,7 +20,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-benchmarks.yml b/.github/workflows/integration-tests-benchmarks.yml index f0beaa60b5..58fc933752 100644 --- a/.github/workflows/integration-tests-benchmarks.yml +++ b/.github/workflows/integration-tests-benchmarks.yml @@ -37,7 +37,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true @@ -86,7 +86,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-ui.yml b/.github/workflows/integration-tests-ui.yml index 771b4b5c8c..6c4ad6564f 100644 --- a/.github/workflows/integration-tests-ui.yml +++ b/.github/workflows/integration-tests-ui.yml @@ -32,7 +32,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index cb6752bb93..83a3c82f8c 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -26,7 +26,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index 31331229fe..0098644d97 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -40,7 +40,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3 + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 with: gradle-home-cache-cleanup: true From a8c72dd392b42bca983d2e56a9e1753774b2683f Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 17 Oct 2024 10:45:41 +0200 Subject: [PATCH 16/40] Fix ensure ndk libs are loaded before calling close (#3797) --- .../main/java/io/sentry/android/ndk/SentryNdk.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 7245516b49..3429780eec 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 @@ -69,6 +69,15 @@ public static void init(@NotNull final SentryAndroidOptions options) { /** Closes the NDK integration */ public static void close() { - shutdown(); + try { + if (loadLibraryLatch.await(2000, TimeUnit.MILLISECONDS)) { + shutdown(); + } else { + throw new IllegalStateException("Timeout waiting for Sentry NDK library to load"); + } + } catch (InterruptedException e) { + throw new IllegalStateException( + "Thread interrupted while waiting for NDK libs to be loaded", e); + } } } From eb5d294f88fe988387ca8fb267d6315a45758c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Kozi=C5=84ski?= Date: Thu, 17 Oct 2024 10:57:50 +0200 Subject: [PATCH 17/40] docs(okhttp): update documented default value to match actual (#3800) Co-authored-by: Alexander Dinauer Co-authored-by: Stefano --- .../src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt index 66a9fcc75c..601e6d56d4 100644 --- a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt @@ -33,7 +33,7 @@ import java.io.IOException * @param hub The [IHub], internal and only used for testing. * @param beforeSpan The [ISpan] can be customized or dropped with the [BeforeSpanCallback]. * @param captureFailedRequests The SDK will only capture HTTP Client errors if it is enabled, - * Defaults to false. + * Defaults to true. * @param failedRequestStatusCodes The SDK will only capture HTTP Client errors if the HTTP Response * status code is within the defined ranges. * @param failedRequestTargets The SDK will only capture HTTP Client errors if the HTTP Request URL From 143f91afb8523188e7319e3324b9d7064ba83a9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:03:52 +0000 Subject: [PATCH 18/40] Bump JamesIves/github-pages-deploy-action from 4.6.4 to 4.6.8 (#3728) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.6.4 to 4.6.8. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/920cbb300dcd3f0568dbc42700c61e2fd9e6139c...881db5376404c5c8d621010bcbec0310b58d5e29) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/generate-javadocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-javadocs.yml b/.github/workflows/generate-javadocs.yml index 2a167621f3..b540441fb7 100644 --- a/.github/workflows/generate-javadocs.yml +++ b/.github/workflows/generate-javadocs.yml @@ -28,7 +28,7 @@ jobs: run: | ./gradlew aggregateJavadocs - name: Deploy - uses: JamesIves/github-pages-deploy-action@920cbb300dcd3f0568dbc42700c61e2fd9e6139c # pin@4.6.4 + uses: JamesIves/github-pages-deploy-action@881db5376404c5c8d621010bcbec0310b58d5e29 # pin@4.6.8 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages From 9182d863db86e7fd645d03ad6d19b441930b1067 Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 17 Oct 2024 11:45:58 +0200 Subject: [PATCH 19/40] [QA] Cache parsed Dsn (#3796) * parsed Dsn object is now cached * updated LazyEvaluator javadoc * setting a new dsn resets the cached parsed dsn --- CHANGELOG.md | 1 + sentry/api/sentry.api | 1 + sentry/src/main/java/io/sentry/Baggage.java | 6 +++--- sentry/src/main/java/io/sentry/DsnUtil.java | 2 +- .../java/io/sentry/RequestDetailsResolver.java | 2 +- sentry/src/main/java/io/sentry/Sentry.java | 4 ++-- .../src/main/java/io/sentry/SentryOptions.java | 17 ++++++++++++++++- .../main/java/io/sentry/util/LazyEvaluator.java | 13 ++++++++++++- .../java/io/sentry/util/LazyEvaluatorTest.kt | 14 ++++++++++++++ 9 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32cf654261..76776ce6a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixes +- Cache parsed Dsn ([#3796](https://github.com/getsentry/sentry-java/pull/3796)) - fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) - Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 5ca8c00da1..ed06b29b35 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5703,6 +5703,7 @@ public final class io/sentry/util/JsonSerializationUtils { public final class io/sentry/util/LazyEvaluator { public fun (Lio/sentry/util/LazyEvaluator$Evaluator;)V public fun getValue ()Ljava/lang/Object; + public fun resetValue ()V public fun setValue (Ljava/lang/Object;)V } diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index d736358ee1..0a80b154bf 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -134,7 +134,7 @@ public static Baggage fromEvent( final Baggage baggage = new Baggage(options.getLogger()); final SpanContext trace = event.getContexts().getTrace(); baggage.setTraceId(trace != null ? trace.getTraceId().toString() : null); - baggage.setPublicKey(new Dsn(options.getDsn()).getPublicKey()); + baggage.setPublicKey(options.getParsedDsn().getPublicKey()); baggage.setRelease(event.getRelease()); baggage.setEnvironment(event.getEnvironment()); final User user = event.getUser(); @@ -405,7 +405,7 @@ public void setValuesFromTransaction( final @NotNull SentryOptions sentryOptions, final @Nullable TracesSamplingDecision samplingDecision) { setTraceId(transaction.getSpanContext().getTraceId().toString()); - setPublicKey(new Dsn(sentryOptions.getDsn()).getPublicKey()); + setPublicKey(sentryOptions.getParsedDsn().getPublicKey()); setRelease(sentryOptions.getRelease()); setEnvironment(sentryOptions.getEnvironment()); setUserSegment(user != null ? getSegment(user) : null); @@ -427,7 +427,7 @@ public void setValuesFromScope( final @Nullable User user = scope.getUser(); final @NotNull SentryId replayId = scope.getReplayId(); setTraceId(propagationContext.getTraceId().toString()); - setPublicKey(new Dsn(options.getDsn()).getPublicKey()); + setPublicKey(options.getParsedDsn().getPublicKey()); setRelease(options.getRelease()); setEnvironment(options.getEnvironment()); if (!SentryId.EMPTY_ID.equals(replayId)) { diff --git a/sentry/src/main/java/io/sentry/DsnUtil.java b/sentry/src/main/java/io/sentry/DsnUtil.java index 3c48e4a43e..6cc0dc360b 100644 --- a/sentry/src/main/java/io/sentry/DsnUtil.java +++ b/sentry/src/main/java/io/sentry/DsnUtil.java @@ -23,7 +23,7 @@ public static boolean urlContainsDsnHost(@Nullable SentryOptions options, @Nulla return false; } - final @NotNull Dsn dsn = new Dsn(dsnString); + final @NotNull Dsn dsn = options.getParsedDsn(); final @NotNull URI sentryUri = dsn.getSentryUri(); final @Nullable String dsnHost = sentryUri.getHost(); diff --git a/sentry/src/main/java/io/sentry/RequestDetailsResolver.java b/sentry/src/main/java/io/sentry/RequestDetailsResolver.java index b4474893a2..6083c69e99 100644 --- a/sentry/src/main/java/io/sentry/RequestDetailsResolver.java +++ b/sentry/src/main/java/io/sentry/RequestDetailsResolver.java @@ -21,7 +21,7 @@ public RequestDetailsResolver(final @NotNull SentryOptions options) { @NotNull RequestDetails resolve() { - final Dsn dsn = new Dsn(options.getDsn()); + final Dsn dsn = options.getParsedDsn(); final URI sentryUri = dsn.getSentryUri(); final String envelopeUrl = sentryUri.resolve(sentryUri.getPath() + "/envelope/").toString(); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 08571e151a..7c34720d19 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -381,8 +381,8 @@ private static boolean initConfigurations(final @NotNull SentryOptions options) "DSN is required. Use empty string or set enabled to false in SentryOptions to disable SDK."); } - @SuppressWarnings("unused") - final Dsn parsedDsn = new Dsn(dsn); + // This creates the DSN object and performs some checks + options.getParsedDsn(); ILogger logger = options.getLogger(); diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 0eb3bace91..91a1372fed 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -82,6 +82,9 @@ public class SentryOptions { */ private @Nullable String dsn; + /** Parsed DSN to avoid parsing it every time. */ + private final @NotNull LazyEvaluator parsedDsn = new LazyEvaluator<>(() -> new Dsn(dsn)); + /** dsnHash is used as a subfolder of cacheDirPath to isolate events when rotating DSNs */ private @Nullable String dsnHash; @@ -529,7 +532,7 @@ public void addIntegration(@NotNull Integration integration) { } /** - * Returns the DSN + * Returns the DSN. * * @return the DSN or null if not set */ @@ -537,6 +540,17 @@ public void addIntegration(@NotNull Integration integration) { return dsn; } + /** + * Evaluates and parses the DSN. May throw an exception if the DSN is invalid. + * + * @return the parsed DSN or throws if dsn is invalid + */ + @ApiStatus.Internal + @NotNull + Dsn getParsedDsn() throws IllegalArgumentException { + return parsedDsn.getValue(); + } + /** * Sets the DSN * @@ -544,6 +558,7 @@ public void addIntegration(@NotNull Integration integration) { */ public void setDsn(final @Nullable String dsn) { this.dsn = dsn; + this.parsedDsn.resetValue(); dsnHash = StringUtils.calculateStringHash(this.dsn, logger); } diff --git a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java index d540cbe508..8b3a6cce53 100644 --- a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java +++ b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java @@ -25,7 +25,8 @@ public LazyEvaluator(final @NotNull Evaluator evaluator) { } /** - * Executes the evaluator function and caches its result, so that it's called only once. + * 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. */ @@ -48,6 +49,16 @@ public void setValue(final @Nullable T 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 Evaluator { @NotNull T evaluate(); diff --git a/sentry/src/test/java/io/sentry/util/LazyEvaluatorTest.kt b/sentry/src/test/java/io/sentry/util/LazyEvaluatorTest.kt index 8f0e3bc0a7..a238205405 100644 --- a/sentry/src/test/java/io/sentry/util/LazyEvaluatorTest.kt +++ b/sentry/src/test/java/io/sentry/util/LazyEvaluatorTest.kt @@ -38,4 +38,18 @@ class LazyEvaluatorTest { assertEquals(1, evaluator.value) assertEquals(1, fixture.count) } + + @Test + fun `evaluates again after resetValue`() { + val evaluator = fixture.getSut() + assertEquals(0, fixture.count) + assertEquals(1, evaluator.value) + assertEquals(1, evaluator.value) + assertEquals(1, fixture.count) + // Evaluate again, only once + evaluator.resetValue() + assertEquals(2, evaluator.value) + assertEquals(2, evaluator.value) + assertEquals(2, fixture.count) + } } From f14a2996d18d2973bdb511c584966b63bb1cf99c Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 17 Oct 2024 12:31:48 +0000 Subject: [PATCH 20/40] release: 7.16.0-alpha.1 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76776ce6a7..7cf8b80cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 7.16.0-alpha.1 ### Features diff --git a/gradle.properties b/gradle.properties index 170db6c944..73dc376308 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ android.useAndroidX=true android.defaults.buildfeatures.buildconfig=true # Release information -versionName=7.15.0 +versionName=7.16.0-alpha.1 # Override the SDK name on native crashes on Android sentryAndroidSdkName=sentry.native.android From 0132cddd4dc4f595e45fb47526244ad32cee1c4f Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 17 Oct 2024 15:42:41 +0200 Subject: [PATCH 21/40] [QA] Replace setOf with HashSet.add (#3801) * remove kotlin setOf() and replace it with HashSet<>() + add() --- CHANGELOG.md | 1 + .../api/sentry-android-fragment.api | 5 +++++ .../fragment/FragmentLifecycleIntegration.kt | 4 ++-- .../android/fragment/FragmentLifecycleState.kt | 18 +++++++++++++++++- .../SentryFragmentLifecycleCallbacks.kt | 4 ++-- .../fragment/FragmentLifecycleStateTest.kt | 11 +++++++++++ .../SentryFragmentLifecycleCallbacksTest.kt | 2 +- .../replay/DefaultReplayBreadcrumbConverter.kt | 16 +++++++--------- 8 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleStateTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf8b80cac..c7e5c4149b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixes +- Replace setOf with HashSet.add ([#3801](https://github.com/getsentry/sentry-java/pull/3801)) - Cache parsed Dsn ([#3796](https://github.com/getsentry/sentry-java/pull/3796)) - fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) diff --git a/sentry-android-fragment/api/sentry-android-fragment.api b/sentry-android-fragment/api/sentry-android-fragment.api index 4b3487c36e..d850abedca 100644 --- a/sentry-android-fragment/api/sentry-android-fragment.api +++ b/sentry-android-fragment/api/sentry-android-fragment.api @@ -24,6 +24,7 @@ public final class io/sentry/android/fragment/FragmentLifecycleIntegration : and public final class io/sentry/android/fragment/FragmentLifecycleState : java/lang/Enum { public static final field ATTACHED Lio/sentry/android/fragment/FragmentLifecycleState; public static final field CREATED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field Companion Lio/sentry/android/fragment/FragmentLifecycleState$Companion; public static final field DESTROYED Lio/sentry/android/fragment/FragmentLifecycleState; public static final field DETACHED Lio/sentry/android/fragment/FragmentLifecycleState; public static final field PAUSED Lio/sentry/android/fragment/FragmentLifecycleState; @@ -37,6 +38,10 @@ public final class io/sentry/android/fragment/FragmentLifecycleState : java/lang public static fun values ()[Lio/sentry/android/fragment/FragmentLifecycleState; } +public final class io/sentry/android/fragment/FragmentLifecycleState$Companion { + public final fun getStates ()Ljava/util/HashSet; +} + public final class io/sentry/android/fragment/SentryFragmentLifecycleCallbacks : androidx/fragment/app/FragmentManager$FragmentLifecycleCallbacks { public static final field Companion Lio/sentry/android/fragment/SentryFragmentLifecycleCallbacks$Companion; public static final field FRAGMENT_LOAD_OP Ljava/lang/String; diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt index 30aa306dc1..bd94c58684 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt @@ -24,7 +24,7 @@ class FragmentLifecycleIntegration( constructor(application: Application) : this( application = application, - filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet(), + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.states, enableAutoFragmentLifecycleTracing = false ) @@ -34,7 +34,7 @@ class FragmentLifecycleIntegration( enableAutoFragmentLifecycleTracing: Boolean ) : this( application = application, - filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet() + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.states .takeIf { enableFragmentLifecycleBreadcrumbs } .orEmpty(), enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt index cdc5ea999d..fd52437d60 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt @@ -11,5 +11,21 @@ enum class FragmentLifecycleState(internal val breadcrumbName: String) { STOPPED("stopped"), VIEW_DESTROYED("view destroyed"), DESTROYED("destroyed"), - DETACHED("detached") + DETACHED("detached"); + + companion object { + val states = HashSet().apply { + add(ATTACHED) + add(SAVE_INSTANCE_STATE) + add(CREATED) + add(VIEW_CREATED) + add(STARTED) + add(RESUMED) + add(PAUSED) + add(STOPPED) + add(VIEW_DESTROYED) + add(DESTROYED) + add(DETACHED) + } + } } diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt index 18468b99c1..983d17464b 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt @@ -31,7 +31,7 @@ class SentryFragmentLifecycleCallbacks( enableAutoFragmentLifecycleTracing: Boolean ) : this( hub = hub, - filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet() + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.states .takeIf { enableFragmentLifecycleBreadcrumbs } .orEmpty(), enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing @@ -42,7 +42,7 @@ class SentryFragmentLifecycleCallbacks( enableAutoFragmentLifecycleTracing: Boolean = false ) : this( hub = HubAdapter.getInstance(), - filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet() + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.states .takeIf { enableFragmentLifecycleBreadcrumbs } .orEmpty(), enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing diff --git a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleStateTest.kt b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleStateTest.kt new file mode 100644 index 0000000000..b5ab1f19d2 --- /dev/null +++ b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleStateTest.kt @@ -0,0 +1,11 @@ +package io.sentry.android.fragment + +import kotlin.test.Test +import kotlin.test.assertEquals + +class FragmentLifecycleStateTest { + @Test + fun `states contains all states`() { + assertEquals(FragmentLifecycleState.states, FragmentLifecycleState.values().toSet()) + } +} diff --git a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt index 812d78de30..249d40d299 100644 --- a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt +++ b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt @@ -40,7 +40,7 @@ class SentryFragmentLifecycleCallbacksTest { val span = mock() fun getSut( - loggedFragmentLifecycleStates: Set = FragmentLifecycleState.values().toSet(), + loggedFragmentLifecycleStates: Set = FragmentLifecycleState.states, enableAutoFragmentLifecycleTracing: Boolean = false, tracesSampleRate: Double? = 1.0, isAdded: Boolean = true diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt index 1a6f3fed37..d5c666b5b0 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt @@ -12,15 +12,13 @@ import kotlin.LazyThreadSafetyMode.NONE public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { internal companion object { private val snakecasePattern by lazy(NONE) { "_[a-z]".toRegex() } - private val supportedNetworkData by lazy(NONE) { - setOf( - "status_code", - "method", - "response_content_length", - "request_content_length", - "http.response_content_length", - "http.request_content_length" - ) + private val supportedNetworkData = HashSet().apply { + add("status_code") + add("method") + add("response_content_length") + add("request_content_length") + add("http.response_content_length") + add("http.request_content_length") } } From 0cc92623d159ad340653a539366b226986e20090 Mon Sep 17 00:00:00 2001 From: Stefano Date: Fri, 18 Oct 2024 10:47:11 +0200 Subject: [PATCH 22/40] [QA] Load lazy fields on init in the background (#3803) * Lazy fields in SentryOptions are now loaded on init in the background * use option's executor instead of a new thread to load fields --- CHANGELOG.md | 8 +++++++- sentry/src/main/java/io/sentry/Sentry.java | 16 ++++++++++++++++ .../src/main/java/io/sentry/SentryOptions.java | 11 +++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e5c4149b..9eb6d75604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### Fixes + +- Load lazy fields on init in the background ([#3803](https://github.com/getsentry/sentry-java/pull/3803)) +- Replace setOf with HashSet.add ([#3801](https://github.com/getsentry/sentry-java/pull/3801)) + ## 7.16.0-alpha.1 ### Features @@ -8,7 +15,6 @@ ### Fixes -- Replace setOf with HashSet.add ([#3801](https://github.com/getsentry/sentry-java/pull/3801)) - Cache parsed Dsn ([#3796](https://github.com/getsentry/sentry-java/pull/3796)) - fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 7c34720d19..6e4a2530a7 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -217,6 +217,10 @@ public static void init(final @NotNull SentryOptions options) { * @param options options the SentryOptions * @param globalHubMode the globalHubMode */ + @SuppressWarnings({ + "Convert2MethodRef", + "FutureReturnValueIgnored" + }) // older AGP versions do not support method references private static synchronized void init( final @NotNull SentryOptions options, final boolean globalHubMode) { if (isEnabled()) { @@ -231,6 +235,18 @@ private static synchronized void init( return; } + // load lazy fields of the options in a separate thread + try { + options.getExecutorService().submit(() -> options.loadLazyFields()); + } catch (RejectedExecutionException e) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Failed to call the executor. Lazy fields will not be loaded. Did you call Sentry.close()?", + e); + } + options.getLogger().log(SentryLevel.INFO, "GlobalHubMode: '%s'", String.valueOf(globalHubMode)); Sentry.globalHubMode = globalHubMode; diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 91a1372fed..3c286f2bff 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -2451,6 +2451,17 @@ public void setEnableScreenTracking(final boolean enableScreenTracking) { this.enableScreenTracking = enableScreenTracking; } + /** + * Load the lazy fields. Useful to load in the background, so that results are already cached. DO + * NOT CALL THIS METHOD ON THE MAIN THREAD. + */ + void loadLazyFields() { + getSerializer(); + getParsedDsn(); + getEnvelopeReader(); + getDateProvider(); + } + /** The BeforeSend callback */ public interface BeforeSendCallback { From 4988d5bf9e0a5be708853cf53d07b8503e4c77be Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Mon, 21 Oct 2024 16:37:40 +0200 Subject: [PATCH 23/40] test(ui): Add critical tests run by Maestro (#3802) --- .../integration-tests-ui-critical.yml | 106 ++++++++++++++++++ .github/workflows/system-tests-backend.yml | 33 +++++- Makefile | 10 +- build.gradle.kts | 1 + scripts/test-ui-critical.sh | 35 ++++++ .../sentry-uitest-android-critical/.gitignore | 2 + .../build.gradle.kts | 69 ++++++++++++ .../maestro/corruptEnvelope.yaml | 11 ++ .../maestro/crash.yaml | 6 + .../proguard-rules.pro | 21 ++++ .../src/main/AndroidManifest.xml | 21 ++++ .../uitest/android/critical/MainActivity.kt | 51 +++++++++ settings.gradle.kts | 1 + 13 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/integration-tests-ui-critical.yml create mode 100755 scripts/test-ui-critical.sh create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/.gitignore create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/maestro/corruptEnvelope.yaml create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/maestro/crash.yaml create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/proguard-rules.pro create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/src/main/AndroidManifest.xml create mode 100644 sentry-android-integration-tests/sentry-uitest-android-critical/src/main/java/io/sentry/uitest/android/critical/MainActivity.kt diff --git a/.github/workflows/integration-tests-ui-critical.yml b/.github/workflows/integration-tests-ui-critical.yml new file mode 100644 index 0000000000..6729e40ca5 --- /dev/null +++ b/.github/workflows/integration-tests-ui-critical.yml @@ -0,0 +1,106 @@ +name: UI Tests Critical + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + BASE_PATH: "sentry-android-integration-tests/sentry-uitest-android-critical" + BUILD_PATH: "build/outputs/apk/release" + APK_NAME: "sentry-uitest-android-critical-release.apk" + APK_ARTIFACT_NAME: "sentry-uitest-android-critical-release" + MAESTRO_VERSION: "1.39.0" + +jobs: + build: + name: Build sentry-uitest-android-critical + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Java 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + with: + gradle-home-cache-cleanup: true + + - name: Build debug APK + run: make assembleUiTestCriticalRelease + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: ${{env.APK_ARTIFACT_NAME}} + path: "${{env.BASE_PATH}}/${{env.BUILD_PATH}}/${{env.APK_NAME}}" + retention-days: 1 + + run-maestro-tests: + name: Run Maestro Tests + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup KVM + shell: bash + run: | + # check if virtualization is supported... + sudo apt install -y --no-install-recommends cpu-checker coreutils && echo "CPUs=$(nproc --all)" && kvm-ok + # allow access to KVM to run the emulator + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Download APK artifact + uses: actions/download-artifact@v4 + with: + name: ${{env.APK_ARTIFACT_NAME}} + + - name: Install Maestro + uses: dniHze/maestro-test-action@bda8a93211c86d0a05b7a4597c5ad134566fbde4 # pin@v1.0.0 + with: + version: ${{env.MAESTRO_VERSION}} + + - name: Run tests + uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915 # pin@v2.32.0 + with: + api-level: 30 + force-avd-creation: false + disable-animations: true + disable-spellchecker: true + target: 'aosp_atd' + channel: canary # Necessary for ATDs + emulator-options: > + -no-window + -no-snapshot-save + -gpu swiftshader_indirect + -noaudio + -no-boot-anim + -camera-back none + -camera-front none + -timezone US/Pacific + script: | + adb install -r -d "${{env.APK_NAME}}" + maestro test "${{env.BASE_PATH}}/maestro" --debug-output "${{env.BASE_PATH}}/maestro-logs" + + - name: Upload Maestro test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: maestro-logs + path: "${{env.BASE_PATH}}/maestro-logs" + retention-days: 1 diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index 0098644d97..2222f910ad 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -46,11 +46,33 @@ jobs: - name: Exclude android modules from build run: | - sed -i -e '/.*"sentry-android-ndk",/d' -e '/.*"sentry-android",/d' -e '/.*"sentry-compose",/d' -e '/.*"sentry-android-core",/d' -e '/.*"sentry-android-fragment",/d' -e '/.*"sentry-android-navigation",/d' -e '/.*"sentry-android-okhttp",/d' -e '/.*"sentry-android-sqlite",/d' -e '/.*"sentry-android-timber",/d' -e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' -e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' -e '/.*"sentry-android-integration-tests:test-app-sentry",/d' -e '/.*"sentry-samples:sentry-samples-android",/d' -e '/.*"sentry-android-replay",/d' settings.gradle.kts + sed -i \ + -e '/.*"sentry-android-ndk",/d' \ + -e '/.*"sentry-android",/d' \ + -e '/.*"sentry-compose",/d' \ + -e '/.*"sentry-android-core",/d' \ + -e '/.*"sentry-android-fragment",/d' \ + -e '/.*"sentry-android-navigation",/d' \ + -e '/.*"sentry-android-okhttp",/d' \ + -e '/.*"sentry-android-sqlite",/d' \ + -e '/.*"sentry-android-timber",/d' \ + -e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' \ + -e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' \ + -e '/.*"sentry-android-integration-tests:sentry-uitest-android-critical",/d' \ + -e '/.*"sentry-android-integration-tests:test-app-sentry",/d' \ + -e '/.*"sentry-samples:sentry-samples-android",/d' \ + -e '/.*"sentry-android-replay",/d' \ + settings.gradle.kts - name: Exclude android modules from ignore list run: | - sed -i -e '/.*"sentry-uitest-android",/d' -e '/.*"sentry-uitest-android-benchmark",/d' -e '/.*"test-app-sentry",/d' -e '/.*"sentry-samples-android",/d' build.gradle.kts + sed -i \ + -e '/.*"sentry-uitest-android",/d' \ + -e '/.*"sentry-uitest-android-benchmark",/d' \ + -e '/.*"sentry-uitest-android-critical",/d' \ + -e '/.*"test-app-sentry",/d' \ + -e '/.*"sentry-samples-android",/d' \ + build.gradle.kts - name: Build server jar run: | @@ -58,7 +80,12 @@ jobs: - name: Start server and run integration test for sentry-cli commands run: | - test/system-test-sentry-server-start.sh > sentry-mock-server.txt 2>&1 & test/system-test-spring-server-start.sh "${{ matrix.sample }}" > spring-server.txt 2>&1 & test/wait-for-spring.sh && ./gradlew :sentry-samples:${{ matrix.sample }}:systemTest + test/system-test-sentry-server-start.sh \ + > sentry-mock-server.txt 2>&1 & \ + test/system-test-spring-server-start.sh "${{ matrix.sample }}" \ + > spring-server.txt 2>&1 & \ + test/wait-for-spring.sh && \ + ./gradlew :sentry-samples:${{ matrix.sample }}:systemTest - name: Upload test results if: always() diff --git a/Makefile b/Makefile index 2117e6da21..62e6e258f3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean compile javadocs dryRelease update stop checkFormat format api assembleBenchmarkTestRelease assembleUiTestRelease createCoverageReports check preMerge publish +.PHONY: all clean compile javadocs dryRelease update stop checkFormat format api assembleBenchmarkTestRelease assembleUiTestRelease assembleUiTestCriticalRelease createCoverageReports runUiTestCritical check preMerge publish all: stop clean javadocs compile createCoverageReports assembleBenchmarks: assembleBenchmarkTestRelease @@ -53,6 +53,14 @@ assembleUiTestRelease: ./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease ./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest -DtestBuildType=release +# Assemble release of the uitest-android-critical module +assembleUiTestCriticalRelease: + ./gradlew :sentry-android-integration-tests:sentry-uitest-android-critical:assembleRelease + +# Run Maestro tests for the uitest-android-critical module +runUiTestCritical: + ./scripts/test-ui-critical.sh + # Create coverage reports # - Jacoco for Java & Android modules # - Kover for KMP modules e.g sentry-compose diff --git a/build.gradle.kts b/build.gradle.kts index 7985a55486..9d53252562 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -68,6 +68,7 @@ apiValidation { "sentry-samples-spring-boot-webflux-jakarta", "sentry-uitest-android", "sentry-uitest-android-benchmark", + "sentry-uitest-android-critical", "test-app-plain", "test-app-sentry", "sentry-samples-netflix-dgs" diff --git a/scripts/test-ui-critical.sh b/scripts/test-ui-critical.sh new file mode 100755 index 0000000000..7bb36eebec --- /dev/null +++ b/scripts/test-ui-critical.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +echo "Checking if ADB is installed..." +if ! command -v adb &> /dev/null; then + echo "ADB is not installed or not in PATH. Please install Android SDK platform tools and ensure ADB is in your PATH." + exit 1 +fi + +echo "Checking if an Android emulator is running..." +if ! adb devices | grep -q "emulator"; then + echo "No Android emulator is currently running. Please start an emulator before running this script." + exit 1 +fi + +echo "Checking if Maestro is installed..." +if ! command -v maestro &> /dev/null; then + echo "Maestro is not installed. Please install Maestro before running this script." + exit 1 +fi + +echo "Building the UI Test Critical app..." +make assembleUiTestCriticalRelease + +echo "Installing the UI Test Critical app on the emulator..." +baseDir="sentry-android-integration-tests/sentry-uitest-android-critical" +buildDir="build/outputs/apk/release" +apkName="sentry-uitest-android-critical-release.apk" +appPath="${baseDir}/${buildDir}/${apkName}" +adb install -r -d "$appPath" + +echo "Running the Maestro tests..." +maestro test \ + "${baseDir}/maestro" \ + --debug-output "${baseDir}/maestro-logs" diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/.gitignore b/sentry-android-integration-tests/sentry-uitest-android-critical/.gitignore new file mode 100644 index 0000000000..48fc28dcf5 --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/.gitignore @@ -0,0 +1,2 @@ +/build +/maestro-logs diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts new file mode 100644 index 0000000000..cebf744a24 --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts @@ -0,0 +1,69 @@ +import io.gitlab.arturbosch.detekt.Detekt + +plugins { + id("com.android.application") + kotlin("android") +} + +android { + compileSdk = Config.Android.compileSdkVersion + namespace = "io.sentry.uitest.android.critical" + + signingConfigs { + getByName("debug") { + // Debug config remains unchanged + } + } + + defaultConfig { + applicationId = "io.sentry.uitest.android.critical" + minSdk = Config.Android.minSdkVersionCompose + targetSdk = Config.Android.targetSdkVersion + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + signingConfig = signingConfigs.getByName("debug") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = Config.androidComposeCompilerVersion + } + variantFilter { + if (Config.Android.shouldSkipDebugVariant(buildType.name)) { + ignore = true + } + } +} + +dependencies { + implementation(kotlin(Config.kotlinStdLib, org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)) + implementation(Config.Libs.androidxCore) + implementation(Config.Libs.composeActivity) + implementation(Config.Libs.composeFoundation) + implementation(Config.Libs.composeMaterial) + implementation(Config.Libs.constraintLayout) + implementation(projects.sentryAndroidCore) +} + +tasks.withType { + // Target version of the generated JVM bytecode. It is used for type resolution. + jvmTarget = JavaVersion.VERSION_1_8.toString() +} + +kotlin { + explicitApi() +} diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/maestro/corruptEnvelope.yaml b/sentry-android-integration-tests/sentry-uitest-android-critical/maestro/corruptEnvelope.yaml new file mode 100644 index 0000000000..dec889731b --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/maestro/corruptEnvelope.yaml @@ -0,0 +1,11 @@ +appId: io.sentry.uitest.android.critical +--- +- launchApp +- tapOn: "Write Corrupted Envelope" +# The close here ensures the next corrupted envelope +# will be present on the next app launch +- tapOn: "Close SDK" +- tapOn: "Write Corrupted Envelope" +- stopApp +- launchApp +- assertVisible: "Welcome!" diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/maestro/crash.yaml b/sentry-android-integration-tests/sentry-uitest-android-critical/maestro/crash.yaml new file mode 100644 index 0000000000..f9543f365c --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/maestro/crash.yaml @@ -0,0 +1,6 @@ +appId: io.sentry.uitest.android.critical +--- +- launchApp +- tapOn: "Crash" +- launchApp +- assertVisible: "Welcome!" diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/proguard-rules.pro b/sentry-android-integration-tests/sentry-uitest-android-critical/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/src/main/AndroidManifest.xml b/sentry-android-integration-tests/sentry-uitest-android-critical/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..0ab5e6052d --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/src/main/java/io/sentry/uitest/android/critical/MainActivity.kt b/sentry-android-integration-tests/sentry-uitest-android-critical/src/main/java/io/sentry/uitest/android/critical/MainActivity.kt new file mode 100644 index 0000000000..8802f3dca2 --- /dev/null +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/src/main/java/io/sentry/uitest/android/critical/MainActivity.kt @@ -0,0 +1,51 @@ +package io.sentry.uitest.android.critical + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import io.sentry.Sentry +import java.io.File + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val outboxPath = Sentry.getCurrentHub().options.outboxPath + ?: throw RuntimeException("Outbox path is not set.") + + setContent { + MaterialTheme { + Surface() { + Column() { + Text(text = "Welcome!") + Button(onClick = { + throw RuntimeException("Crash the test app.") + }) { + Text("Crash") + } + Button(onClick = { + Sentry.close() + }) { + Text("Close SDK") + } + Button(onClick = { + val file = File(outboxPath, "corrupted.envelope") + val corruptedEnvelopeContent = """ + {"event_id":"1990b5bc31904b7395fd07feb72daf1c","sdk":{"name":"sentry.java.android","version":"7.21.0"}} + {"type":"test","length":50} + """.trimIndent() + file.writeText(corruptedEnvelopeContent) + println("Wrote corrupted envelope to: ${file.absolutePath}") + }) { + Text("Write Corrupted Envelope") + } + } + } + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 760c6e6905..77b3be021d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,6 +61,7 @@ include( "sentry-samples:sentry-samples-spring-boot-webflux", "sentry-samples:sentry-samples-spring-boot-webflux-jakarta", "sentry-samples:sentry-samples-netflix-dgs", + "sentry-android-integration-tests:sentry-uitest-android-critical", "sentry-android-integration-tests:sentry-uitest-android-benchmark", "sentry-android-integration-tests:sentry-uitest-android", "sentry-android-integration-tests:test-app-plain", From 28b6ac91bec6b033b065a308e323020150a2a234 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:07:12 +0200 Subject: [PATCH 24/40] ci(build): Lower gradle workers to max 2 (#3814) --- gradle.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle.properties b/gradle.properties index 73dc376308..b333f4b906 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,9 @@ org.gradle.jvmargs=-Xmx12g -XX:MaxMetaspaceSize=4g -XX:+CrashOnOutOfMemoryError org.gradle.caching=true org.gradle.parallel=true +# Daemons workers +org.gradle.workers.max=2 + # AndroidX required by AGP >= 3.6.x android.useAndroidX=true From 285450bbbbbd2f1769481620d5a78abd919bd9a9 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Wed, 23 Oct 2024 14:35:17 +0200 Subject: [PATCH 25/40] Prepare Changelog for 7.16.0 release (#3816) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb6d75604..b8d4fa9f85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,21 @@ ## Unreleased +### Features + +- Add meta option to attach ANR thread dumps ([#3791](https://github.com/getsentry/sentry-java/pull/3791)) + ### Fixes +- Cache parsed Dsn ([#3796](https://github.com/getsentry/sentry-java/pull/3796)) +- fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) +- Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) +- Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) +- Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) +- Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) +- Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) +- Fix slow Integration name parsing ([#3794](https://github.com/getsentry/sentry-java/pull/3794)) +- Session Replay: Reduce startup and capture overhead ([#3799](https://github.com/getsentry/sentry-java/pull/3799)) - Load lazy fields on init in the background ([#3803](https://github.com/getsentry/sentry-java/pull/3803)) - Replace setOf with HashSet.add ([#3801](https://github.com/getsentry/sentry-java/pull/3801)) From 95f6443bdcdb65a2855452a8e6f9fa3bd524d5f7 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 23 Oct 2024 12:36:13 +0000 Subject: [PATCH 26/40] release: 7.16.0 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8d4fa9f85..c6d8bdfc83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 7.16.0 ### Features diff --git a/gradle.properties b/gradle.properties index b333f4b906..4268e10785 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ android.useAndroidX=true android.defaults.buildfeatures.buildconfig=true # Release information -versionName=7.16.0-alpha.1 +versionName=7.16.0 # Override the SDK name on native crashes on Android sentryAndroidSdkName=sentry.native.android From 378d12c8be1519ed7caea8bb7db8bcb0ab8275a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:37:13 +0000 Subject: [PATCH 27/40] Bump github/codeql-action from 3.26.12 to 3.26.13 (#3808) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.12 to 3.26.13. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c36620d31ac7c881962c3d9dd939c40ec9434f2b...f779452ac5af1c261dce0346a8f964149f49322b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7ae4f25c65..599b815657 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: gradle-home-cache-cleanup: true - name: Initialize CodeQL - uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # pin@v2 + uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # pin@v2 with: languages: ${{ matrix.language }} @@ -55,4 +55,4 @@ jobs: ./gradlew buildForCodeQL - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # pin@v2 + uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # pin@v2 From 771d13f99c77186b890e379bc9b27aae2a5bdd94 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 28 Oct 2024 09:31:51 +0100 Subject: [PATCH 28/40] Bump androidx test libraries (#3743) --- buildSrc/src/main/java/Config.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 8e4b6832fb..2715dd5767 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -177,18 +177,17 @@ object Config { } object TestLibs { - private val androidxTestVersion = "1.5.0" private val espressoVersion = "3.5.0" val androidJUnitRunner = "androidx.test.runner.AndroidJUnitRunner" val kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion" - val androidxCore = "androidx.test:core:$androidxTestVersion" - val androidxRunner = "androidx.test:runner:$androidxTestVersion" - val androidxTestCoreKtx = "androidx.test:core-ktx:$androidxTestVersion" - val androidxTestRules = "androidx.test:rules:$androidxTestVersion" + val androidxCore = "androidx.test:core:1.6.1" + val androidxRunner = "androidx.test:runner:1.6.2" + val androidxTestCoreKtx = "androidx.test:core-ktx:1.6.1" + val androidxTestRules = "androidx.test:rules:1.6.1" val espressoCore = "androidx.test.espresso:espresso-core:$espressoVersion" val espressoIdlingResource = "androidx.test.espresso:espresso-idling-resource:$espressoVersion" - val androidxTestOrchestrator = "androidx.test:orchestrator:1.4.2" + val androidxTestOrchestrator = "androidx.test:orchestrator:1.5.0" val androidxJunit = "androidx.test.ext:junit:1.1.5" val androidxCoreKtx = "androidx.core:core-ktx:1.7.0" val robolectric = "org.robolectric:robolectric:4.10.3" From 92ab1d93a6b3de7c557e8aeb51c1506c216a51b6 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Mon, 28 Oct 2024 11:36:20 +0100 Subject: [PATCH 29/40] Add callout about `addIntegrationToSdkVersion` breaking change (#3829) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d8bdfc83..676cf4da5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ - Load lazy fields on init in the background ([#3803](https://github.com/getsentry/sentry-java/pull/3803)) - Replace setOf with HashSet.add ([#3801](https://github.com/getsentry/sentry-java/pull/3801)) +### Breaking changes + +- The method `addIntegrationToSdkVersion(Ljava/lang/Class;)V` has been removed from the core (`io.sentry:sentry`) package. Please make sure all of the packages (e.g. `io.sentry:sentry-android-core`, `io.sentry:sentry-android-fragment`, `io.sentry:sentry-okhttp` and others) are all aligned and using the same version to prevent the `NoSuchMethodError` exception. + ## 7.16.0-alpha.1 ### Features From 283c6ccfe35090f5b96ff635bf08eae105305120 Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 28 Oct 2024 12:59:32 +0100 Subject: [PATCH 30/40] Accept manifest integer values when requiring floating values (#3823) * ManifestMetadataReader now accepts integers other than floats --- CHANGELOG.md | 6 +++++ .../android/core/ManifestMetadataReader.java | 2 +- .../core/ManifestMetadataReaderTest.kt | 25 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 4 +-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 676cf4da5c..813e9e65bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823)) + ## 7.16.0 ### Features 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 babcfdfc98..adfd4f22ad 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 @@ -492,7 +492,7 @@ private static boolean readBool( private static @NotNull Double readDouble( final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { // manifest meta-data only reads float - final Double value = ((Float) metadata.getFloat(key, -1)).doubleValue(); + final Double value = ((Number) metadata.getFloat(key, metadata.getInt(key, -1))).doubleValue(); logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } 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 25b2e0191c..17f0f3950b 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 @@ -1515,4 +1515,29 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME)) assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME)) } + + @Test + fun `applyMetadata reads integers even when expecting floats`() { + // Arrange + val expectedSampleRate: Int = 1 + + val bundle = bundleOf( + ManifestMetadataReader.SAMPLE_RATE to expectedSampleRate, + ManifestMetadataReader.TRACES_SAMPLE_RATE to expectedSampleRate, + ManifestMetadataReader.PROFILES_SAMPLE_RATE to expectedSampleRate, + ManifestMetadataReader.REPLAYS_SESSION_SAMPLE_RATE to expectedSampleRate, + ManifestMetadataReader.REPLAYS_ERROR_SAMPLE_RATE to expectedSampleRate + ) + val context = fixture.getContext(metaData = bundle) + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertEquals(expectedSampleRate.toDouble(), fixture.options.sampleRate) + assertEquals(expectedSampleRate.toDouble(), fixture.options.tracesSampleRate) + assertEquals(expectedSampleRate.toDouble(), fixture.options.profilesSampleRate) + assertEquals(expectedSampleRate.toDouble(), fixture.options.experimental.sessionReplay.sessionSampleRate) + assertEquals(expectedSampleRate.toDouble(), fixture.options.experimental.sessionReplay.onErrorSampleRate) + } } diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index 058ad3710c..2327573a43 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -114,7 +114,7 @@ - + @@ -165,7 +165,7 @@ - + From 58da2a3ebb66b60b1c67584060ffb2f35182f9f2 Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 28 Oct 2024 13:36:42 +0100 Subject: [PATCH 31/40] update benchmark devices on saucelabs (#3822) --- .sauce/sentry-uitest-android-benchmark.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.sauce/sentry-uitest-android-benchmark.yml b/.sauce/sentry-uitest-android-benchmark.yml index 6b8120cfa7..48737b5fa5 100644 --- a/.sauce/sentry-uitest-android-benchmark.yml +++ b/.sauce/sentry-uitest-android-benchmark.yml @@ -33,7 +33,7 @@ suites: useTestOrchestrator: true devices: - id: Samsung_Galaxy_S10_Plus_11_real_us # Samsung Galaxy S10+ - api 30 (11) - high end - - id: Samsung_Galaxy_A71_5G_real_us # Samsung Galaxy A71 5G - api 30 (11) - mid end + - id: Google_Pixel_4a_real_us # Google Pixel 4a - api 30 (11) - mid end - id: Google_Pixel_3a_real # Google Pixel 3a - api 30 (11) - low end - name: "Android 10 (api 29)" @@ -42,7 +42,7 @@ suites: useTestOrchestrator: true devices: - id: Google_Pixel_3a_XL_real # Google Pixel 3a XL - api 29 (10) - - id: Motorola_Moto_G_Power_real_us # Motorola Moto G Power - api 29 (10) + - id: OnePlus_6T_real # OnePlus 6T - api 29 (10) # At the time of writing (July, 4, 2022), the market share per android version is: # 12.0 = 17.54%, 11.0 = 31.65%, 10.0 = 21.92% From 4c5d0ff63726f2c31a0ebf46081e9bc8ced004ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:45:31 +0000 Subject: [PATCH 32/40] Bump reactivecircus/android-emulator-runner from 2.32.0 to 2.33.0 (#3826) Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.32.0 to 2.33.0. - [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases) - [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md) - [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.32.0...62dbb605bba737720e10b196cb4220d374026a6d) --- updated-dependencies: - dependency-name: reactivecircus/android-emulator-runner dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integration-tests-ui-critical.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests-ui-critical.yml b/.github/workflows/integration-tests-ui-critical.yml index 6729e40ca5..d30b3f7045 100644 --- a/.github/workflows/integration-tests-ui-critical.yml +++ b/.github/workflows/integration-tests-ui-critical.yml @@ -76,7 +76,7 @@ jobs: version: ${{env.MAESTRO_VERSION}} - name: Run tests - uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915 # pin@v2.32.0 + uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2.33.0 with: api-level: 30 force-avd-creation: false From c362c98b2d0a0b1d25765922c415835d1dbfbc3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:50:37 +0000 Subject: [PATCH 33/40] Bump github/codeql-action from 3.26.13 to 3.27.0 (#3827) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.13 to 3.27.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f779452ac5af1c261dce0346a8f964149f49322b...662472033e021d55d94146f66f6058822b0b39fd) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 599b815657..68855cbd8d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: gradle-home-cache-cleanup: true - name: Initialize CodeQL - uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # pin@v2 + uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # pin@v2 with: languages: ${{ matrix.language }} @@ -55,4 +55,4 @@ jobs: ./gradlew buildForCodeQL - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # pin@v2 + uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # pin@v2 From 5183da95e2282e6b581c6fc6599673fc11eba696 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:02:25 +0100 Subject: [PATCH 34/40] test(critical): Add API Level matrix (#3810) --- .../integration-tests-ui-critical.yml | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration-tests-ui-critical.yml b/.github/workflows/integration-tests-ui-critical.yml index d30b3f7045..815adf9b61 100644 --- a/.github/workflows/integration-tests-ui-critical.yml +++ b/.github/workflows/integration-tests-ui-critical.yml @@ -19,7 +19,7 @@ env: jobs: build: - name: Build sentry-uitest-android-critical + name: Build runs-on: ubuntu-latest steps: - name: Checkout code @@ -47,9 +47,30 @@ jobs: retention-days: 1 run-maestro-tests: - name: Run Maestro Tests + name: Run Tests for API Level ${{ matrix.api-level }} needs: build runs-on: ubuntu-latest + strategy: + # we want that the matrix keeps running, default is to cancel them if it fails. + fail-fast: false + matrix: + include: + - api-level: 30 # Android 11 + target: aosp_atd + channel: canary # Necessary for ATDs + arch: x86_64 + - api-level: 31 # Android 12 + target: aosp_atd + channel: canary # Necessary for ATDs + arch: x86_64 + - api-level: 33 # Android 13 + target: aosp_atd + channel: canary # Necessary for ATDs + arch: x86_64 + - api-level: 34 # Android 14 + target: aosp_atd + channel: canary # Necessary for ATDs + arch: x86_64 steps: - name: Checkout code uses: actions/checkout@v4 @@ -78,12 +99,13 @@ jobs: - name: Run tests uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2.33.0 with: - api-level: 30 + api-level: ${{ matrix.api-level }} force-avd-creation: false disable-animations: true disable-spellchecker: true - target: 'aosp_atd' - channel: canary # Necessary for ATDs + target: ${{ matrix.target }} + channel: ${{ matrix.channel }} + arch: ${{ matrix.arch }} emulator-options: > -no-window -no-snapshot-save From 28a11a7925b8e45bec38684f2002192787f13477 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 31 Oct 2024 06:48:34 +0100 Subject: [PATCH 35/40] Use `Random` through `ThreadLocal` (#3835) * Use Random as a ThreadLocal<> * changelog * code review changes --- CHANGELOG.md | 4 ++ .../android/core/AnrV2EventProcessor.java | 16 +------ sentry/api/sentry.api | 5 +++ .../src/main/java/io/sentry/SentryClient.java | 5 +-- .../main/java/io/sentry/TracesSampler.java | 16 +++++-- .../java/io/sentry/util/SentryRandom.java | 37 +++++++++++++++ .../java/io/sentry/util/SentryRandomTest.kt | 45 +++++++++++++++++++ 7 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/SentryRandom.java create mode 100644 sentry/src/test/java/io/sentry/util/SentryRandomTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 813e9e65bc..cd68c3158e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Use a separate `Random` instance per thread to improve SDK performance ([#3835](https://github.com/getsentry/sentry-java/pull/3835)) + ### Fixes - Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823)) 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 0399b634fb..e914029c30 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 @@ -54,7 +54,7 @@ import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.User; import io.sentry.util.HintUtils; -import io.sentry.util.Random; +import io.sentry.util.SentryRandom; import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -83,24 +83,13 @@ public final class AnrV2EventProcessor implements BackfillingEventProcessor { private final @NotNull SentryExceptionFactory sentryExceptionFactory; - private final @Nullable Random random; - public AnrV2EventProcessor( final @NotNull Context context, final @NotNull SentryAndroidOptions options, final @NotNull BuildInfoProvider buildInfoProvider) { - this(context, options, buildInfoProvider, null); - } - - AnrV2EventProcessor( - final @NotNull Context context, - final @NotNull SentryAndroidOptions options, - final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable Random random) { this.context = ContextUtils.getApplicationContext(context); this.options = options; this.buildInfoProvider = buildInfoProvider; - this.random = random; final SentryStackTraceFactory sentryStackTraceFactory = new SentryStackTraceFactory(this.options); @@ -180,9 +169,8 @@ private boolean sampleReplay(final @NotNull SentryEvent event) { try { // we have to sample here with the old sample rate, because it may change between app launches - final @NotNull Random random = this.random != null ? this.random : new Random(); final double replayErrorSampleRateDouble = Double.parseDouble(replayErrorSampleRate); - if (replayErrorSampleRateDouble < random.nextDouble()) { + if (replayErrorSampleRateDouble < SentryRandom.current().nextDouble()) { options .getLogger() .log( diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ed06b29b35..8e266bcdba 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5829,6 +5829,11 @@ public final class io/sentry/util/SampleRateUtils { public static fun isValidTracesSampleRate (Ljava/lang/Double;Z)Z } +public final class io/sentry/util/SentryRandom { + public fun ()V + public static fun current ()Lio/sentry/util/Random; +} + public final class io/sentry/util/StringUtils { public static fun byteCountToString (J)Ljava/lang/String; public static fun calculateStringHash (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 27529d100b..b053230ce5 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -18,6 +18,7 @@ import io.sentry.util.HintUtils; import io.sentry.util.Objects; import io.sentry.util.Random; +import io.sentry.util.SentryRandom; import io.sentry.util.TracingUtils; import java.io.Closeable; import java.io.IOException; @@ -40,7 +41,6 @@ public final class SentryClient implements ISentryClient, IMetricsClient { private final @NotNull SentryOptions options; private final @NotNull ITransport transport; - private final @Nullable Random random; private final @NotNull SortBreadcrumbsByDate sortBreadcrumbsByDate = new SortBreadcrumbsByDate(); private final @NotNull IMetricsAggregator metricsAggregator; @@ -66,8 +66,6 @@ public boolean isEnabled() { options.isEnableMetrics() ? new MetricsAggregator(options, this) : NoopMetricsAggregator.getInstance(); - - this.random = options.getSampleRate() == null ? null : new Random(); } private boolean shouldApplyScopeData( @@ -1183,6 +1181,7 @@ public boolean isHealthy() { } private boolean sample() { + final @Nullable Random random = options.getSampleRate() == null ? null : SentryRandom.current(); // https://docs.sentry.io/development/sdk-dev/features/#event-sampling if (options.getSampleRate() != null && random != null) { final double sampling = options.getSampleRate(); diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 5e5b808333..ef04cae369 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -2,6 +2,7 @@ import io.sentry.util.Objects; import io.sentry.util.Random; +import io.sentry.util.SentryRandom; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -10,14 +11,14 @@ final class TracesSampler { private static final @NotNull Double DEFAULT_TRACES_SAMPLE_RATE = 1.0; private final @NotNull SentryOptions options; - private final @NotNull Random random; + private final @Nullable Random random; public TracesSampler(final @NotNull SentryOptions options) { - this(Objects.requireNonNull(options, "options are required"), new Random()); + this(Objects.requireNonNull(options, "options are required"), null); } @TestOnly - TracesSampler(final @NotNull SentryOptions options, final @NotNull Random random) { + TracesSampler(final @NotNull SentryOptions options, final @Nullable Random random) { this.options = options; this.random = random; } @@ -90,6 +91,13 @@ TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { } private boolean sample(final @NotNull Double aDouble) { - return !(aDouble < random.nextDouble()); + return !(aDouble < getRandom().nextDouble()); + } + + private Random getRandom() { + if (random == null) { + return SentryRandom.current(); + } + return random; } } diff --git a/sentry/src/main/java/io/sentry/util/SentryRandom.java b/sentry/src/main/java/io/sentry/util/SentryRandom.java new file mode 100644 index 0000000000..f6e1d0a974 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/SentryRandom.java @@ -0,0 +1,37 @@ +package io.sentry.util; + +import org.jetbrains.annotations.NotNull; + +/** + * This SentryRandom is a compromise used for improving performance of the SDK. + * + *

We did some testing where using Random from multiple threads degrades performance + * significantly. We opted for this approach as it wasn't easily possible to vendor + * ThreadLocalRandom since it's using advanced features that can cause java.lang.IllegalAccessError. + */ +public final class SentryRandom { + + private static final @NotNull SentryRandomThreadLocal instance = new SentryRandomThreadLocal(); + + /** + * Returns the current threads instance of {@link Random}. An instance of {@link Random} will be + * created the first time this is invoked on each thread. + * + *

NOTE: Avoid holding a reference to the returned {@link Random} instance as sharing a + * reference across threads (while being thread-safe) will likely degrade performance + * significantly. + * + * @return random + */ + public static @NotNull Random current() { + return instance.get(); + } + + private static class SentryRandomThreadLocal extends ThreadLocal { + + @Override + protected Random initialValue() { + return new Random(); + } + } +} diff --git a/sentry/src/test/java/io/sentry/util/SentryRandomTest.kt b/sentry/src/test/java/io/sentry/util/SentryRandomTest.kt new file mode 100644 index 0000000000..c812c6cbdc --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/SentryRandomTest.kt @@ -0,0 +1,45 @@ +package io.sentry.util + +import kotlin.test.Test +import kotlin.test.assertNotSame +import kotlin.test.assertSame + +class SentryRandomTest { + + @Test + fun `thread local creates a new instance per thread but keeps re-using it for the same thread`() { + val mainThreadRandom1 = SentryRandom.current() + val mainThreadRandom2 = SentryRandom.current() + assertSame(mainThreadRandom1, mainThreadRandom2) + + var thread1Random1: Random? = null + var thread1Random2: Random? = null + + val thread1 = Thread() { + thread1Random1 = SentryRandom.current() + thread1Random2 = SentryRandom.current() + } + + var thread2Random1: Random? = null + var thread2Random2: Random? = null + + val thread2 = Thread() { + thread2Random1 = SentryRandom.current() + thread2Random2 = SentryRandom.current() + } + + thread1.start() + thread2.start() + thread1.join() + thread2.join() + + assertSame(thread1Random1, thread1Random2) + assertNotSame(mainThreadRandom1, thread1Random1) + + assertSame(thread2Random1, thread2Random2) + assertNotSame(mainThreadRandom1, thread2Random1) + + val mainThreadRandom3 = SentryRandom.current() + assertSame(mainThreadRandom1, mainThreadRandom3) + } +} From 2af8d1ae1af717314bb479e07d163f6461205387 Mon Sep 17 00:00:00 2001 From: LucasZF Date: Mon, 4 Nov 2024 14:54:06 +0000 Subject: [PATCH 36/40] Fix: Allow MaxBreadcrumb 0 / Expose MaxBreadcrumb metadata. (#3836) * expose max-breadcrumbs on meta data and implement disabled queue when maxbreadcrumbs sets to 0 * missing queue class and test * update changelog --------- Co-authored-by: Lucas Co-authored-by: Stefano --- CHANGELOG.md | 2 + .../android/core/ManifestMetadataReader.java | 5 + .../core/ManifestMetadataReaderTest.kt | 25 ++++ .../src/main/AndroidManifest.xml | 3 + .../main/java/io/sentry/DisabledQueue.java | 115 ++++++++++++++++++ sentry/src/main/java/io/sentry/Scope.java | 4 +- .../test/java/io/sentry/DisabledQueueTest.kt | 93 ++++++++++++++ sentry/src/test/java/io/sentry/ScopeTest.kt | 11 ++ 8 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 sentry/src/main/java/io/sentry/DisabledQueue.java create mode 100644 sentry/src/test/java/io/sentry/DisabledQueueTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index cd68c3158e..da49a05efd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ ### Features +- Add meta option to set the maximum amount of breadcrumbs to be logged. ([#3836](https://github.com/getsentry/sentry-java/pull/3836)) - Use a separate `Random` instance per thread to improve SDK performance ([#3835](https://github.com/getsentry/sentry-java/pull/3835)) ### Fixes +- Using MaxBreadcrumb with value 0 no longer crashes. ([#3836](https://github.com/getsentry/sentry-java/pull/3836)) - Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823)) ## 7.16.0 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 adfd4f22ad..eb60c5d9c4 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 @@ -104,6 +104,8 @@ final class ManifestMetadataReader { static final String ENABLE_METRICS = "io.sentry.enable-metrics"; + static final String MAX_BREADCRUMBS = "io.sentry.max-breadcrumbs"; + static final String REPLAYS_SESSION_SAMPLE_RATE = "io.sentry.session-replay.session-sample-rate"; static final String REPLAYS_ERROR_SAMPLE_RATE = "io.sentry.session-replay.on-error-sample-rate"; @@ -213,6 +215,9 @@ static void applyMetadata( SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS, options.getSessionTrackingIntervalMillis())); + options.setMaxBreadcrumbs( + (int) readLong(metadata, logger, MAX_BREADCRUMBS, options.getMaxBreadcrumbs())); + options.setEnableActivityLifecycleBreadcrumbs( readBool( metadata, 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 17f0f3950b..ee4b4ae39a 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 @@ -1516,6 +1516,31 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME)) } + @Test + fun `applyMetadata reads maxBreadcrumbs to options and sets the value if found`() { + // Arrange + val bundle = bundleOf(ManifestMetadataReader.MAX_BREADCRUMBS to 1) + val context = fixture.getContext(metaData = bundle) + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertEquals(1, fixture.options.maxBreadcrumbs) + } + + @Test + fun `applyMetadata reads maxBreadcrumbs to options and keeps default if not found`() { + // Arrange + val context = fixture.getContext() + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertEquals(100, fixture.options.maxBreadcrumbs) + } + @Test fun `applyMetadata reads integers even when expecting floats`() { // Arrange diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index 2327573a43..d8ae6c709d 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -152,6 +152,9 @@ + + + diff --git a/sentry/src/main/java/io/sentry/DisabledQueue.java b/sentry/src/main/java/io/sentry/DisabledQueue.java new file mode 100644 index 0000000000..afef111af4 --- /dev/null +++ b/sentry/src/main/java/io/sentry/DisabledQueue.java @@ -0,0 +1,115 @@ +package io.sentry; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class DisabledQueue extends AbstractCollection implements Queue, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID = -8423413834657610417L; + + /** Constructor that creates a queue that does not accept any element. */ + public DisabledQueue() {} + + // ----------------------------------------------------------------------- + /** + * Returns the number of elements stored in the queue. + * + * @return this queue's size + */ + @Override + public int size() { + return 0; + } + + /** + * Returns true if this queue is empty; false otherwise. + * + * @return false + */ + @Override + public boolean isEmpty() { + return false; + } + + /** Does nothing. */ + @Override + public void clear() {} + + /** + * Since the queue is disabled, the element will not be added. + * + * @param element the element to add + * @return false, always + */ + @Override + public boolean add(final @NotNull E element) { + return false; + } + + // ----------------------------------------------------------------------- + + /** + * Receives an element but do nothing with it. + * + * @param element the element to add + * @return false, always + */ + @Override + public boolean offer(@NotNull E element) { + return false; + } + + @Override + public @Nullable E poll() { + return null; + } + + @Override + public @Nullable E element() { + return null; + } + + @Override + public @Nullable E peek() { + return null; + } + + @Override + public @NotNull E remove() { + throw new NoSuchElementException("queue is disabled"); + } + + // ----------------------------------------------------------------------- + + /** + * Returns an iterator over this queue's elements. + * + * @return an iterator over this queue's elements + */ + @Override + public @NotNull Iterator iterator() { + return new Iterator() { + + @Override + public boolean hasNext() { + return false; + } + + @Override + public E next() { + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new IllegalStateException(); + } + }; + } +} diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index f071cb8c5e..c74fabce25 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -754,7 +754,9 @@ public void clearAttachments() { * @return the breadcrumbs queue */ private @NotNull Queue createBreadcrumbsList(final int maxBreadcrumb) { - return SynchronizedQueue.synchronizedQueue(new CircularFifoQueue<>(maxBreadcrumb)); + return maxBreadcrumb > 0 + ? SynchronizedQueue.synchronizedQueue(new CircularFifoQueue<>(maxBreadcrumb)) + : SynchronizedQueue.synchronizedQueue(new DisabledQueue<>()); } /** diff --git a/sentry/src/test/java/io/sentry/DisabledQueueTest.kt b/sentry/src/test/java/io/sentry/DisabledQueueTest.kt new file mode 100644 index 0000000000..351f87eaff --- /dev/null +++ b/sentry/src/test/java/io/sentry/DisabledQueueTest.kt @@ -0,0 +1,93 @@ +package io.sentry +import org.junit.Assert.assertThrows +import java.util.NoSuchElementException +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull + +class DisabledQueueTest { + + @Test + fun `size starts empty`() { + val queue = DisabledQueue() + assertEquals(0, queue.size, "Size should always be zero.") + } + + @Test + fun `add does not add elements`() { + val queue = DisabledQueue() + assertFalse(queue.add(1), "add should always return false.") + assertEquals(0, queue.size, "Size should still be zero after attempting to add an element.") + } + + @Test + fun `isEmpty returns false when created`() { + val queue = DisabledQueue() + assertFalse(queue.isEmpty(), "isEmpty should always return false.") + } + + @Test + fun `isEmpty always returns false if add function was called`() { + val queue = DisabledQueue() + queue.add(1) + + assertFalse(queue.isEmpty(), "isEmpty should always return false.") + } + + @Test + fun `offer does not add elements`() { + val queue = DisabledQueue() + assertFalse(queue.offer(1), "offer should always return false.") + assertEquals(0, queue.size, "Size should still be zero after attempting to offer an element.") + } + + @Test + fun `poll returns null`() { + val queue = DisabledQueue() + queue.add(1) + assertNull(queue.poll(), "poll should always return null.") + } + + @Test + fun `peek returns null`() { + val queue = DisabledQueue() + queue.add(1) + + assertNull(queue.peek(), "peek should always return null.") + } + + @Test + fun `element returns null`() { + val queue = DisabledQueue() + assertNull(queue.element(), "element should always return null.") + } + + @Test + fun `remove throws NoSuchElementException`() { + val queue = DisabledQueue() + assertThrows(NoSuchElementException::class.java) { queue.remove() } + } + + @Test + fun `clear does nothing`() { + val queue = DisabledQueue() + queue.clear() // Should not throw an exception + assertEquals(0, queue.size, "Size should remain zero after clear.") + } + + @Test + fun `iterator has no elements`() { + val queue = DisabledQueue() + val iterator = queue.iterator() + assertFalse(iterator.hasNext(), "Iterator should have no elements.") + assertThrows(NoSuchElementException::class.java) { iterator.next() } + } + + @Test + fun `iterator remove throws IllegalStateException`() { + val queue = DisabledQueue() + val iterator = queue.iterator() + assertThrows(IllegalStateException::class.java) { iterator.remove() } + } +} diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 07b9176de7..a0f54d5205 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -1029,6 +1029,17 @@ class ScopeTest { ) } + @Test + fun `creating a new scope won't crash if max breadcrumbs is set to zero`() { + val options = SentryOptions().apply { + maxBreadcrumbs = 0 + } + val scope = Scope(options) + + // expect no exception to be thrown + // previously was crashing, see https://github.com/getsentry/sentry-java/issues/3313 + } + private fun eventProcessor(): EventProcessor { return object : EventProcessor { override fun process(event: SentryEvent, hint: Hint): SentryEvent? { From fd1151b5c7696d17711c40c2facee553a43d07c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:14:32 +0000 Subject: [PATCH 37/40] Bump gradle/actions (#3842) Bumps [gradle/actions](https://github.com/gradle/actions) from bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b to 707359876a764dbcdb9da0b0ed08291818310c3d. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b...707359876a764dbcdb9da0b0ed08291818310c3d) --- updated-dependencies: - dependency-name: gradle/actions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/agp-matrix.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/enforce-license-compliance.yml | 2 +- .github/workflows/generate-javadocs.yml | 2 +- .github/workflows/integration-tests-benchmarks.yml | 4 ++-- .github/workflows/integration-tests-ui-critical.yml | 2 +- .github/workflows/integration-tests-ui.yml | 2 +- .github/workflows/release-build.yml | 2 +- .github/workflows/system-tests-backend.yml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index 182c0fa19b..56a4f74fae 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -38,7 +38,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b885942e9..5988e27888 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 68855cbd8d..9ce3d9f9bd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index 2b87c1d278..c4a22a909b 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/generate-javadocs.yml b/.github/workflows/generate-javadocs.yml index b540441fb7..5d358bdf50 100644 --- a/.github/workflows/generate-javadocs.yml +++ b/.github/workflows/generate-javadocs.yml @@ -20,7 +20,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-benchmarks.yml b/.github/workflows/integration-tests-benchmarks.yml index 58fc933752..34eea1733c 100644 --- a/.github/workflows/integration-tests-benchmarks.yml +++ b/.github/workflows/integration-tests-benchmarks.yml @@ -37,7 +37,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true @@ -86,7 +86,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-ui-critical.yml b/.github/workflows/integration-tests-ui-critical.yml index 815adf9b61..a28400e50f 100644 --- a/.github/workflows/integration-tests-ui-critical.yml +++ b/.github/workflows/integration-tests-ui-critical.yml @@ -32,7 +32,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/integration-tests-ui.yml b/.github/workflows/integration-tests-ui.yml index 6c4ad6564f..059e3c7424 100644 --- a/.github/workflows/integration-tests-ui.yml +++ b/.github/workflows/integration-tests-ui.yml @@ -32,7 +32,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 83a3c82f8c..c625efa8eb 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -26,7 +26,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index 2222f910ad..3703e26168 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -40,7 +40,7 @@ jobs: java-version: '17' - name: Setup Gradle - uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + uses: gradle/actions/setup-gradle@707359876a764dbcdb9da0b0ed08291818310c3d # pin@v3 with: gradle-home-cache-cleanup: true From fba10b88355deba03a45d039d8d3103d5455e2a7 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 12 Nov 2024 09:23:04 +0100 Subject: [PATCH 38/40] Limit emulator size to 4096M (#3875) CI runs recently started to fail due to disk size issues when creating the emulator: > ERROR | Not enough space to create userdata partition. Available: 7329.925781 MB at /home/runner/.android/avd/../avd/test.avd, need 7372.800000 MB. #skip-changelog --- .github/workflows/agp-matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index 56a4f74fae..b93bc364ac 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -69,6 +69,7 @@ jobs: target: 'aosp_atd' arch: x86 channel: canary # Necessary for ATDs + disk-size: 4096M script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -DtestBuildType=release --daemon - name: Upload test results From 566da7624407dd4df6ea3a128431cc223ecc2035 Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 12 Nov 2024 14:12:53 +0100 Subject: [PATCH 39/40] Fix standalone tomcat jndi issue (#3873) * fix standalone tomcat jndi issue * format * add changelog entry * adapt changelog --- CHANGELOG.md | 3 +++ sentry/src/main/java/io/sentry/Baggage.java | 6 +++--- sentry/src/main/java/io/sentry/DsnUtil.java | 2 +- .../src/main/java/io/sentry/RequestDetailsResolver.java | 2 +- sentry/src/main/java/io/sentry/Sentry.java | 2 +- sentry/src/main/java/io/sentry/SentryOptions.java | 8 +++++--- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da49a05efd..34fa731c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ - Using MaxBreadcrumb with value 0 no longer crashes. ([#3836](https://github.com/getsentry/sentry-java/pull/3836)) - Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823)) +- Fix standalone tomcat jndi issue ([#3873](https://github.com/getsentry/sentry-java/pull/3873)) + - Using Sentry Spring Boot on a standalone tomcat caused the following error: + - Failed to bind properties under 'sentry.parsed-dsn' to io.sentry.Dsn ## 7.16.0 diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 0a80b154bf..befdead4a5 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -134,7 +134,7 @@ public static Baggage fromEvent( final Baggage baggage = new Baggage(options.getLogger()); final SpanContext trace = event.getContexts().getTrace(); baggage.setTraceId(trace != null ? trace.getTraceId().toString() : null); - baggage.setPublicKey(options.getParsedDsn().getPublicKey()); + baggage.setPublicKey(options.retrieveParsedDsn().getPublicKey()); baggage.setRelease(event.getRelease()); baggage.setEnvironment(event.getEnvironment()); final User user = event.getUser(); @@ -405,7 +405,7 @@ public void setValuesFromTransaction( final @NotNull SentryOptions sentryOptions, final @Nullable TracesSamplingDecision samplingDecision) { setTraceId(transaction.getSpanContext().getTraceId().toString()); - setPublicKey(sentryOptions.getParsedDsn().getPublicKey()); + setPublicKey(sentryOptions.retrieveParsedDsn().getPublicKey()); setRelease(sentryOptions.getRelease()); setEnvironment(sentryOptions.getEnvironment()); setUserSegment(user != null ? getSegment(user) : null); @@ -427,7 +427,7 @@ public void setValuesFromScope( final @Nullable User user = scope.getUser(); final @NotNull SentryId replayId = scope.getReplayId(); setTraceId(propagationContext.getTraceId().toString()); - setPublicKey(options.getParsedDsn().getPublicKey()); + setPublicKey(options.retrieveParsedDsn().getPublicKey()); setRelease(options.getRelease()); setEnvironment(options.getEnvironment()); if (!SentryId.EMPTY_ID.equals(replayId)) { diff --git a/sentry/src/main/java/io/sentry/DsnUtil.java b/sentry/src/main/java/io/sentry/DsnUtil.java index 6cc0dc360b..b6902ad274 100644 --- a/sentry/src/main/java/io/sentry/DsnUtil.java +++ b/sentry/src/main/java/io/sentry/DsnUtil.java @@ -23,7 +23,7 @@ public static boolean urlContainsDsnHost(@Nullable SentryOptions options, @Nulla return false; } - final @NotNull Dsn dsn = options.getParsedDsn(); + final @NotNull Dsn dsn = options.retrieveParsedDsn(); final @NotNull URI sentryUri = dsn.getSentryUri(); final @Nullable String dsnHost = sentryUri.getHost(); diff --git a/sentry/src/main/java/io/sentry/RequestDetailsResolver.java b/sentry/src/main/java/io/sentry/RequestDetailsResolver.java index 6083c69e99..bba4dc19ac 100644 --- a/sentry/src/main/java/io/sentry/RequestDetailsResolver.java +++ b/sentry/src/main/java/io/sentry/RequestDetailsResolver.java @@ -21,7 +21,7 @@ public RequestDetailsResolver(final @NotNull SentryOptions options) { @NotNull RequestDetails resolve() { - final Dsn dsn = options.getParsedDsn(); + final Dsn dsn = options.retrieveParsedDsn(); final URI sentryUri = dsn.getSentryUri(); final String envelopeUrl = sentryUri.resolve(sentryUri.getPath() + "/envelope/").toString(); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 6e4a2530a7..f075876325 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -398,7 +398,7 @@ private static boolean initConfigurations(final @NotNull SentryOptions options) } // This creates the DSN object and performs some checks - options.getParsedDsn(); + options.retrieveParsedDsn(); ILogger logger = options.getLogger(); diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 3c286f2bff..8614abc287 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -541,13 +541,15 @@ public void addIntegration(@NotNull Integration integration) { } /** - * Evaluates and parses the DSN. May throw an exception if the DSN is invalid. + * Evaluates and parses the DSN. May throw an exception if the DSN is invalid. Renamed from + * `getParsedDsn` as this would cause an error when deploying as WAR to Tomcat due to `JNDI` + * property binding. * * @return the parsed DSN or throws if dsn is invalid */ @ApiStatus.Internal @NotNull - Dsn getParsedDsn() throws IllegalArgumentException { + Dsn retrieveParsedDsn() throws IllegalArgumentException { return parsedDsn.getValue(); } @@ -2457,7 +2459,7 @@ public void setEnableScreenTracking(final boolean enableScreenTracking) { */ void loadLazyFields() { getSerializer(); - getParsedDsn(); + retrieveParsedDsn(); getEnvelopeReader(); getDateProvider(); } From 4bd1aa38243dd26abd3c26af70868c2fe5e94048 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 12 Nov 2024 14:05:40 +0000 Subject: [PATCH 40/40] release: 7.17.0 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34fa731c2b..d7a41e65dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 7.17.0 ### Features diff --git a/gradle.properties b/gradle.properties index 4268e10785..f9cfa874ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ android.useAndroidX=true android.defaults.buildfeatures.buildconfig=true # Release information -versionName=7.16.0 +versionName=7.17.0 # Override the SDK name on native crashes on Android sentryAndroidSdkName=sentry.native.android