Skip to content

Commit 2186444

Browse files
authored
Allow removing integrations in SentryAndroid.init (#2826)
* configure options now happens after adding integrations in SentryAndroid.init * added LazyEvaluator to evaluate a function lazily * AndroidOptionsInitializer.installDefaultIntegrations now evaluate cache dir lazily
1 parent bb6705c commit 2186444

File tree

12 files changed

+245
-42
lines changed

12 files changed

+245
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Fixes
66

7+
- Allow removing integrations in SentryAndroid.init ([#2826](https://github.com/getsentry/sentry-java/pull/2826))
78
- Fix concurrent access to frameMetrics listener ([#2823](https://github.com/getsentry/sentry-java/pull/2823))
89

910
### Dependencies

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

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.sentry.internal.gestures.GestureTargetLocator;
2727
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter;
2828
import io.sentry.transport.NoOpEnvelopeCache;
29+
import io.sentry.util.LazyEvaluator;
2930
import io.sentry.util.Objects;
3031
import java.io.File;
3132
import java.util.ArrayList;
@@ -100,41 +101,30 @@ static void loadDefaultAndMetadataOptions(
100101

101102
@TestOnly
102103
static void initializeIntegrationsAndProcessors(
103-
final @NotNull SentryAndroidOptions options, final @NotNull Context context) {
104+
final @NotNull SentryAndroidOptions options,
105+
final @NotNull Context context,
106+
final @NotNull LoadClass loadClass,
107+
final @NotNull ActivityFramesTracker activityFramesTracker) {
104108
initializeIntegrationsAndProcessors(
105109
options,
106110
context,
107111
new BuildInfoProvider(new AndroidLogger()),
108-
new LoadClass(),
109-
false,
110-
false);
112+
loadClass,
113+
activityFramesTracker);
111114
}
112115

113116
static void initializeIntegrationsAndProcessors(
114117
final @NotNull SentryAndroidOptions options,
115118
final @NotNull Context context,
116119
final @NotNull BuildInfoProvider buildInfoProvider,
117120
final @NotNull LoadClass loadClass,
118-
final boolean isFragmentAvailable,
119-
final boolean isTimberAvailable) {
121+
final @NotNull ActivityFramesTracker activityFramesTracker) {
120122

121123
if (options.getCacheDirPath() != null
122124
&& options.getEnvelopeDiskCache() instanceof NoOpEnvelopeCache) {
123125
options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));
124126
}
125127

126-
final ActivityFramesTracker activityFramesTracker =
127-
new ActivityFramesTracker(loadClass, options);
128-
129-
installDefaultIntegrations(
130-
context,
131-
options,
132-
buildInfoProvider,
133-
loadClass,
134-
activityFramesTracker,
135-
isFragmentAvailable,
136-
isTimberAvailable);
137-
138128
options.addEventProcessor(
139129
new DefaultAndroidEventProcessor(context, buildInfoProvider, options));
140130
options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));
@@ -192,7 +182,7 @@ static void initializeIntegrationsAndProcessors(
192182
}
193183
}
194184

195-
private static void installDefaultIntegrations(
185+
static void installDefaultIntegrations(
196186
final @NotNull Context context,
197187
final @NotNull SentryAndroidOptions options,
198188
final @NotNull BuildInfoProvider buildInfoProvider,
@@ -201,14 +191,18 @@ private static void installDefaultIntegrations(
201191
final boolean isFragmentAvailable,
202192
final boolean isTimberAvailable) {
203193

194+
// Integration MUST NOT cache option values in ctor, as they will be configured later by the
195+
// user
196+
204197
// read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope
205198
// integrations below
206-
final boolean hasStartupCrashMarker = AndroidEnvelopeCache.hasStartupCrashMarker(options);
199+
LazyEvaluator<Boolean> startupCrashMarkerEvaluator =
200+
new LazyEvaluator<>(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options));
207201

208202
options.addIntegration(
209203
new SendCachedEnvelopeIntegration(
210204
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()),
211-
hasStartupCrashMarker));
205+
startupCrashMarkerEvaluator));
212206

213207
// Integrations are registered in the same order. NDK before adding Watch outbox,
214208
// because sentry-native move files around and we don't want to watch that.
@@ -228,7 +222,7 @@ private static void installDefaultIntegrations(
228222
options.addIntegration(
229223
new SendCachedEnvelopeIntegration(
230224
new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),
231-
hasStartupCrashMarker));
225+
startupCrashMarkerEvaluator));
232226

233227
// AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration
234228
// relies on AppState set by it

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration;
66
import io.sentry.SentryLevel;
77
import io.sentry.SentryOptions;
8+
import io.sentry.util.LazyEvaluator;
89
import io.sentry.util.Objects;
910
import java.util.concurrent.Future;
1011
import java.util.concurrent.RejectedExecutionException;
@@ -16,13 +17,13 @@ final class SendCachedEnvelopeIntegration implements Integration {
1617

1718
private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
1819
factory;
19-
private final boolean hasStartupCrashMarker;
20+
private final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator;
2021

2122
public SendCachedEnvelopeIntegration(
2223
final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory,
23-
final boolean hasStartupCrashMarker) {
24+
final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator) {
2425
this.factory = Objects.requireNonNull(factory, "SendFireAndForgetFactory is required");
25-
this.hasStartupCrashMarker = hasStartupCrashMarker;
26+
this.startupCrashMarkerEvaluator = startupCrashMarkerEvaluator;
2627
}
2728

2829
@Override
@@ -62,7 +63,7 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
6263
}
6364
});
6465

65-
if (hasStartupCrashMarker) {
66+
if (startupCrashMarkerEvaluator.getValue()) {
6667
androidOptions
6768
.getLogger()
6869
.log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush.");

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,29 @@ public static synchronized void init(
104104

105105
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
106106
final LoadClass loadClass = new LoadClass();
107+
final ActivityFramesTracker activityFramesTracker =
108+
new ActivityFramesTracker(loadClass, options);
107109

108110
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
109111
options, context, logger, buildInfoProvider);
110112

111-
configuration.configure(options);
112-
113-
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
114-
options,
113+
// We install the default integrations before the option configuration, so that the user
114+
// can remove any of them. Integrations will not evaluate the options immediately, but
115+
// will use them later, after being configured.
116+
AndroidOptionsInitializer.installDefaultIntegrations(
115117
context,
118+
options,
116119
buildInfoProvider,
117120
loadClass,
121+
activityFramesTracker,
118122
isFragmentAvailable,
119123
isTimberAvailable);
120124

125+
configuration.configure(options);
126+
127+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
128+
options, context, buildInfoProvider, loadClass, activityFramesTracker);
129+
121130
deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
122131
},
123132
true);
@@ -151,7 +160,7 @@ public static synchronized void init(
151160

152161
/**
153162
* Deduplicate potentially duplicated Fragment and Timber integrations, which can be added
154-
* automatically by our SDK as well as by the user. The user's ones (provided first in the
163+
* automatically by our SDK as well as by the user. The user's ones (provided last in the
155164
* options.integrations list) win over ours.
156165
*
157166
* @param options SentryOptions to retrieve integrations from
@@ -178,14 +187,14 @@ private static void deduplicateIntegrations(
178187
}
179188

180189
if (fragmentIntegrations.size() > 1) {
181-
for (int i = 1; i < fragmentIntegrations.size(); i++) {
190+
for (int i = 0; i < fragmentIntegrations.size() - 1; i++) {
182191
final Integration integration = fragmentIntegrations.get(i);
183192
options.getIntegrations().remove(integration);
184193
}
185194
}
186195

187196
if (timberIntegrations.size() > 1) {
188-
for (int i = 1; i < timberIntegrations.size(); i++) {
197+
for (int i = 0; i < timberIntegrations.size() - 1; i++) {
189198
final Integration integration = timberIntegrations.get(i);
190199
options.getIntegrations().remove(integration);
191200
}

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import org.junit.runner.RunWith
2222
import org.mockito.kotlin.any
2323
import org.mockito.kotlin.eq
2424
import org.mockito.kotlin.mock
25+
import org.mockito.kotlin.never
26+
import org.mockito.kotlin.spy
27+
import org.mockito.kotlin.verify
2528
import org.mockito.kotlin.whenever
2629
import org.robolectric.annotation.Config
2730
import java.io.File
@@ -68,10 +71,26 @@ class AndroidOptionsInitializerTest {
6871
sentryOptions,
6972
if (useRealContext) context else mockContext
7073
)
74+
75+
val loadClass = LoadClass()
76+
val activityFramesTracker = ActivityFramesTracker(loadClass, sentryOptions)
77+
78+
AndroidOptionsInitializer.installDefaultIntegrations(
79+
if (useRealContext) context else mockContext,
80+
sentryOptions,
81+
BuildInfoProvider(AndroidLogger()),
82+
loadClass,
83+
activityFramesTracker,
84+
false,
85+
false
86+
)
87+
7188
sentryOptions.configureOptions()
7289
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
7390
sentryOptions,
74-
if (useRealContext) context else mockContext
91+
if (useRealContext) context else mockContext,
92+
loadClass,
93+
activityFramesTracker
7594
)
7695
}
7796

@@ -89,21 +108,33 @@ class AndroidOptionsInitializerTest {
89108
)
90109
sentryOptions.isDebug = true
91110
val buildInfo = createBuildInfo(minApi)
111+
val loadClass = createClassMock(classesToLoad)
112+
val activityFramesTracker = ActivityFramesTracker(loadClass, sentryOptions)
92113

93114
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
94115
sentryOptions,
95116
context,
96117
logger,
97118
buildInfo
98119
)
99-
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
100-
sentryOptions,
120+
121+
AndroidOptionsInitializer.installDefaultIntegrations(
101122
context,
123+
sentryOptions,
102124
buildInfo,
103-
createClassMock(classesToLoad),
125+
loadClass,
126+
activityFramesTracker,
104127
isFragmentAvailable,
105128
isTimberAvailable
106129
)
130+
131+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
132+
sentryOptions,
133+
context,
134+
buildInfo,
135+
loadClass,
136+
activityFramesTracker
137+
)
107138
}
108139

109140
private fun createBuildInfo(minApi: Int = 16): BuildInfoProvider {
@@ -571,6 +602,22 @@ class AndroidOptionsInitializerTest {
571602
assertFalse(fixture.sentryOptions.scopeObservers.any { it is PersistingScopeObserver })
572603
}
573604

605+
@Test
606+
fun `installDefaultIntegrations does not evaluate cacheDir or outboxPath when called`() {
607+
val mockOptions = spy(fixture.sentryOptions)
608+
AndroidOptionsInitializer.installDefaultIntegrations(
609+
fixture.context,
610+
mockOptions,
611+
mock(),
612+
mock(),
613+
mock(),
614+
false,
615+
false
616+
)
617+
verify(mockOptions, never()).outboxPath
618+
verify(mockOptions, never()).cacheDirPath
619+
}
620+
574621
@Config(sdk = [30])
575622
@Test
576623
fun `AnrV2Integration added to integrations list for API 30 and above`() {

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,32 @@ class AndroidTransactionProfilerTest {
109109
fun `set up`() {
110110
context = ApplicationProvider.getApplicationContext()
111111
val buildInfoProvider = BuildInfoProvider(fixture.mockLogger)
112+
val loadClass = LoadClass()
113+
val activityFramesTracker = ActivityFramesTracker(loadClass, fixture.options)
112114
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
113115
fixture.options,
114116
context,
115117
fixture.mockLogger,
116118
buildInfoProvider
117119
)
118-
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
119-
fixture.options,
120+
121+
AndroidOptionsInitializer.installDefaultIntegrations(
120122
context,
123+
fixture.options,
121124
buildInfoProvider,
122-
LoadClass(),
125+
loadClass,
126+
activityFramesTracker,
123127
false,
124128
false
125129
)
130+
131+
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
132+
fixture.options,
133+
context,
134+
buildInfoProvider,
135+
loadClass,
136+
activityFramesTracker
137+
)
126138
// Profiler doesn't start if the folder doesn't exists.
127139
// Usually it's generated when calling Sentry.init, but for tests we can create it manually.
128140
File(fixture.options.profilingTracesDirPath!!).mkdirs()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.sentry.ILogger
55
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget
66
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
77
import io.sentry.SentryLevel.DEBUG
8+
import io.sentry.util.LazyEvaluator
89
import org.awaitility.kotlin.await
910
import org.mockito.kotlin.any
1011
import org.mockito.kotlin.eq
@@ -53,7 +54,7 @@ class SendCachedEnvelopeIntegrationTest {
5354
}
5455
)
5556

56-
return SendCachedEnvelopeIntegration(factory, hasStartupCrashMarker)
57+
return SendCachedEnvelopeIntegration(factory, LazyEvaluator { hasStartupCrashMarker })
5758
}
5859
}
5960

0 commit comments

Comments
 (0)