Skip to content

Commit 2d676d1

Browse files
authored
Fix ensure all options are processed before integrations are loaded (#2377)
1 parent ed00ecb commit 2d676d1

File tree

9 files changed

+135
-103
lines changed

9 files changed

+135
-103
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Remove profiler main thread io ([#2348](https://github.com/getsentry/sentry-java/pull/2348))
8+
- Fix ensure all options are processed before integrations are loaded ([#2377](https://github.com/getsentry/sentry-java/pull/2377))
89

910
### Features
1011

sentry-android-core/api/sentry-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ public final class io/sentry/android/core/UserInteractionIntegration : android/a
224224

225225
public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache {
226226
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
227+
public fun getDirectory ()Ljava/io/File;
227228
public static fun hasStartupCrashMarker (Lio/sentry/SentryOptions;)Z
228229
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
229230
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 37 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
1717
import io.sentry.android.fragment.FragmentLifecycleIntegration;
1818
import io.sentry.android.timber.SentryTimberIntegration;
19+
import io.sentry.transport.NoOpEnvelopeCache;
1920
import io.sentry.util.Objects;
2021
import java.io.BufferedInputStream;
2122
import java.io.File;
@@ -25,6 +26,7 @@
2526
import java.util.Properties;
2627
import org.jetbrains.annotations.NotNull;
2728
import org.jetbrains.annotations.Nullable;
29+
import org.jetbrains.annotations.TestOnly;
2830

2931
/**
3032
* Android Options initializer, it reads configurations from AndroidManifest and sets to the
@@ -42,62 +44,11 @@ private AndroidOptionsInitializer() {}
4244
* @param options the SentryAndroidOptions
4345
* @param context the Application context
4446
*/
45-
static void init(final @NotNull SentryAndroidOptions options, final @NotNull Context context) {
46-
Objects.requireNonNull(context, "The application context is required.");
47-
Objects.requireNonNull(options, "The options object is required.");
48-
49-
init(options, context, new AndroidLogger(), false, false);
50-
}
51-
52-
/**
53-
* Init method of the Android Options initializer
54-
*
55-
* @param options the SentryAndroidOptions
56-
* @param context the Application context
57-
* @param logger the ILogger interface
58-
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
59-
* @param isTimberAvailable whether the Timber integration is available on the classpath
60-
*/
61-
static void init(
62-
final @NotNull SentryAndroidOptions options,
63-
@NotNull Context context,
64-
final @NotNull ILogger logger,
65-
final boolean isFragmentAvailable,
66-
final boolean isTimberAvailable) {
67-
init(
68-
options,
69-
context,
70-
logger,
71-
new BuildInfoProvider(logger),
72-
isFragmentAvailable,
73-
isTimberAvailable);
74-
}
75-
76-
/**
77-
* Init method of the Android Options initializer
78-
*
79-
* @param options the SentryAndroidOptions
80-
* @param context the Application context
81-
* @param logger the ILogger interface
82-
* @param buildInfoProvider the BuildInfoProvider interface
83-
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
84-
* @param isTimberAvailable whether the Timber integration is available on the classpath
85-
*/
86-
static void init(
87-
final @NotNull SentryAndroidOptions options,
88-
@NotNull Context context,
89-
final @NotNull ILogger logger,
90-
final @NotNull BuildInfoProvider buildInfoProvider,
91-
final boolean isFragmentAvailable,
92-
final boolean isTimberAvailable) {
93-
init(
94-
options,
95-
context,
96-
logger,
97-
buildInfoProvider,
98-
new LoadClass(),
99-
isFragmentAvailable,
100-
isTimberAvailable);
47+
@TestOnly
48+
static void loadDefaultAndMetadataOptions(
49+
final @NotNull SentryAndroidOptions options, final @NotNull Context context) {
50+
final ILogger logger = new AndroidLogger();
51+
loadDefaultAndMetadataOptions(options, context, logger, new BuildInfoProvider(logger));
10152
}
10253

10354
/**
@@ -107,18 +58,12 @@ static void init(
10758
* @param context the Application context
10859
* @param logger the ILogger interface
10960
* @param buildInfoProvider the BuildInfoProvider interface
110-
* @param loadClass the LoadClass wrapper
111-
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
112-
* @param isTimberAvailable whether the Timber integration is available on the classpath
11361
*/
114-
static void init(
62+
static void loadDefaultAndMetadataOptions(
11563
final @NotNull SentryAndroidOptions options,
11664
@NotNull Context context,
11765
final @NotNull ILogger logger,
118-
final @NotNull BuildInfoProvider buildInfoProvider,
119-
final @NotNull LoadClass loadClass,
120-
final boolean isFragmentAvailable,
121-
final boolean isTimberAvailable) {
66+
final @NotNull BuildInfoProvider buildInfoProvider) {
12267
Objects.requireNonNull(context, "The context is required.");
12368

12469
// it returns null if ContextImpl, so let's check for nullability
@@ -134,7 +79,34 @@ static void init(
13479

13580
ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
13681
initializeCacheDirs(context, options);
137-
options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));
82+
83+
readDefaultOptionValues(options, context, buildInfoProvider);
84+
}
85+
86+
@TestOnly
87+
static void initializeIntegrationsAndProcessors(
88+
final @NotNull SentryAndroidOptions options, final @NotNull Context context) {
89+
initializeIntegrationsAndProcessors(
90+
options,
91+
context,
92+
new BuildInfoProvider(new AndroidLogger()),
93+
new LoadClass(),
94+
false,
95+
false);
96+
}
97+
98+
static void initializeIntegrationsAndProcessors(
99+
final @NotNull SentryAndroidOptions options,
100+
final @NotNull Context context,
101+
final @NotNull BuildInfoProvider buildInfoProvider,
102+
final @NotNull LoadClass loadClass,
103+
final boolean isFragmentAvailable,
104+
final boolean isTimberAvailable) {
105+
106+
if (options.getCacheDirPath() != null
107+
&& options.getEnvelopeDiskCache() instanceof NoOpEnvelopeCache) {
108+
options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));
109+
}
138110

139111
final ActivityFramesTracker activityFramesTracker =
140112
new ActivityFramesTracker(loadClass, options);
@@ -148,8 +120,6 @@ static void init(
148120
isFragmentAvailable,
149121
isTimberAvailable);
150122

151-
readDefaultOptionValues(options, context, buildInfoProvider);
152-
153123
options.addEventProcessor(
154124
new DefaultAndroidEventProcessor(context, buildInfoProvider, options));
155125
options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));

sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
import io.sentry.Sentry;
1010
import io.sentry.SentryLevel;
1111
import io.sentry.SentryOptions;
12-
import io.sentry.android.core.cache.AndroidEnvelopeCache;
1312
import io.sentry.android.fragment.FragmentLifecycleIntegration;
1413
import io.sentry.android.timber.SentryTimberIntegration;
15-
import io.sentry.transport.NoOpEnvelopeCache;
1614
import java.lang.reflect.InvocationTargetException;
1715
import java.util.ArrayList;
1816
import java.util.Date;
@@ -102,11 +100,23 @@ public static synchronized void init(
102100
(isTimberUpstreamAvailable
103101
&& classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));
104102

105-
AndroidOptionsInitializer.init(
106-
options, context, logger, isFragmentAvailable, isTimberAvailable);
103+
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
104+
final LoadClass loadClass = new LoadClass();
105+
106+
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
107+
options, context, logger, buildInfoProvider);
108+
107109
configuration.configure(options);
110+
111+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
112+
options,
113+
context,
114+
buildInfoProvider,
115+
loadClass,
116+
isFragmentAvailable,
117+
isTimberAvailable);
118+
108119
deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
109-
resetEnvelopeCacheIfNeeded(options);
110120
},
111121
true);
112122
} catch (IllegalAccessException e) {
@@ -132,7 +142,8 @@ public static synchronized void init(
132142

133143
/**
134144
* Deduplicate potentially duplicated Fragment and Timber integrations, which can be added
135-
* automatically by our SDK as well as by the user. The user's ones win over ours.
145+
* automatically by our SDK as well as by the user. The user's ones (provided first in the
146+
* options.integrations list) win over ours.
136147
*
137148
* @param options SentryOptions to retrieve integrations from
138149
*/
@@ -158,31 +169,17 @@ private static void deduplicateIntegrations(
158169
}
159170

160171
if (fragmentIntegrations.size() > 1) {
161-
for (int i = 0; i < fragmentIntegrations.size() - 1; i++) {
172+
for (int i = 1; i < fragmentIntegrations.size(); i++) {
162173
final Integration integration = fragmentIntegrations.get(i);
163174
options.getIntegrations().remove(integration);
164175
}
165176
}
166177

167178
if (timberIntegrations.size() > 1) {
168-
for (int i = 0; i < timberIntegrations.size() - 1; i++) {
179+
for (int i = 1; i < timberIntegrations.size(); i++) {
169180
final Integration integration = timberIntegrations.get(i);
170181
options.getIntegrations().remove(integration);
171182
}
172183
}
173184
}
174-
175-
/**
176-
* Resets envelope cache if {@link SentryOptions#getCacheDirPath()} was set to null by the user
177-
* and the IEnvelopCache implementation remained ours (AndroidEnvelopeCache), which relies on
178-
* cacheDirPath set.
179-
*
180-
* @param options SentryOptions to retrieve cacheDirPath from
181-
*/
182-
private static void resetEnvelopeCacheIfNeeded(final @NotNull SentryAndroidOptions options) {
183-
if (options.getCacheDirPath() == null
184-
&& options.getEnvelopeDiskCache() instanceof AndroidEnvelopeCache) {
185-
options.setEnvelopeDiskCache(NoOpEnvelopeCache.getInstance());
186-
}
187-
}
188185
}

sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.io.File;
1818
import org.jetbrains.annotations.ApiStatus;
1919
import org.jetbrains.annotations.NotNull;
20+
import org.jetbrains.annotations.TestOnly;
2021

2122
@ApiStatus.Internal
2223
public final class AndroidEnvelopeCache extends EnvelopeCache {
@@ -58,6 +59,11 @@ public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) {
5859
}
5960
}
6061

62+
@TestOnly
63+
public @NotNull File getDirectory() {
64+
return directory;
65+
}
66+
6167
private void writeStartupCrashMarkerFile() {
6268
// we use outbox path always, as it's the one that will also contain markers if hybrid sdks
6369
// decide to write it, which will trigger the blocking init

sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ class AndroidOptionsInitializerTest {
5252
whenever(mockContext.applicationContext.cacheDir).thenReturn(file)
5353
}
5454
mockContext.configureContext()
55+
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
56+
sentryOptions,
57+
if (useRealContext) context else mockContext
58+
)
5559
sentryOptions.configureOptions()
56-
AndroidOptionsInitializer.init(
60+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
5761
sentryOptions,
5862
if (useRealContext) context else mockContext
5963
)
@@ -71,12 +75,19 @@ class AndroidOptionsInitializerTest {
7175
putString(ManifestMetadataReader.DSN, "https://[email protected]/123")
7276
}
7377
)
74-
sentryOptions.setDebug(true)
75-
AndroidOptionsInitializer.init(
78+
sentryOptions.isDebug = true
79+
val buildInfo = createBuildInfo(minApi)
80+
81+
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
7682
sentryOptions,
77-
mockContext,
83+
context,
7884
logger,
79-
createBuildInfo(minApi),
85+
buildInfo
86+
)
87+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
88+
sentryOptions,
89+
context,
90+
buildInfo,
8091
createClassMock(classToLoad),
8192
isFragmentAvailable,
8293
isTimberAvailable
@@ -381,9 +392,13 @@ class AndroidOptionsInitializerTest {
381392

382393
@Test
383394
fun `When Activity Frames Tracking is enabled, the Activity Frames Tracker should be available`() {
384-
fixture.initSut(hasAppContext = true, configureOptions = {
385-
isEnableFramesTracking = true
386-
})
395+
fixture.initSut(
396+
hasAppContext = true,
397+
useRealContext = true,
398+
configureOptions = {
399+
isEnableFramesTracking = true
400+
}
401+
)
387402

388403
val activityLifeCycleIntegration = fixture.sentryOptions.integrations
389404
.first { it is ActivityLifecycleIntegration }
@@ -395,7 +410,7 @@ class AndroidOptionsInitializerTest {
395410

396411
@Test
397412
fun `When Frames Tracking is disabled, the Activity Frames Tracker should not be available`() {
398-
fixture.initSut(hasAppContext = true, configureOptions = {
413+
fixture.initSut(hasAppContext = true, useRealContext = true, configureOptions = {
399414
isEnableFramesTracking = false
400415
})
401416

@@ -410,9 +425,13 @@ class AndroidOptionsInitializerTest {
410425
@Test
411426
fun `When Frames Tracking is initially disabled, but enabled via configureOptions it should be available`() {
412427
fixture.sentryOptions.isEnableFramesTracking = false
413-
fixture.initSut(hasAppContext = true, configureOptions = {
414-
isEnableFramesTracking = true
415-
})
428+
fixture.initSut(
429+
hasAppContext = true,
430+
useRealContext = true,
431+
configureOptions = {
432+
isEnableFramesTracking = true
433+
}
434+
)
416435

417436
val activityLifeCycleIntegration = fixture.sentryOptions.integrations
418437
.first { it is ActivityLifecycleIntegration }

sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,21 @@ class AndroidTransactionProfilerTest {
8888
@BeforeTest
8989
fun `set up`() {
9090
context = ApplicationProvider.getApplicationContext()
91-
AndroidOptionsInitializer.init(fixture.options, context, fixture.mockLogger, false, false)
91+
val buildInfoProvider = BuildInfoProvider(fixture.mockLogger)
92+
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
93+
fixture.options,
94+
context,
95+
fixture.mockLogger,
96+
buildInfoProvider
97+
)
98+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
99+
fixture.options,
100+
context,
101+
buildInfoProvider,
102+
LoadClass(),
103+
false,
104+
false
105+
)
92106
// Profiler doesn't start if the folder doesn't exists.
93107
// Usually it's generated when calling Sentry.init, but for tests we can create it manually.
94108
File(fixture.options.profilingTracesDirPath!!).mkdirs()

0 commit comments

Comments
 (0)