Skip to content

Commit

Permalink
Add Timber and Fragment integrations if they are present on the class…
Browse files Browse the repository at this point in the history
…path (#1936)

Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>
  • Loading branch information
romtsn and bruno-garcia committed Mar 15, 2022
1 parent db05017 commit 4bfe31b
Show file tree
Hide file tree
Showing 16 changed files with 426 additions and 334 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Feat: Automatically enable `Timber` and `Fragment` integrations if they are present on the classpath (#1936)

## 5.6.3

* Fix: If transaction or span is finished, do not allow to mutate (#1940)
Expand Down
5 changes: 4 additions & 1 deletion sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
public final class io/sentry/android/core/ActivityFramesTracker {
public fun <init> (Lio/sentry/android/core/LoadClass;)V
public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/ILogger;)V
public fun addActivity (Landroid/app/Activity;)V
public fun setMetrics (Landroid/app/Activity;Lio/sentry/protocol/SentryId;)V
public fun stop ()V
Expand Down Expand Up @@ -82,7 +83,9 @@ public abstract interface class io/sentry/android/core/IDebugImagesLoader {

public final class io/sentry/android/core/LoadClass {
public fun <init> ()V
public fun loadClass (Ljava/lang/String;)Ljava/lang/Class;
public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z
public fun isClassAvailable (Ljava/lang/String;Lio/sentry/SentryOptions;)Z
public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class;
}

public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration, java/io/Closeable {
Expand Down
5 changes: 5 additions & 0 deletions sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ android {

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

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

dependencies {
api(projects.sentry)
compileOnly(projects.sentryAndroidFragment)
compileOnly(projects.sentryAndroidTimber)

// lifecycle processor, session tracking
implementation(Config.Libs.lifecycleProcess)
Expand All @@ -102,4 +105,6 @@ dependencies {
testImplementation(Config.TestLibs.mockitoInline)
testImplementation(Config.TestLibs.awaitility)
testImplementation(projects.sentryTestSupport)
testImplementation(projects.sentryAndroidFragment)
testImplementation(projects.sentryAndroidTimber)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Activity;
import android.util.SparseIntArray;
import androidx.core.app.FrameMetricsAggregator;
import io.sentry.ILogger;
import io.sentry.protocol.MeasurementValue;
import io.sentry.protocol.SentryId;
import java.util.HashMap;
Expand All @@ -25,28 +26,23 @@ public final class ActivityFramesTracker {
private final @NotNull Map<SentryId, Map<String, @NotNull MeasurementValue>>
activityMeasurements = new ConcurrentHashMap<>();

public ActivityFramesTracker(final @NotNull LoadClass loadClass) {
androidXAvailable = checkAndroidXAvailability(loadClass);
public ActivityFramesTracker(final @NotNull LoadClass loadClass, final @Nullable ILogger logger) {
androidXAvailable =
loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", logger);
if (androidXAvailable) {
frameMetricsAggregator = new FrameMetricsAggregator();
}
}

public ActivityFramesTracker(final @NotNull LoadClass loadClass) {
this(loadClass, null);
}

@TestOnly
ActivityFramesTracker(final @Nullable FrameMetricsAggregator frameMetricsAggregator) {
this.frameMetricsAggregator = frameMetricsAggregator;
}

private static boolean checkAndroidXAvailability(final @NotNull LoadClass loadClass) {
try {
loadClass.loadClass("androidx.core.app.FrameMetricsAggregator");
return true;
} catch (ClassNotFoundException ignored) {
// androidx.core isn't available.
return false;
}
}

private boolean isFrameMetricsAggregatorAvailable() {
return androidXAvailable && frameMetricsAggregator != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import io.sentry.SendFireAndForgetOutboxSender;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
import io.sentry.util.Objects;
import java.io.BufferedInputStream;
import java.io.File;
Expand Down Expand Up @@ -43,7 +45,7 @@ static void init(final @NotNull SentryAndroidOptions options, final @NotNull Con
Objects.requireNonNull(context, "The application context is required.");
Objects.requireNonNull(options, "The options object is required.");

init(options, context, new AndroidLogger());
init(options, context, new AndroidLogger(), false, false);
}

/**
Expand All @@ -52,12 +54,16 @@ static void init(final @NotNull SentryAndroidOptions options, final @NotNull Con
* @param options the SentryOptions
* @param context the Application context
* @param logger the ILogger interface
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
* @param isTimberAvailable whether the Timber integration is available on the classpath
*/
static void init(
final @NotNull SentryAndroidOptions options,
@NotNull Context context,
final @NotNull ILogger logger) {
init(options, context, logger, new BuildInfoProvider());
final @NotNull ILogger logger,
final boolean isFragmentAvailable,
final boolean isTimberAvailable) {
init(options, context, logger, new BuildInfoProvider(), isFragmentAvailable, isTimberAvailable);
}

/**
Expand All @@ -67,13 +73,24 @@ static void init(
* @param context the Application context
* @param logger the ILogger interface
* @param buildInfoProvider the IBuildInfoProvider interface
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
* @param isTimberAvailable whether the Timber integration is available on the classpath
*/
static void init(
final @NotNull SentryAndroidOptions options,
@NotNull Context context,
final @NotNull ILogger logger,
final @NotNull IBuildInfoProvider buildInfoProvider) {
init(options, context, logger, buildInfoProvider, new LoadClass());
final @NotNull IBuildInfoProvider buildInfoProvider,
final boolean isFragmentAvailable,
final boolean isTimberAvailable) {
init(
options,
context,
logger,
buildInfoProvider,
new LoadClass(),
isFragmentAvailable,
isTimberAvailable);
}

/**
Expand All @@ -84,13 +101,17 @@ static void init(
* @param logger the ILogger interface
* @param buildInfoProvider the IBuildInfoProvider interface
* @param loadClass the LoadClass wrapper
* @param isFragmentAvailable whether the Fragment integration is available on the classpath
* @param isTimberAvailable whether the Timber integration is available on the classpath
*/
static void init(
final @NotNull SentryAndroidOptions options,
@NotNull Context context,
final @NotNull ILogger logger,
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull LoadClass loadClass) {
final @NotNull LoadClass loadClass,
final boolean isFragmentAvailable,
final boolean isTimberAvailable) {
Objects.requireNonNull(context, "The context is required.");

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

final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker(loadClass);
final ActivityFramesTracker activityFramesTracker =
new ActivityFramesTracker(loadClass, options.getLogger());
installDefaultIntegrations(
context, options, buildInfoProvider, loadClass, activityFramesTracker);
context,
options,
buildInfoProvider,
loadClass,
activityFramesTracker,
isFragmentAvailable,
isTimberAvailable);

readDefaultOptionValues(options, context);

Expand All @@ -124,15 +152,20 @@ private static void installDefaultIntegrations(
final @NotNull SentryOptions options,
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull LoadClass loadClass,
final @NotNull ActivityFramesTracker activityFramesTracker) {
final @NotNull ActivityFramesTracker activityFramesTracker,
final boolean isFragmentAvailable,
final boolean isTimberAvailable) {

options.addIntegration(
new SendCachedEnvelopeFireAndForgetIntegration(
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath())));

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

// this integration uses android.os.FileObserver, we can't move to sentry
Expand All @@ -155,12 +188,18 @@ private static void installDefaultIntegrations(
new ActivityLifecycleIntegration(
(Application) context, buildInfoProvider, activityFramesTracker));
options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
if (isFragmentAvailable) {
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
}
} else {
options
.getLogger()
.log(
SentryLevel.WARNING,
"ActivityLifecycle and UserInteraction Integrations need an Application class to be installed.");
"ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed.");
}
if (isTimberAvailable) {
options.addIntegration(new SentryTimberIntegration());
}
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
Expand Down Expand Up @@ -257,24 +296,4 @@ private static void initializeCacheDirs(
private static boolean isNdkAvailable(final @NotNull IBuildInfoProvider buildInfoProvider) {
return buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN;
}

private static @Nullable Class<?> loadNdkIfAvailable(
final @NotNull SentryOptions options,
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull LoadClass loadClass) {
if (isNdkAvailable(buildInfoProvider)) {
try {
return loadClass.loadClass(SENTRY_NDK_CLASS_NAME);
} catch (ClassNotFoundException e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to load SentryNdk.", e);
} catch (UnsatisfiedLinkError e) {
options
.getLogger()
.log(SentryLevel.ERROR, "Failed to load (UnsatisfiedLinkError) SentryNdk.", e);
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package io.sentry.android.core;

import io.sentry.ILogger;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** An Adapter for making Class.forName testable */
public final class LoadClass {
Expand All @@ -9,10 +13,34 @@ public final class LoadClass {
* Try to load a class via reflection
*
* @param clazz the full class name
* @return a Class<?>
* @throws ClassNotFoundException if class is not found
* @param logger an instance of ILogger
* @return a Class<?> if it's available, or null
*/
public @NotNull Class<?> loadClass(@NotNull String clazz) throws ClassNotFoundException {
return Class.forName(clazz);
public @Nullable Class<?> loadClass(final @NotNull String clazz, final @Nullable ILogger logger) {
try {
return Class.forName(clazz);
} catch (ClassNotFoundException e) {
if (logger != null) {
logger.log(SentryLevel.DEBUG, "Class not available:" + clazz, e);
}
} catch (UnsatisfiedLinkError e) {
if (logger != null) {
logger.log(SentryLevel.ERROR, "Failed to load (UnsatisfiedLinkError) " + clazz, e);
}
} catch (Throwable e) {
if (logger != null) {
logger.log(SentryLevel.ERROR, "Failed to initialize " + clazz, e);
}
}
return null;
}

public boolean isClassAvailable(final @NotNull String clazz, final @Nullable ILogger logger) {
return loadClass(clazz, logger) != null;
}

public boolean isClassAvailable(
final @NotNull String clazz, final @Nullable SentryOptions options) {
return isClassAvailable(clazz, options != null ? options.getLogger() : null);
}
}

0 comments on commit 4bfe31b

Please sign in to comment.