Skip to content

Commit

Permalink
Add breadcrumbs on network changes (#2608)
Browse files Browse the repository at this point in the history
* added NetworkBreadcrumbsIntegration
* added io.sentry.breadcrumbs.network-events manifest option
* added enableNetworkEventBreadcrumbs manual option
* added NetworkConnectionDetails in Breadcrumb data
  • Loading branch information
stefanosiano authored Mar 29, 2023
1 parent d265ac4 commit 7b0f520
Show file tree
Hide file tree
Showing 13 changed files with 875 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Add `name` and `geo` to `User` ([#2556](https://github.com/getsentry/sentry-java/pull/2556))
- Add breadcrumbs on network changes ([#2608](https://github.com/getsentry/sentry-java/pull/2608))
- Add time-to-initial-display and time-to-full-display measurements to Activity transactions ([#2611](https://github.com/getsentry/sentry-java/pull/2611))
- Read integration list written by sentry gradle plugin from manifest ([#2598](https://github.com/getsentry/sentry-java/pull/2598))
- Add Logcat adapter ([#2620](https://github.com/getsentry/sentry-java/pull/2620))
Expand Down
8 changes: 8 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration
public final fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/ILogger;)V
public fun close ()V
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/PhoneStateBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun close ()V
Expand Down Expand Up @@ -193,6 +199,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isEnableAppLifecycleBreadcrumbs ()Z
public fun isEnableAutoActivityLifecycleTracing ()Z
public fun isEnableFramesTracking ()Z
public fun isEnableNetworkEventBreadcrumbs ()Z
public fun isEnableSystemEventBreadcrumbs ()Z
public fun setAnrEnabled (Z)V
public fun setAnrReportInDebug (Z)V
Expand All @@ -207,6 +214,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableAppLifecycleBreadcrumbs (Z)V
public fun setEnableAutoActivityLifecycleTracing (Z)V
public fun setEnableFramesTracking (Z)V
public fun setEnableNetworkEventBreadcrumbs (Z)V
public fun setEnableSystemEventBreadcrumbs (Z)V
public fun setProfilingTracesHz (I)V
public fun setProfilingTracesIntervalMillis (I)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ private static void installDefaultIntegrations(
}
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
options.addIntegration(
new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger()));
options.addIntegration(new TempSensorBreadcrumbsIntegration(context));
options.addIntegration(new PhoneStateBreadcrumbsIntegration(context));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class ManifestMetadataReader {
"io.sentry.breadcrumbs.activity-lifecycle";
static final String BREADCRUMBS_APP_LIFECYCLE_ENABLE = "io.sentry.breadcrumbs.app-lifecycle";
static final String BREADCRUMBS_SYSTEM_EVENTS_ENABLE = "io.sentry.breadcrumbs.system-events";
static final String BREADCRUMBS_NETWORK_EVENTS_ENABLE = "io.sentry.breadcrumbs.network-events";
static final String BREADCRUMBS_APP_COMPONENTS_ENABLE = "io.sentry.breadcrumbs.app-components";
static final String BREADCRUMBS_USER_INTERACTION_ENABLE =
"io.sentry.breadcrumbs.user-interaction";
Expand Down Expand Up @@ -191,7 +192,7 @@ static void applyMetadata(
metadata,
logger,
BREADCRUMBS_APP_LIFECYCLE_ENABLE,
options.isEnableAppComponentBreadcrumbs()));
options.isEnableAppLifecycleBreadcrumbs()));

options.setEnableSystemEventBreadcrumbs(
readBool(
Expand All @@ -214,6 +215,13 @@ static void applyMetadata(
BREADCRUMBS_USER_INTERACTION_ENABLE,
options.isEnableUserInteractionBreadcrumbs()));

options.setEnableNetworkEventBreadcrumbs(
readBool(
metadata,
logger,
BREADCRUMBS_NETWORK_EVENTS_ENABLE,
options.isEnableNetworkEventBreadcrumbs()));

options.setEnableUncaughtExceptionHandler(
readBool(
metadata,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package io.sentry.android.core;

import android.annotation.SuppressLint;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import io.sentry.Breadcrumb;
import io.sentry.Hint;
import io.sentry.IHub;
import io.sentry.ILogger;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.TypeCheckHint;
import io.sentry.android.core.internal.util.ConnectivityChecker;
import io.sentry.util.Objects;
import java.io.Closeable;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

public final class NetworkBreadcrumbsIntegration implements Integration, Closeable {

private final @NotNull Context context;

private final @NotNull BuildInfoProvider buildInfoProvider;

private final @NotNull ILogger logger;

@TestOnly @Nullable NetworkBreadcrumbsNetworkCallback networkCallback;

public NetworkBreadcrumbsIntegration(
final @NotNull Context context,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull ILogger logger) {
this.context = Objects.requireNonNull(context, "Context is required");
this.buildInfoProvider =
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
this.logger = Objects.requireNonNull(logger, "ILogger is required");
}

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

logger.log(
SentryLevel.DEBUG,
"NetworkBreadcrumbsIntegration enabled: %s",
androidOptions.isEnableNetworkEventBreadcrumbs());

if (androidOptions.isEnableNetworkEventBreadcrumbs()) {

// The specific error is logged in the ConnectivityChecker method
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) {
networkCallback = null;
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration requires Android 5+");
return;
}

networkCallback = new NetworkBreadcrumbsNetworkCallback(hub, buildInfoProvider);
final boolean registered =
ConnectivityChecker.registerNetworkCallback(
context, logger, buildInfoProvider, networkCallback);

// The specific error is logged in the ConnectivityChecker method
if (!registered) {
networkCallback = null;
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration not installed.");
return;
}
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration installed.");
addIntegrationToSdkVersion();
}
}

@Override
public void close() throws IOException {
if (networkCallback != null) {
ConnectivityChecker.unregisterNetworkCallback(
context, logger, buildInfoProvider, networkCallback);
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration remove.");
}
networkCallback = null;
}

@SuppressLint("ObsoleteSdkInt")
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
static final class NetworkBreadcrumbsNetworkCallback extends ConnectivityManager.NetworkCallback {
final @NotNull IHub hub;
final @NotNull BuildInfoProvider buildInfoProvider;

@Nullable Network currentNetwork = null;

@Nullable NetworkCapabilities lastCapabilities = null;

NetworkBreadcrumbsNetworkCallback(
final @NotNull IHub hub, final @NotNull BuildInfoProvider buildInfoProvider) {
this.hub = Objects.requireNonNull(hub, "Hub is required");
this.buildInfoProvider =
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
}

@Override
public void onAvailable(final @NonNull Network network) {
if (network.equals(currentNetwork)) {
return;
}
final Breadcrumb breadcrumb = createBreadcrumb("NETWORK_AVAILABLE");
hub.addBreadcrumb(breadcrumb);
currentNetwork = network;
lastCapabilities = null;
}

@Override
public void onCapabilitiesChanged(
final @NonNull Network network, final @NonNull NetworkCapabilities networkCapabilities) {
if (!network.equals(currentNetwork)) {
return;
}
final @Nullable NetworkBreadcrumbConnectionDetail connectionDetail =
getNewConnectionDetails(lastCapabilities, networkCapabilities);
if (connectionDetail == null) {
return;
}
lastCapabilities = networkCapabilities;
final Breadcrumb breadcrumb = createBreadcrumb("NETWORK_CAPABILITIES_CHANGED");
breadcrumb.setData("download_bandwidth", connectionDetail.downBandwidth);
breadcrumb.setData("upload_bandwidth", connectionDetail.upBandwidth);
breadcrumb.setData("vpn_active", connectionDetail.isVpn);
breadcrumb.setData("network_type", connectionDetail.type);
if (connectionDetail.signalStrength != 0) {
breadcrumb.setData("signal_strength", connectionDetail.signalStrength);
}
Hint hint = new Hint();
hint.set(TypeCheckHint.ANDROID_NETWORK_CAPABILITIES, connectionDetail);
hub.addBreadcrumb(breadcrumb, hint);
}

@Override
public void onLost(final @NonNull Network network) {
if (!network.equals(currentNetwork)) {
return;
}
final Breadcrumb breadcrumb = createBreadcrumb("NETWORK_LOST");
hub.addBreadcrumb(breadcrumb);
currentNetwork = null;
lastCapabilities = null;
}

private Breadcrumb createBreadcrumb(String action) {
final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("system");
breadcrumb.setCategory("network.event");
breadcrumb.setData("action", action);
breadcrumb.setLevel(SentryLevel.INFO);
return breadcrumb;
}

private @Nullable NetworkBreadcrumbConnectionDetail getNewConnectionDetails(
final @Nullable NetworkCapabilities oldCapabilities,
final @NotNull NetworkCapabilities newCapabilities) {
if (oldCapabilities == null) {
return new NetworkBreadcrumbConnectionDetail(newCapabilities, buildInfoProvider);
}
NetworkBreadcrumbConnectionDetail oldConnectionDetails =
new NetworkBreadcrumbConnectionDetail(oldCapabilities, buildInfoProvider);
NetworkBreadcrumbConnectionDetail newConnectionDetails =
new NetworkBreadcrumbConnectionDetail(newCapabilities, buildInfoProvider);

// We compare the details and if they are similar we return null, so that we don't spam the
// user with lots of breadcrumbs for e.g. an increase of signal strength of 1 point
if (newConnectionDetails.isSimilar(oldConnectionDetails)) {
return null;
}
return newConnectionDetails;
}
}

static class NetworkBreadcrumbConnectionDetail {
final int downBandwidth, upBandwidth, signalStrength;
final boolean isVpn;
final @NotNull String type;

@SuppressLint({"NewApi", "ObsoleteSdkInt"})
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
NetworkBreadcrumbConnectionDetail(
final @NotNull NetworkCapabilities networkCapabilities,
final @NotNull BuildInfoProvider buildInfoProvider) {
Objects.requireNonNull(networkCapabilities, "NetworkCapabilities is required");
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
this.downBandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps();
this.upBandwidth = networkCapabilities.getLinkUpstreamBandwidthKbps();
int strength =
buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.Q
? networkCapabilities.getSignalStrength()
: 0;
// If the system reports a signalStrength of Integer.MIN_VALUE, we adjust it to be 0
this.signalStrength = strength > -100 ? strength : 0;
this.isVpn = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
String connectionType =
ConnectivityChecker.getConnectionType(networkCapabilities, buildInfoProvider);
this.type = connectionType != null ? connectionType : "";
}

/**
* Compares this connection detail to another one.
*
* @param other The other NetworkBreadcrumbConnectionDetail to compare
* @return true if the details are similar enough, false otherwise
*/
boolean isSimilar(final @NotNull NetworkBreadcrumbConnectionDetail other) {
return isVpn == other.isVpn
&& type.equals(other.type)
&& (-5 <= signalStrength - other.signalStrength
&& signalStrength - other.signalStrength <= 5)
&& (-1000 <= downBandwidth - other.downBandwidth
&& downBandwidth - other.downBandwidth <= 1000)
&& (-1000 <= upBandwidth - other.upBandwidth && upBandwidth - other.upBandwidth <= 1000);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public final class SentryAndroidOptions extends SentryOptions {
/** Enable or disable automatic breadcrumbs for App Components Using ComponentCallbacks */
private boolean enableAppComponentBreadcrumbs = true;

/** Enable or disable automatic breadcrumbs for Network Events Using NetworkCallback */
private boolean enableNetworkEventBreadcrumbs = true;

/**
* Enables the Auto instrumentation for Activity lifecycle tracing.
*
Expand Down Expand Up @@ -238,6 +241,14 @@ public void setEnableAppComponentBreadcrumbs(boolean enableAppComponentBreadcrum
this.enableAppComponentBreadcrumbs = enableAppComponentBreadcrumbs;
}

public boolean isEnableNetworkEventBreadcrumbs() {
return enableNetworkEventBreadcrumbs;
}

public void setEnableNetworkEventBreadcrumbs(boolean enableNetworkEventBreadcrumbs) {
this.enableNetworkEventBreadcrumbs = enableNetworkEventBreadcrumbs;
}

/**
* Enable or disable all the automatic breadcrumbs
*
Expand All @@ -248,6 +259,7 @@ public void enableAllAutoBreadcrumbs(boolean enable) {
enableAppComponentBreadcrumbs = enable;
enableSystemEventBreadcrumbs = enable;
enableAppLifecycleBreadcrumbs = enable;
enableNetworkEventBreadcrumbs = enable;
setEnableUserInteractionBreadcrumbs(enable);
}

Expand Down
Loading

0 comments on commit 7b0f520

Please sign in to comment.