Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Timber and Fragment integrations if they are present on the classpath #1936

Merged
merged 22 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
}

// 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());
romtsn marked this conversation as resolved.
Show resolved Hide resolved
}
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);
}
}
Loading