Skip to content

Commit 66c90cd

Browse files
authored
Merge 5d02eba into 986d057
2 parents 986d057 + 5d02eba commit 66c90cd

10 files changed

Lines changed: 867 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

7+
- Add breadcrumbs on network changes ([#2608](https://github.com/getsentry/sentry-java/pull/2608))
78
- Add time-to-initial-display and time-to-full-display measurements to Activity transactions ([#2611](https://github.com/getsentry/sentry-java/pull/2611))
89
- Read integration list written by sentry gradle plugin from manifest ([#2598](https://github.com/getsentry/sentry-java/pull/2598))
910

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration
151151
public final fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
152152
}
153153

154+
public class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
155+
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/ILogger;)V
156+
public fun close ()V
157+
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
158+
}
159+
154160
public final class io/sentry/android/core/PhoneStateBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
155161
public fun <init> (Landroid/content/Context;)V
156162
public fun close ()V
@@ -193,6 +199,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
193199
public fun isEnableAppLifecycleBreadcrumbs ()Z
194200
public fun isEnableAutoActivityLifecycleTracing ()Z
195201
public fun isEnableFramesTracking ()Z
202+
public fun isEnableNetworkEventBreadcrumbs ()Z
196203
public fun isEnableSystemEventBreadcrumbs ()Z
197204
public fun setAnrEnabled (Z)V
198205
public fun setAnrReportInDebug (Z)V
@@ -207,6 +214,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
207214
public fun setEnableAppLifecycleBreadcrumbs (Z)V
208215
public fun setEnableAutoActivityLifecycleTracing (Z)V
209216
public fun setEnableFramesTracking (Z)V
217+
public fun setEnableNetworkEventBreadcrumbs (Z)V
210218
public fun setEnableSystemEventBreadcrumbs (Z)V
211219
public fun setProfilingTracesHz (I)V
212220
public fun setProfilingTracesIntervalMillis (I)V

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ private static void installDefaultIntegrations(
238238
}
239239
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
240240
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
241+
options.addIntegration(
242+
new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger()));
241243
options.addIntegration(new TempSensorBreadcrumbsIntegration(context));
242244
options.addIntegration(new PhoneStateBreadcrumbsIntegration(context));
243245
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ final class ManifestMetadataReader {
4848
"io.sentry.breadcrumbs.activity-lifecycle";
4949
static final String BREADCRUMBS_APP_LIFECYCLE_ENABLE = "io.sentry.breadcrumbs.app-lifecycle";
5050
static final String BREADCRUMBS_SYSTEM_EVENTS_ENABLE = "io.sentry.breadcrumbs.system-events";
51+
static final String BREADCRUMBS_NETWORK_EVENTS_ENABLE = "io.sentry.breadcrumbs.network-events";
5152
static final String BREADCRUMBS_APP_COMPONENTS_ENABLE = "io.sentry.breadcrumbs.app-components";
5253
static final String BREADCRUMBS_USER_INTERACTION_ENABLE =
5354
"io.sentry.breadcrumbs.user-interaction";
@@ -191,7 +192,7 @@ static void applyMetadata(
191192
metadata,
192193
logger,
193194
BREADCRUMBS_APP_LIFECYCLE_ENABLE,
194-
options.isEnableAppComponentBreadcrumbs()));
195+
options.isEnableAppLifecycleBreadcrumbs()));
195196

196197
options.setEnableSystemEventBreadcrumbs(
197198
readBool(
@@ -214,6 +215,13 @@ static void applyMetadata(
214215
BREADCRUMBS_USER_INTERACTION_ENABLE,
215216
options.isEnableUserInteractionBreadcrumbs()));
216217

218+
options.setEnableNetworkEventBreadcrumbs(
219+
readBool(
220+
metadata,
221+
logger,
222+
BREADCRUMBS_NETWORK_EVENTS_ENABLE,
223+
options.isEnableNetworkEventBreadcrumbs()));
224+
217225
options.setEnableUncaughtExceptionHandler(
218226
readBool(
219227
metadata,
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package io.sentry.android.core;
2+
3+
import android.annotation.SuppressLint;
4+
import android.content.Context;
5+
import android.net.ConnectivityManager;
6+
import android.net.Network;
7+
import android.net.NetworkCapabilities;
8+
import android.os.Build;
9+
import androidx.annotation.NonNull;
10+
import androidx.annotation.RequiresApi;
11+
import io.sentry.Breadcrumb;
12+
import io.sentry.Hint;
13+
import io.sentry.IHub;
14+
import io.sentry.ILogger;
15+
import io.sentry.Integration;
16+
import io.sentry.SentryLevel;
17+
import io.sentry.SentryOptions;
18+
import io.sentry.android.core.internal.util.ConnectivityChecker;
19+
import io.sentry.util.Objects;
20+
import java.io.Closeable;
21+
import java.io.IOException;
22+
import org.jetbrains.annotations.NotNull;
23+
import org.jetbrains.annotations.Nullable;
24+
import org.jetbrains.annotations.TestOnly;
25+
26+
public class NetworkBreadcrumbsIntegration implements Integration, Closeable {
27+
28+
private final @NotNull Context context;
29+
30+
private final @NotNull BuildInfoProvider buildInfoProvider;
31+
32+
private final @NotNull ILogger logger;
33+
34+
@TestOnly @Nullable NetworkBreadcrumbsNetworkCallback networkCallback;
35+
36+
public NetworkBreadcrumbsIntegration(
37+
final @NotNull Context context,
38+
final @NotNull BuildInfoProvider buildInfoProvider,
39+
final @NotNull ILogger logger) {
40+
this.context = Objects.requireNonNull(context, "Context is required");
41+
this.buildInfoProvider =
42+
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
43+
this.logger = Objects.requireNonNull(logger, "ILogger is required");
44+
}
45+
46+
@SuppressLint("NewApi")
47+
@Override
48+
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
49+
Objects.requireNonNull(hub, "Hub is required");
50+
SentryAndroidOptions androidOptions =
51+
Objects.requireNonNull(
52+
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
53+
"SentryAndroidOptions is required");
54+
55+
logger.log(
56+
SentryLevel.DEBUG,
57+
"NetworkBreadcrumbsIntegration enabled: %s",
58+
androidOptions.isEnableNetworkEventBreadcrumbs());
59+
60+
if (androidOptions.isEnableNetworkEventBreadcrumbs()) {
61+
62+
// The specific error is logged in the ConnectivityChecker method
63+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) {
64+
networkCallback = null;
65+
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration requires Android 5+");
66+
return;
67+
}
68+
69+
networkCallback = new NetworkBreadcrumbsNetworkCallback(hub, buildInfoProvider);
70+
final boolean registered =
71+
ConnectivityChecker.registerNetworkCallback(
72+
context, logger, buildInfoProvider, networkCallback);
73+
74+
// The specific error is logged in the ConnectivityChecker method
75+
if (!registered) {
76+
networkCallback = null;
77+
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration not installed.");
78+
return;
79+
}
80+
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration installed.");
81+
addIntegrationToSdkVersion();
82+
}
83+
}
84+
85+
@Override
86+
public void close() throws IOException {
87+
if (networkCallback != null) {
88+
ConnectivityChecker.unregisterNetworkCallback(
89+
context, logger, buildInfoProvider, networkCallback);
90+
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration remove.");
91+
}
92+
networkCallback = null;
93+
}
94+
95+
@SuppressLint("ObsoleteSdkInt")
96+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
97+
static final class NetworkBreadcrumbsNetworkCallback extends ConnectivityManager.NetworkCallback {
98+
final @NotNull IHub hub;
99+
final @NotNull BuildInfoProvider buildInfoProvider;
100+
101+
@Nullable Network currentNetwork = null;
102+
103+
@Nullable NetworkCapabilities lastCapabilities = null;
104+
105+
NetworkBreadcrumbsNetworkCallback(
106+
final @NotNull IHub hub, final @NotNull BuildInfoProvider buildInfoProvider) {
107+
this.hub = Objects.requireNonNull(hub, "Hub is required");
108+
this.buildInfoProvider =
109+
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
110+
}
111+
112+
@Override
113+
public void onAvailable(final @NonNull Network network) {
114+
if (network.equals(currentNetwork)) {
115+
return;
116+
}
117+
final Breadcrumb breadcrumb = createBreadcrumb("networkAvailable");
118+
hub.addBreadcrumb(breadcrumb);
119+
currentNetwork = network;
120+
lastCapabilities = null;
121+
}
122+
123+
@Override
124+
public void onCapabilitiesChanged(
125+
final @NonNull Network network, final @NonNull NetworkCapabilities networkCapabilities) {
126+
if (!network.equals(currentNetwork)) {
127+
return;
128+
}
129+
final @Nullable NetworkBreadcrumbConnectionDetail connectionDetail =
130+
getNewConnectionDetails(lastCapabilities, networkCapabilities);
131+
if (connectionDetail == null) {
132+
return;
133+
}
134+
lastCapabilities = networkCapabilities;
135+
final Breadcrumb breadcrumb = createBreadcrumb("networkCapabilitiesChanged");
136+
breadcrumb.setData("download_bandwidth", connectionDetail.downBandwidth);
137+
breadcrumb.setData("upload_bandwidth", connectionDetail.upBandwidth);
138+
breadcrumb.setData("vpn_active", connectionDetail.isVpn);
139+
breadcrumb.setData("network_type", connectionDetail.type);
140+
if (connectionDetail.signalStrength != 0) {
141+
breadcrumb.setData("signal_strength", connectionDetail.signalStrength);
142+
}
143+
Hint hint = new Hint();
144+
hint.set("data", connectionDetail);
145+
hub.addBreadcrumb(breadcrumb, hint);
146+
}
147+
148+
@Override
149+
public void onLost(final @NonNull Network network) {
150+
if (!network.equals(currentNetwork)) {
151+
return;
152+
}
153+
final Breadcrumb breadcrumb = createBreadcrumb("networkLost");
154+
hub.addBreadcrumb(breadcrumb);
155+
currentNetwork = null;
156+
lastCapabilities = null;
157+
}
158+
159+
private Breadcrumb createBreadcrumb(String action) {
160+
final Breadcrumb breadcrumb = new Breadcrumb();
161+
breadcrumb.setType("system");
162+
breadcrumb.setCategory("network.event");
163+
breadcrumb.setData("action", action);
164+
breadcrumb.setLevel(SentryLevel.INFO);
165+
return breadcrumb;
166+
}
167+
168+
private @Nullable NetworkBreadcrumbConnectionDetail getNewConnectionDetails(
169+
final @Nullable NetworkCapabilities oldCapabilities,
170+
final @NotNull NetworkCapabilities newCapabilities) {
171+
if (oldCapabilities == null) {
172+
return new NetworkBreadcrumbConnectionDetail(newCapabilities, buildInfoProvider);
173+
}
174+
NetworkBreadcrumbConnectionDetail oldConnectionDetails =
175+
new NetworkBreadcrumbConnectionDetail(oldCapabilities, buildInfoProvider);
176+
NetworkBreadcrumbConnectionDetail newConnectionDetails =
177+
new NetworkBreadcrumbConnectionDetail(newCapabilities, buildInfoProvider);
178+
179+
// We compare the details and if they are similar we return null, so that we don't spam the
180+
// user with lots of breadcrumbs for e.g. an increase of signal strength of 1 point
181+
if (newConnectionDetails.isSimilar(oldConnectionDetails)) {
182+
return null;
183+
}
184+
return newConnectionDetails;
185+
}
186+
}
187+
188+
static class NetworkBreadcrumbConnectionDetail {
189+
final int downBandwidth, upBandwidth, signalStrength;
190+
final boolean isVpn;
191+
final @NotNull String type;
192+
193+
@SuppressLint({"NewApi", "ObsoleteSdkInt"})
194+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
195+
NetworkBreadcrumbConnectionDetail(
196+
final @NotNull NetworkCapabilities networkCapabilities,
197+
final @NotNull BuildInfoProvider buildInfoProvider) {
198+
Objects.requireNonNull(networkCapabilities, "NetworkCapabilities is required");
199+
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
200+
this.downBandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps();
201+
this.upBandwidth = networkCapabilities.getLinkUpstreamBandwidthKbps();
202+
int strength =
203+
buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.Q
204+
? networkCapabilities.getSignalStrength()
205+
: 0;
206+
// If the system reports a signalStrength of Integer.MIN_VALUE, we adjust it to be 0
207+
this.signalStrength = strength > -100 ? strength : 0;
208+
this.isVpn = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
209+
String connectionType =
210+
ConnectivityChecker.getConnectionType(networkCapabilities, buildInfoProvider);
211+
this.type = connectionType != null ? connectionType : "";
212+
}
213+
214+
/**
215+
* Compares this connection detail to another one.
216+
*
217+
* @param other The other NetworkBreadcrumbConnectionDetail to compare
218+
* @return true if the details are similar enough, false otherwise
219+
*/
220+
boolean isSimilar(final @NotNull NetworkBreadcrumbConnectionDetail other) {
221+
return isVpn == other.isVpn
222+
&& type.equals(other.type)
223+
&& (-5 <= signalStrength - other.signalStrength
224+
&& signalStrength - other.signalStrength <= 5)
225+
&& (-1000 <= downBandwidth - other.downBandwidth
226+
&& downBandwidth - other.downBandwidth <= 1000)
227+
&& (-1000 <= upBandwidth - other.upBandwidth && upBandwidth - other.upBandwidth <= 1000);
228+
}
229+
}
230+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public final class SentryAndroidOptions extends SentryOptions {
3939
/** Enable or disable automatic breadcrumbs for App Components Using ComponentCallbacks */
4040
private boolean enableAppComponentBreadcrumbs = true;
4141

42+
/** Enable or disable automatic breadcrumbs for Network Events Using NetworkCallback */
43+
private boolean enableNetworkEventBreadcrumbs = true;
44+
4245
/**
4346
* Enables the Auto instrumentation for Activity lifecycle tracing.
4447
*
@@ -238,6 +241,14 @@ public void setEnableAppComponentBreadcrumbs(boolean enableAppComponentBreadcrum
238241
this.enableAppComponentBreadcrumbs = enableAppComponentBreadcrumbs;
239242
}
240243

244+
public boolean isEnableNetworkEventBreadcrumbs() {
245+
return enableNetworkEventBreadcrumbs;
246+
}
247+
248+
public void setEnableNetworkEventBreadcrumbs(boolean enableNetworkEventBreadcrumbs) {
249+
this.enableNetworkEventBreadcrumbs = enableNetworkEventBreadcrumbs;
250+
}
251+
241252
/**
242253
* Enable or disable all the automatic breadcrumbs
243254
*
@@ -248,6 +259,7 @@ public void enableAllAutoBreadcrumbs(boolean enable) {
248259
enableAppComponentBreadcrumbs = enable;
249260
enableSystemEventBreadcrumbs = enable;
250261
enableAppLifecycleBreadcrumbs = enable;
262+
enableNetworkEventBreadcrumbs = enable;
251263
setEnableUserInteractionBreadcrumbs(enable);
252264
}
253265

0 commit comments

Comments
 (0)