Skip to content

Commit 4bfe31b

Browse files
romtsnbruno-garcia
andauthored
Add Timber and Fragment integrations if they are present on the classpath (#1936)
Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>
1 parent db05017 commit 4bfe31b

File tree

16 files changed

+426
-334
lines changed

16 files changed

+426
-334
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
* Feat: Automatically enable `Timber` and `Fragment` integrations if they are present on the classpath (#1936)
6+
57
## 5.6.3
68

79
* Fix: If transaction or span is finished, do not allow to mutate (#1940)

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
public final class io/sentry/android/core/ActivityFramesTracker {
22
public fun <init> (Lio/sentry/android/core/LoadClass;)V
3+
public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/ILogger;)V
34
public fun addActivity (Landroid/app/Activity;)V
45
public fun setMetrics (Landroid/app/Activity;Lio/sentry/protocol/SentryId;)V
56
public fun stop ()V
@@ -82,7 +83,9 @@ public abstract interface class io/sentry/android/core/IDebugImagesLoader {
8283

8384
public final class io/sentry/android/core/LoadClass {
8485
public fun <init> ()V
85-
public fun loadClass (Ljava/lang/String;)Ljava/lang/Class;
86+
public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z
87+
public fun isClassAvailable (Ljava/lang/String;Lio/sentry/SentryOptions;)Z
88+
public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class;
8689
}
8790

8891
public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration, java/io/Closeable {

sentry-android-core/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ android {
4949

5050
// We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks.
5151
checkReleaseBuilds = false
52+
disable += "LogNotTimber"
5253
}
5354

5455
// needed because of Kotlin 1.4.x
@@ -78,6 +79,8 @@ tasks.withType<JavaCompile>().configureEach {
7879

7980
dependencies {
8081
api(projects.sentry)
82+
compileOnly(projects.sentryAndroidFragment)
83+
compileOnly(projects.sentryAndroidTimber)
8184

8285
// lifecycle processor, session tracking
8386
implementation(Config.Libs.lifecycleProcess)
@@ -102,4 +105,6 @@ dependencies {
102105
testImplementation(Config.TestLibs.mockitoInline)
103106
testImplementation(Config.TestLibs.awaitility)
104107
testImplementation(projects.sentryTestSupport)
108+
testImplementation(projects.sentryAndroidFragment)
109+
testImplementation(projects.sentryAndroidTimber)
105110
}

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.app.Activity;
44
import android.util.SparseIntArray;
55
import androidx.core.app.FrameMetricsAggregator;
6+
import io.sentry.ILogger;
67
import io.sentry.protocol.MeasurementValue;
78
import io.sentry.protocol.SentryId;
89
import java.util.HashMap;
@@ -25,28 +26,23 @@ public final class ActivityFramesTracker {
2526
private final @NotNull Map<SentryId, Map<String, @NotNull MeasurementValue>>
2627
activityMeasurements = new ConcurrentHashMap<>();
2728

28-
public ActivityFramesTracker(final @NotNull LoadClass loadClass) {
29-
androidXAvailable = checkAndroidXAvailability(loadClass);
29+
public ActivityFramesTracker(final @NotNull LoadClass loadClass, final @Nullable ILogger logger) {
30+
androidXAvailable =
31+
loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", logger);
3032
if (androidXAvailable) {
3133
frameMetricsAggregator = new FrameMetricsAggregator();
3234
}
3335
}
3436

37+
public ActivityFramesTracker(final @NotNull LoadClass loadClass) {
38+
this(loadClass, null);
39+
}
40+
3541
@TestOnly
3642
ActivityFramesTracker(final @Nullable FrameMetricsAggregator frameMetricsAggregator) {
3743
this.frameMetricsAggregator = frameMetricsAggregator;
3844
}
3945

40-
private static boolean checkAndroidXAvailability(final @NotNull LoadClass loadClass) {
41-
try {
42-
loadClass.loadClass("androidx.core.app.FrameMetricsAggregator");
43-
return true;
44-
} catch (ClassNotFoundException ignored) {
45-
// androidx.core isn't available.
46-
return false;
47-
}
48-
}
49-
5046
private boolean isFrameMetricsAggregatorAvailable() {
5147
return androidXAvailable && frameMetricsAggregator != null;
5248
}

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

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.sentry.SendFireAndForgetOutboxSender;
1414
import io.sentry.SentryLevel;
1515
import io.sentry.SentryOptions;
16+
import io.sentry.android.fragment.FragmentLifecycleIntegration;
17+
import io.sentry.android.timber.SentryTimberIntegration;
1618
import io.sentry.util.Objects;
1719
import java.io.BufferedInputStream;
1820
import java.io.File;
@@ -43,7 +45,7 @@ static void init(final @NotNull SentryAndroidOptions options, final @NotNull Con
4345
Objects.requireNonNull(context, "The application context is required.");
4446
Objects.requireNonNull(options, "The options object is required.");
4547

46-
init(options, context, new AndroidLogger());
48+
init(options, context, new AndroidLogger(), false, false);
4749
}
4850

4951
/**
@@ -52,12 +54,16 @@ static void init(final @NotNull SentryAndroidOptions options, final @NotNull Con
5254
* @param options the SentryOptions
5355
* @param context the Application context
5456
* @param logger the ILogger interface
57+
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
58+
* @param isTimberAvailable whether the Timber integration is available on the classpath
5559
*/
5660
static void init(
5761
final @NotNull SentryAndroidOptions options,
5862
@NotNull Context context,
59-
final @NotNull ILogger logger) {
60-
init(options, context, logger, new BuildInfoProvider());
63+
final @NotNull ILogger logger,
64+
final boolean isFragmentAvailable,
65+
final boolean isTimberAvailable) {
66+
init(options, context, logger, new BuildInfoProvider(), isFragmentAvailable, isTimberAvailable);
6167
}
6268

6369
/**
@@ -67,13 +73,24 @@ static void init(
6773
* @param context the Application context
6874
* @param logger the ILogger interface
6975
* @param buildInfoProvider the IBuildInfoProvider interface
76+
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
77+
* @param isTimberAvailable whether the Timber integration is available on the classpath
7078
*/
7179
static void init(
7280
final @NotNull SentryAndroidOptions options,
7381
@NotNull Context context,
7482
final @NotNull ILogger logger,
75-
final @NotNull IBuildInfoProvider buildInfoProvider) {
76-
init(options, context, logger, buildInfoProvider, new LoadClass());
83+
final @NotNull IBuildInfoProvider buildInfoProvider,
84+
final boolean isFragmentAvailable,
85+
final boolean isTimberAvailable) {
86+
init(
87+
options,
88+
context,
89+
logger,
90+
buildInfoProvider,
91+
new LoadClass(),
92+
isFragmentAvailable,
93+
isTimberAvailable);
7794
}
7895

7996
/**
@@ -84,13 +101,17 @@ static void init(
84101
* @param logger the ILogger interface
85102
* @param buildInfoProvider the IBuildInfoProvider interface
86103
* @param loadClass the LoadClass wrapper
104+
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
105+
* @param isTimberAvailable whether the Timber integration is available on the classpath
87106
*/
88107
static void init(
89108
final @NotNull SentryAndroidOptions options,
90109
@NotNull Context context,
91110
final @NotNull ILogger logger,
92111
final @NotNull IBuildInfoProvider buildInfoProvider,
93-
final @NotNull LoadClass loadClass) {
112+
final @NotNull LoadClass loadClass,
113+
final boolean isFragmentAvailable,
114+
final boolean isTimberAvailable) {
94115
Objects.requireNonNull(context, "The context is required.");
95116

96117
// it returns null if ContextImpl, so let's check for nullability
@@ -107,9 +128,16 @@ static void init(
107128
ManifestMetadataReader.applyMetadata(context, options);
108129
initializeCacheDirs(context, options);
109130

110-
final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker(loadClass);
131+
final ActivityFramesTracker activityFramesTracker =
132+
new ActivityFramesTracker(loadClass, options.getLogger());
111133
installDefaultIntegrations(
112-
context, options, buildInfoProvider, loadClass, activityFramesTracker);
134+
context,
135+
options,
136+
buildInfoProvider,
137+
loadClass,
138+
activityFramesTracker,
139+
isFragmentAvailable,
140+
isTimberAvailable);
113141

114142
readDefaultOptionValues(options, context);
115143

@@ -124,15 +152,20 @@ private static void installDefaultIntegrations(
124152
final @NotNull SentryOptions options,
125153
final @NotNull IBuildInfoProvider buildInfoProvider,
126154
final @NotNull LoadClass loadClass,
127-
final @NotNull ActivityFramesTracker activityFramesTracker) {
155+
final @NotNull ActivityFramesTracker activityFramesTracker,
156+
final boolean isFragmentAvailable,
157+
final boolean isTimberAvailable) {
128158

129159
options.addIntegration(
130160
new SendCachedEnvelopeFireAndForgetIntegration(
131161
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath())));
132162

133163
// Integrations are registered in the same order. NDK before adding Watch outbox,
134164
// because sentry-native move files around and we don't want to watch that.
135-
final Class<?> sentryNdkClass = loadNdkIfAvailable(options, buildInfoProvider, loadClass);
165+
final Class<?> sentryNdkClass =
166+
isNdkAvailable(buildInfoProvider)
167+
? loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger())
168+
: null;
136169
options.addIntegration(new NdkIntegration(sentryNdkClass));
137170

138171
// this integration uses android.os.FileObserver, we can't move to sentry
@@ -155,12 +188,18 @@ private static void installDefaultIntegrations(
155188
new ActivityLifecycleIntegration(
156189
(Application) context, buildInfoProvider, activityFramesTracker));
157190
options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
191+
if (isFragmentAvailable) {
192+
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
193+
}
158194
} else {
159195
options
160196
.getLogger()
161197
.log(
162198
SentryLevel.WARNING,
163-
"ActivityLifecycle and UserInteraction Integrations need an Application class to be installed.");
199+
"ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed.");
200+
}
201+
if (isTimberAvailable) {
202+
options.addIntegration(new SentryTimberIntegration());
164203
}
165204
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
166205
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
@@ -257,24 +296,4 @@ private static void initializeCacheDirs(
257296
private static boolean isNdkAvailable(final @NotNull IBuildInfoProvider buildInfoProvider) {
258297
return buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN;
259298
}
260-
261-
private static @Nullable Class<?> loadNdkIfAvailable(
262-
final @NotNull SentryOptions options,
263-
final @NotNull IBuildInfoProvider buildInfoProvider,
264-
final @NotNull LoadClass loadClass) {
265-
if (isNdkAvailable(buildInfoProvider)) {
266-
try {
267-
return loadClass.loadClass(SENTRY_NDK_CLASS_NAME);
268-
} catch (ClassNotFoundException e) {
269-
options.getLogger().log(SentryLevel.ERROR, "Failed to load SentryNdk.", e);
270-
} catch (UnsatisfiedLinkError e) {
271-
options
272-
.getLogger()
273-
.log(SentryLevel.ERROR, "Failed to load (UnsatisfiedLinkError) SentryNdk.", e);
274-
} catch (Throwable e) {
275-
options.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e);
276-
}
277-
}
278-
return null;
279-
}
280299
}
Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package io.sentry.android.core;
22

3+
import io.sentry.ILogger;
4+
import io.sentry.SentryLevel;
5+
import io.sentry.SentryOptions;
36
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
48

59
/** An Adapter for making Class.forName testable */
610
public final class LoadClass {
@@ -9,10 +13,34 @@ public final class LoadClass {
913
* Try to load a class via reflection
1014
*
1115
* @param clazz the full class name
12-
* @return a Class<?>
13-
* @throws ClassNotFoundException if class is not found
16+
* @param logger an instance of ILogger
17+
* @return a Class<?> if it's available, or null
1418
*/
15-
public @NotNull Class<?> loadClass(@NotNull String clazz) throws ClassNotFoundException {
16-
return Class.forName(clazz);
19+
public @Nullable Class<?> loadClass(final @NotNull String clazz, final @Nullable ILogger logger) {
20+
try {
21+
return Class.forName(clazz);
22+
} catch (ClassNotFoundException e) {
23+
if (logger != null) {
24+
logger.log(SentryLevel.DEBUG, "Class not available:" + clazz, e);
25+
}
26+
} catch (UnsatisfiedLinkError e) {
27+
if (logger != null) {
28+
logger.log(SentryLevel.ERROR, "Failed to load (UnsatisfiedLinkError) " + clazz, e);
29+
}
30+
} catch (Throwable e) {
31+
if (logger != null) {
32+
logger.log(SentryLevel.ERROR, "Failed to initialize " + clazz, e);
33+
}
34+
}
35+
return null;
36+
}
37+
38+
public boolean isClassAvailable(final @NotNull String clazz, final @Nullable ILogger logger) {
39+
return loadClass(clazz, logger) != null;
40+
}
41+
42+
public boolean isClassAvailable(
43+
final @NotNull String clazz, final @Nullable SentryOptions options) {
44+
return isClassAvailable(clazz, options != null ? options.getLogger() : null);
1745
}
1846
}

0 commit comments

Comments
 (0)