Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

automatic breadcrumbs for app, activity and sessions lifecycles and system events #348

Merged
merged 21 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0213453
draft for automatic breadcrumbs
marandaneto Apr 8, 2020
1f9a196
Merge branch 'master' into enha/automatic_breadcrumbs
marandaneto Apr 14, 2020
58f12cd
draft with more breadcrumbs
marandaneto Apr 14, 2020
9d684ef
fix merge conflict
marandaneto Apr 17, 2020
68f8137
Merge branch 'master' into enha/automatic_breadcrumbs
marandaneto Apr 20, 2020
d0f6e47
Merge branch 'master' into enha/automatic_breadcrumbs
marandaneto Apr 20, 2020
f0a22f7
a bit of refactoring
marandaneto Apr 20, 2020
0bf9f91
Merge branch 'master' into enha/automatic_breadcrumbs
marandaneto Apr 21, 2020
460c8a1
implement battery temperature and ringing phone breadcrumb
marandaneto Apr 21, 2020
1717733
refactoring
marandaneto Apr 21, 2020
02c2e2b
Merge branch 'master' into enha/automatic_breadcrumbs
marandaneto Apr 22, 2020
282b658
code review
marandaneto Apr 22, 2020
75cbe9b
add tests
marandaneto Apr 22, 2020
f2eeb5b
Merge branch 'master' into enha/automatic_breadcrumbs
marandaneto Apr 22, 2020
e68383c
added 150ms sleep to get enough time for triggering task
marandaneto Apr 22, 2020
7811042
code review
marandaneto Apr 22, 2020
4358fb1
code review
marandaneto Apr 23, 2020
5e583ad
added a test for shortening the action string
marandaneto Apr 24, 2020
c7c8fdd
added tests for automatic breadcrumbs configurations on manifest
marandaneto Apr 24, 2020
064b7c0
Merge branch 'master' of github.com:getsentry/sentry-android into enh…
marandaneto Apr 30, 2020
452ee82
matching up keys and values
marandaneto Apr 30, 2020
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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ spotless {
target("**/*.java")
removeUnusedImports()
googleJavaFormat()
paddedCell()
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.sentry.android.core;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.sentry.core.Breadcrumb;
import io.sentry.core.IHub;
import io.sentry.core.Integration;
import io.sentry.core.SentryLevel;
import io.sentry.core.SentryOptions;
import io.sentry.core.util.Objects;
import java.io.Closeable;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;

public final class ActivityBreadcrumbsIntegration
implements Integration, Closeable, Application.ActivityLifecycleCallbacks {

private final @NotNull Application application;
private @Nullable IHub hub;
private @Nullable SentryAndroidOptions options;

public ActivityBreadcrumbsIntegration(final @NotNull Application application) {
this.application = Objects.requireNonNull(application, "Application is required");
}

@Override
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");

this.hub = Objects.requireNonNull(hub, "Hub is required");

this.options
.getLogger()
.log(
SentryLevel.DEBUG,
"ActivityBreadcrumbsIntegration enabled: %s",
this.options.isEnableActivityLifecycleBreadcrumbs());

if (this.options.isEnableActivityLifecycleBreadcrumbs()) {
application.registerActivityLifecycleCallbacks(this);
options.getLogger().log(SentryLevel.DEBUG, "ActivityBreadcrumbsIntegration installed.");
}
}

@Override
public void close() throws IOException {
application.unregisterActivityLifecycleCallbacks(this);

if (options != null) {
options.getLogger().log(SentryLevel.DEBUG, "ActivityBreadcrumbsIntegration removed.");
}
}

private void addBreadcrumb(final @NonNull Activity activity, final @NotNull String state) {
if (hub != null) {
final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("navigation");
breadcrumb.setData("state", state);
breadcrumb.setData("screen", activity.getClass().getSimpleName());
breadcrumb.setCategory("ui.lifecycle");
hub.addBreadcrumb(breadcrumb);
}
}

@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
addBreadcrumb(activity, "created");
}

@Override
public void onActivityStarted(@NonNull Activity activity) {
addBreadcrumb(activity, "started");
}

@Override
public void onActivityResumed(@NonNull Activity activity) {
addBreadcrumb(activity, "resumed");
}

@Override
public void onActivityPaused(@NonNull Activity activity) {
addBreadcrumb(activity, "paused");
}

@Override
public void onActivityStopped(@NonNull Activity activity) {
addBreadcrumb(activity, "stopped");
}

@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
addBreadcrumb(activity, "saveInstanceState");
}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {
addBreadcrumb(activity, "destroyed");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.android.core;

import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import io.sentry.core.EnvelopeReader;
Expand Down Expand Up @@ -63,6 +64,22 @@ static void init(

final IEnvelopeReader envelopeReader = new EnvelopeReader();

installDefaultIntegrations(context, options, envelopeReader);

readDefaultOptionValues(options, context);

options.addEventProcessor(new DefaultAndroidEventProcessor(context, options));

options.setSerializer(new AndroidSerializer(options.getLogger(), envelopeReader));

options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
}

private static void installDefaultIntegrations(
final @NotNull Context context,
final @NotNull SentryOptions options,
final @NotNull IEnvelopeReader envelopeReader) {

// 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.
options.addIntegration(new NdkIntegration());
Expand Down Expand Up @@ -95,15 +112,20 @@ static void init(
}));

options.addIntegration(new AnrIntegration());
options.addIntegration(new SessionTrackingIntegration());

readDefaultOptionValues(options, context);

options.addEventProcessor(new DefaultAndroidEventProcessor(context, options));

options.setSerializer(new AndroidSerializer(options.getLogger(), envelopeReader));

options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
options.addIntegration(new AppLifecycleIntegration());
if (context instanceof Application) { // just a guard check, it should be an Application
options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context));
} else {
options
.getLogger()
.log(
SentryLevel.WARNING,
"ActivityBreadcrumbsIntegration needs an Application class to be installed.");
}
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
options.addIntegration(new TempSensorBreadcrumbsIntegration(context));
options.addIntegration(new PhoneStateBreadcrumbsIntegration(context));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.sentry.android.core;

import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.sentry.android.core.util.DeviceOrientations;
import io.sentry.core.Breadcrumb;
import io.sentry.core.IHub;
import io.sentry.core.Integration;
import io.sentry.core.SentryLevel;
import io.sentry.core.SentryOptions;
import io.sentry.core.protocol.Device;
import io.sentry.core.util.Objects;
import java.io.Closeable;
import java.io.IOException;
import java.util.Locale;
import org.jetbrains.annotations.NotNull;

public final class AppComponentsBreadcrumbsIntegration
implements Integration, Closeable, ComponentCallbacks {

private final @NotNull Context context;
private @Nullable IHub hub;
private @Nullable SentryAndroidOptions options;

public AppComponentsBreadcrumbsIntegration(final @NotNull Context context) {
this.context = Objects.requireNonNull(context, "Context is required");
}

@Override
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
this.hub = Objects.requireNonNull(hub, "Hub is required");
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");

this.options
.getLogger()
.log(
SentryLevel.DEBUG,
"AppComponentsBreadcrumbsIntegration enabled: %s",
this.options.isEnableAppComponentBreadcrumbs());

if (this.options.isEnableAppComponentBreadcrumbs()) {
context.registerComponentCallbacks(this);
options.getLogger().log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration installed.");
}
}

@Override
public void close() throws IOException {
context.unregisterComponentCallbacks(this);

if (options != null) {
options.getLogger().log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration removed.");
}
}

@SuppressWarnings("deprecation")
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (hub != null) {
final Device.DeviceOrientation deviceOrientation =
DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation);

String orientation;
if (deviceOrientation != null) {
orientation = deviceOrientation.name().toLowerCase(Locale.ROOT);
} else {
orientation = "undefined";
}

final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("navigation");
breadcrumb.setCategory("device.orientation");
breadcrumb.setData("position", orientation);
hub.addBreadcrumb(breadcrumb);
}
}

@Override
public void onLowMemory() {
if (hub != null) {
final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("info");
breadcrumb.setCategory("device.memory");
breadcrumb.setLevel(SentryLevel.WARNING);
hub.addBreadcrumb(breadcrumb);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,47 @@
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

public final class SessionTrackingIntegration implements Integration, Closeable {
public final class AppLifecycleIntegration implements Integration, Closeable {

@TestOnly @Nullable LifecycleWatcher watcher;

private @Nullable SentryOptions options;
private @Nullable SentryAndroidOptions options;

@Override
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
this.options = Objects.requireNonNull(options, "SentryOptions is required");
Objects.requireNonNull(hub, "Hub is required");
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");

options
this.options
.getLogger()
.log(
SentryLevel.DEBUG,
"SessionTrackingIntegration enabled: %s",
options.isEnableSessionTracking());
"enableSessionTracking enabled: %s",
this.options.isEnableSessionTracking());

if (options.isEnableSessionTracking()) {
this.options
.getLogger()
.log(
SentryLevel.DEBUG,
"enableAppLifecycleBreadcrumbs enabled: %s",
this.options.isEnableAppLifecycleBreadcrumbs());

if (this.options.isEnableSessionTracking() || this.options.isEnableAppLifecycleBreadcrumbs()) {
try {
Class.forName("androidx.lifecycle.DefaultLifecycleObserver");
Class.forName("androidx.lifecycle.ProcessLifecycleOwner");
watcher = new LifecycleWatcher(hub, options.getSessionTrackingIntervalMillis());
watcher =
new LifecycleWatcher(
hub,
this.options.getSessionTrackingIntervalMillis(),
options.isEnableSessionTracking(),
this.options.isEnableAppLifecycleBreadcrumbs());
ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher);

options.getLogger().log(SentryLevel.DEBUG, "SessionTrackingIntegration installed.");
options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration installed.");
} catch (ClassNotFoundException e) {
options
.getLogger()
Expand All @@ -55,7 +70,7 @@ public void close() throws IOException {
ProcessLifecycleOwner.get().getLifecycle().removeObserver(watcher);
watcher = null;
if (options != null) {
options.getLogger().log(SentryLevel.DEBUG, "SessionTrackingIntegration removed.");
options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration removed.");
}
}
}
Expand Down
Loading