Skip to content

Commit db4ed1b

Browse files
committed
Merge branch 'rz/feat/session-replay-breadcrumbs-customizer' into rz/fix/session-replay-redaction
2 parents 9c874d7 + c2dcad5 commit db4ed1b

File tree

56 files changed

+1203
-289
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1203
-289
lines changed

CHANGELOG.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
# Changelog
22

3-
## 7.8.0-alpha.0
3+
## 7.9.0-alpha.1
44

5-
- No documented changes.
5+
- Session Replay for Android ([#3339](https://github.com/getsentry/sentry-java/pull/3339))
6+
7+
We released our second Alpha version of the SDK with support. To get access, it requires adding your Sentry org to our feature flag. Please let us know on the [waitlist](https://sentry.io/lp/mobile-replay-beta/) if you're interested
8+
9+
### Features
10+
11+
- Add start_type to app context ([#3379](https://github.com/getsentry/sentry-java/pull/3379))
12+
13+
### Fixes
14+
15+
- Fix Frame measurements in app start transactions ([#3382](https://github.com/getsentry/sentry-java/pull/3382))
16+
- Fix timing metric value different from span duration ([#3368](https://github.com/getsentry/sentry-java/pull/3368))
617

718
## 7.8.0
819

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ android.useAndroidX=true
1010
android.defaults.buildfeatures.buildconfig=true
1111

1212
# Release information
13-
versionName=7.8.0-alpha.0
13+
versionName=7.9.0-alpha.1
1414

1515
# Override the SDK name on native crashes on Android
1616
sentryAndroidSdkName=sentry.native.android

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,11 @@ public final class io/sentry/android/core/CurrentActivityIntegration : android/a
183183
public final class io/sentry/android/core/DeviceInfoUtil {
184184
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)V
185185
public fun collectDeviceInformation (ZZ)Lio/sentry/protocol/Device;
186+
public static fun getBatteryLevel (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Float;
186187
public static fun getInstance (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/DeviceInfoUtil;
187188
public fun getOperatingSystem ()Lio/sentry/protocol/OperatingSystem;
188189
public fun getSideLoadedInfo ()Lio/sentry/android/core/ContextUtils$SideLoadedInfo;
190+
public static fun isCharging (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Boolean;
189191
public static fun resetInstance ()V
190192
}
191193

sentry-android-core/proguard-rules.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,6 @@
7575

7676
##---------------Begin: proguard configuration for sentry-android-replay ----------
7777
-dontwarn io.sentry.android.replay.ReplayIntegration
78-
-dontwarn io.sentry.android.replay.ReplayIntegrationKt
78+
-dontwarn io.sentry.android.replay.DefaultReplayBreadcrumbConverter
7979
-keepnames class io.sentry.android.replay.ReplayIntegration
8080
##---------------End: proguard configuration for sentry-android-replay ----------

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
@@ -22,6 +22,7 @@
2222
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
2323
import io.sentry.android.core.performance.AppStartMetrics;
2424
import io.sentry.android.fragment.FragmentLifecycleIntegration;
25+
import io.sentry.android.replay.DefaultReplayBreadcrumbConverter;
2526
import io.sentry.android.replay.ReplayIntegration;
2627
import io.sentry.android.timber.SentryTimberIntegration;
2728
import io.sentry.cache.PersistingOptionsObserver;
@@ -308,6 +309,7 @@ static void installDefaultIntegrations(
308309
if (isReplayAvailable) {
309310
final ReplayIntegration replay =
310311
new ReplayIntegration(context, CurrentDateProvider.getInstance());
312+
replay.setBreadcrumbConverter(new DefaultReplayBreadcrumbConverter());
311313
options.addIntegration(replay);
312314
options.setReplayController(replay);
313315
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import android.util.DisplayMetrics;
1717
import io.sentry.DateUtils;
1818
import io.sentry.SentryLevel;
19+
import io.sentry.SentryOptions;
1920
import io.sentry.android.core.internal.util.CpuInfoUtils;
2021
import io.sentry.android.core.internal.util.DeviceOrientations;
2122
import io.sentry.android.core.internal.util.RootChecker;
@@ -184,8 +185,8 @@ public ContextUtils.SideLoadedInfo getSideLoadedInfo() {
184185
private void setDeviceIO(final @NotNull Device device, final boolean includeDynamicData) {
185186
final Intent batteryIntent = getBatteryIntent();
186187
if (batteryIntent != null) {
187-
device.setBatteryLevel(getBatteryLevel(batteryIntent));
188-
device.setCharging(isCharging(batteryIntent));
188+
device.setBatteryLevel(getBatteryLevel(batteryIntent, options));
189+
device.setCharging(isCharging(batteryIntent, options));
189190
device.setBatteryTemperature(getBatteryTemperature(batteryIntent));
190191
}
191192

@@ -270,7 +271,8 @@ private Intent getBatteryIntent() {
270271
* @return the device's current battery level (as a percentage of total), or null if unknown
271272
*/
272273
@Nullable
273-
private Float getBatteryLevel(final @NotNull Intent batteryIntent) {
274+
public static Float getBatteryLevel(
275+
final @NotNull Intent batteryIntent, final @NotNull SentryOptions options) {
274276
try {
275277
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
276278
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
@@ -294,7 +296,8 @@ private Float getBatteryLevel(final @NotNull Intent batteryIntent) {
294296
* @return whether or not the device is currently plugged in and charging, or null if unknown
295297
*/
296298
@Nullable
297-
private Boolean isCharging(final @NotNull Intent batteryIntent) {
299+
public static Boolean isCharging(
300+
final @NotNull Intent batteryIntent, final @NotNull SentryOptions options) {
298301
try {
299302
int plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
300303
return plugged == BatteryManager.BATTERY_PLUGGED_AC

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

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
1717
import io.sentry.android.core.performance.AppStartMetrics;
1818
import io.sentry.android.core.performance.TimeSpan;
19+
import io.sentry.protocol.App;
1920
import io.sentry.protocol.MeasurementValue;
2021
import io.sentry.protocol.SentryId;
2122
import io.sentry.protocol.SentrySpan;
@@ -79,27 +80,40 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
7980

8081
// the app start measurement is only sent once and only if the transaction has
8182
// the app.start span, which is automatically created by the SDK.
82-
if (!sentStartMeasurement && hasAppStartSpan(transaction)) {
83-
final @NotNull TimeSpan appStartTimeSpan =
84-
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
85-
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();
86-
87-
// if appStartUpDurationMs is 0, metrics are not ready to be sent
88-
if (appStartUpDurationMs != 0) {
89-
final MeasurementValue value =
90-
new MeasurementValue(
91-
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());
92-
93-
final String appStartKey =
94-
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
95-
? MeasurementValue.KEY_APP_START_COLD
96-
: MeasurementValue.KEY_APP_START_WARM;
97-
98-
transaction.getMeasurements().put(appStartKey, value);
99-
100-
attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction);
101-
sentStartMeasurement = true;
83+
if (hasAppStartSpan(transaction)) {
84+
if (!sentStartMeasurement) {
85+
final @NotNull TimeSpan appStartTimeSpan =
86+
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
87+
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();
88+
89+
// if appStartUpDurationMs is 0, metrics are not ready to be sent
90+
if (appStartUpDurationMs != 0) {
91+
final MeasurementValue value =
92+
new MeasurementValue(
93+
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());
94+
95+
final String appStartKey =
96+
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
97+
? MeasurementValue.KEY_APP_START_COLD
98+
: MeasurementValue.KEY_APP_START_WARM;
99+
100+
transaction.getMeasurements().put(appStartKey, value);
101+
102+
attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction);
103+
sentStartMeasurement = true;
104+
}
105+
}
106+
107+
@Nullable App appContext = transaction.getContexts().getApp();
108+
if (appContext == null) {
109+
appContext = new App();
110+
transaction.getContexts().setApp(appContext);
102111
}
112+
final String appStartType =
113+
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
114+
? "cold"
115+
: "warm";
116+
appContext.setStartType(appStartType);
103117
}
104118

105119
final SentryId eventId = transaction.getEventId();

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ public final class SentryAndroid {
3636
static final String SENTRY_REPLAY_INTEGRATION_CLASS_NAME =
3737
"io.sentry.android.replay.ReplayIntegration";
3838

39-
private static boolean isReplayAvailable = false;
40-
4139
private static final String TIMBER_CLASS_NAME = "timber.log.Timber";
4240
private static final String FRAGMENT_CLASS_NAME =
4341
"androidx.fragment.app.FragmentManager$FragmentLifecycleCallbacks";
@@ -104,7 +102,7 @@ public static synchronized void init(
104102
final boolean isTimberAvailable =
105103
(isTimberUpstreamAvailable
106104
&& classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));
107-
isReplayAvailable =
105+
final boolean isReplayAvailable =
108106
classLoader.isClassAvailable(SENTRY_REPLAY_INTEGRATION_CLASS_NAME, options);
109107

110108
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.sentry.NoOpTransaction;
88
import io.sentry.SentryDate;
99
import io.sentry.SentryNanotimeDate;
10+
import io.sentry.SentryTracer;
1011
import io.sentry.SpanDataConvention;
1112
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
1213
import io.sentry.protocol.MeasurementValue;
@@ -135,11 +136,15 @@ private void captureFrameMetrics(@NotNull final ISpan span) {
135136
return;
136137
}
137138

138-
// ignore spans with no finish date
139-
final @Nullable SentryDate spanFinishDate = span.getFinishDate();
139+
// Ignore spans with no finish date, but SentryTracer is not finished when executing this
140+
// callback, yet, so in that case we use the current timestamp.
141+
final @Nullable SentryDate spanFinishDate =
142+
span instanceof SentryTracer ? new SentryNanotimeDate() : span.getFinishDate();
140143
if (spanFinishDate == null) {
141144
return;
142145
}
146+
// Note: The comparison between two values obtained by realNanos() works only if both are the
147+
// same kind of dates (both are SentryNanotimeDate or both SentryLongDate)
143148
final long spanEndNanos = realNanos(spanFinishDate);
144149

145150
final @NotNull SentryFrameMetrics frameMetrics = new SentryFrameMetrics();

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

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE;
77
import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED;
88
import static android.content.Intent.ACTION_APP_ERROR;
9+
import static android.content.Intent.ACTION_BATTERY_CHANGED;
910
import static android.content.Intent.ACTION_BATTERY_LOW;
1011
import static android.content.Intent.ACTION_BATTERY_OKAY;
1112
import static android.content.Intent.ACTION_BOOT_COMPLETED;
@@ -41,10 +42,11 @@
4142
import io.sentry.Breadcrumb;
4243
import io.sentry.Hint;
4344
import io.sentry.IHub;
44-
import io.sentry.ILogger;
4545
import io.sentry.Integration;
4646
import io.sentry.SentryLevel;
4747
import io.sentry.SentryOptions;
48+
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
49+
import io.sentry.android.core.internal.util.Debouncer;
4850
import io.sentry.util.Objects;
4951
import io.sentry.util.StringUtils;
5052
import java.io.Closeable;
@@ -120,7 +122,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
120122

121123
private void startSystemEventsReceiver(
122124
final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) {
123-
receiver = new SystemEventsBroadcastReceiver(hub, options.getLogger());
125+
receiver = new SystemEventsBroadcastReceiver(hub, options);
124126
final IntentFilter filter = new IntentFilter();
125127
for (String item : actions) {
126128
filter.addAction(item);
@@ -154,6 +156,7 @@ private void startSystemEventsReceiver(
154156
actions.add(ACTION_AIRPLANE_MODE_CHANGED);
155157
actions.add(ACTION_BATTERY_LOW);
156158
actions.add(ACTION_BATTERY_OKAY);
159+
actions.add(ACTION_BATTERY_CHANGED);
157160
actions.add(ACTION_BOOT_COMPLETED);
158161
actions.add(ACTION_CAMERA_BUTTON);
159162
actions.add(ACTION_CONFIGURATION_CHANGED);
@@ -204,45 +207,69 @@ public void close() throws IOException {
204207

205208
static final class SystemEventsBroadcastReceiver extends BroadcastReceiver {
206209

210+
private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000;
207211
private final @NotNull IHub hub;
208-
private final @NotNull ILogger logger;
212+
private final @NotNull SentryAndroidOptions options;
213+
private final @NotNull Debouncer debouncer =
214+
new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0);
209215

210-
SystemEventsBroadcastReceiver(final @NotNull IHub hub, final @NotNull ILogger logger) {
216+
SystemEventsBroadcastReceiver(
217+
final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) {
211218
this.hub = hub;
212-
this.logger = logger;
219+
this.options = options;
213220
}
214221

215222
@Override
216223
public void onReceive(Context context, Intent intent) {
224+
final boolean shouldDebounce = debouncer.checkForDebounce();
225+
final String action = intent.getAction();
226+
final boolean isBatteryChanged = ACTION_BATTERY_CHANGED.equals(action);
227+
if (isBatteryChanged && shouldDebounce) {
228+
// aligning with iOS which only captures battery status changes every minute at maximum
229+
return;
230+
}
231+
217232
final Breadcrumb breadcrumb = new Breadcrumb();
218233
breadcrumb.setType("system");
219234
breadcrumb.setCategory("device.event");
220-
final String action = intent.getAction();
221235
String shortAction = StringUtils.getStringAfterDot(action);
222236
if (shortAction != null) {
223237
breadcrumb.setData("action", shortAction);
224238
}
225239

226-
final Bundle extras = intent.getExtras();
227-
final Map<String, String> newExtras = new HashMap<>();
228-
if (extras != null && !extras.isEmpty()) {
229-
for (String item : extras.keySet()) {
230-
try {
231-
@SuppressWarnings("deprecation")
232-
Object value = extras.get(item);
233-
if (value != null) {
234-
newExtras.put(item, value.toString());
240+
if (isBatteryChanged) {
241+
final Float batteryLevel = DeviceInfoUtil.getBatteryLevel(intent, options);
242+
if (batteryLevel != null) {
243+
breadcrumb.setData("level", batteryLevel);
244+
}
245+
final Boolean isCharging = DeviceInfoUtil.isCharging(intent, options);
246+
if (isCharging != null) {
247+
breadcrumb.setData("charging", isCharging);
248+
}
249+
} else {
250+
final Bundle extras = intent.getExtras();
251+
final Map<String, String> newExtras = new HashMap<>();
252+
if (extras != null && !extras.isEmpty()) {
253+
for (String item : extras.keySet()) {
254+
try {
255+
@SuppressWarnings("deprecation")
256+
Object value = extras.get(item);
257+
if (value != null) {
258+
newExtras.put(item, value.toString());
259+
}
260+
} catch (Throwable exception) {
261+
options
262+
.getLogger()
263+
.log(
264+
SentryLevel.ERROR,
265+
exception,
266+
"%s key of the %s action threw an error.",
267+
item,
268+
action);
235269
}
236-
} catch (Throwable exception) {
237-
logger.log(
238-
SentryLevel.ERROR,
239-
exception,
240-
"%s key of the %s action threw an error.",
241-
item,
242-
action);
243270
}
271+
breadcrumb.setData("extras", newExtras);
244272
}
245-
breadcrumb.setData("extras", newExtras);
246273
}
247274
breadcrumb.setLevel(SentryLevel.INFO);
248275

0 commit comments

Comments
 (0)