From 9596bb93199a1032cb255a62bea32020bd04c9ac Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 3 May 2024 17:31:48 +0200 Subject: [PATCH] Align breadcrumbs with frontend and iOS --- .../api/sentry-android-core.api | 2 + .../sentry/android/core/DeviceInfoUtil.java | 11 ++- .../SystemEventsBreadcrumbsIntegration.java | 73 +++++++++++++------ .../SystemEventsBreadcrumbsIntegrationTest.kt | 60 +++++++++++++++ .../replay/capture/BaseCaptureStrategy.kt | 63 ++++++++++++---- sentry/api/sentry.api | 9 +++ .../src/main/java/io/sentry/SentryLevel.java | 2 +- .../io/sentry/rrweb/RRWebBreadcrumbEvent.java | 22 ++++++ .../RRWebBreadcrumbEventSerializationTest.kt | 2 + .../test/resources/json/replay_recording.json | 2 +- .../json/rrweb_breadcrumb_event.json | 1 + 11 files changed, 202 insertions(+), 45 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 8eb017346d..9ca53b233c 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -183,9 +183,11 @@ public final class io/sentry/android/core/CurrentActivityIntegration : android/a public final class io/sentry/android/core/DeviceInfoUtil { public fun (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)V public fun collectDeviceInformation (ZZ)Lio/sentry/protocol/Device; + public static fun getBatteryLevel (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Float; public static fun getInstance (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/DeviceInfoUtil; public fun getOperatingSystem ()Lio/sentry/protocol/OperatingSystem; public fun getSideLoadedInfo ()Lio/sentry/android/core/ContextUtils$SideLoadedInfo; + public static fun isCharging (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Boolean; public static fun resetInstance ()V } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java index 8c5d661524..f1debc5d23 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java @@ -16,6 +16,7 @@ import android.util.DisplayMetrics; import io.sentry.DateUtils; import io.sentry.SentryLevel; +import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.CpuInfoUtils; import io.sentry.android.core.internal.util.DeviceOrientations; import io.sentry.android.core.internal.util.RootChecker; @@ -184,8 +185,8 @@ public ContextUtils.SideLoadedInfo getSideLoadedInfo() { private void setDeviceIO(final @NotNull Device device, final boolean includeDynamicData) { final Intent batteryIntent = getBatteryIntent(); if (batteryIntent != null) { - device.setBatteryLevel(getBatteryLevel(batteryIntent)); - device.setCharging(isCharging(batteryIntent)); + device.setBatteryLevel(getBatteryLevel(batteryIntent, options)); + device.setCharging(isCharging(batteryIntent, options)); device.setBatteryTemperature(getBatteryTemperature(batteryIntent)); } @@ -270,7 +271,8 @@ private Intent getBatteryIntent() { * @return the device's current battery level (as a percentage of total), or null if unknown */ @Nullable - private Float getBatteryLevel(final @NotNull Intent batteryIntent) { + public static Float getBatteryLevel( + final @NotNull Intent batteryIntent, final @NotNull SentryOptions options) { try { int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); @@ -294,7 +296,8 @@ private Float getBatteryLevel(final @NotNull Intent batteryIntent) { * @return whether or not the device is currently plugged in and charging, or null if unknown */ @Nullable - private Boolean isCharging(final @NotNull Intent batteryIntent) { + public static Boolean isCharging( + final @NotNull Intent batteryIntent, final @NotNull SentryOptions options) { try { int plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); return plugged == BatteryManager.BATTERY_PLUGGED_AC diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index 1c22a7dcc8..dcd92e8bf8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -6,6 +6,7 @@ import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE; import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED; import static android.content.Intent.ACTION_APP_ERROR; +import static android.content.Intent.ACTION_BATTERY_CHANGED; import static android.content.Intent.ACTION_BATTERY_LOW; import static android.content.Intent.ACTION_BATTERY_OKAY; import static android.content.Intent.ACTION_BOOT_COMPLETED; @@ -41,10 +42,11 @@ 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.android.core.internal.util.AndroidCurrentDateProvider; +import io.sentry.android.core.internal.util.Debouncer; import io.sentry.util.Objects; import io.sentry.util.StringUtils; import java.io.Closeable; @@ -120,7 +122,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio private void startSystemEventsReceiver( final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) { - receiver = new SystemEventsBroadcastReceiver(hub, options.getLogger()); + receiver = new SystemEventsBroadcastReceiver(hub, options); final IntentFilter filter = new IntentFilter(); for (String item : actions) { filter.addAction(item); @@ -154,6 +156,7 @@ private void startSystemEventsReceiver( actions.add(ACTION_AIRPLANE_MODE_CHANGED); actions.add(ACTION_BATTERY_LOW); actions.add(ACTION_BATTERY_OKAY); + actions.add(ACTION_BATTERY_CHANGED); actions.add(ACTION_BOOT_COMPLETED); actions.add(ACTION_CAMERA_BUTTON); actions.add(ACTION_CONFIGURATION_CHANGED); @@ -204,45 +207,69 @@ public void close() throws IOException { static final class SystemEventsBroadcastReceiver extends BroadcastReceiver { + private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000; private final @NotNull IHub hub; - private final @NotNull ILogger logger; + private final @NotNull SentryAndroidOptions options; + private final @NotNull Debouncer debouncer = + new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0); - SystemEventsBroadcastReceiver(final @NotNull IHub hub, final @NotNull ILogger logger) { + SystemEventsBroadcastReceiver( + final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) { this.hub = hub; - this.logger = logger; + this.options = options; } @Override public void onReceive(Context context, Intent intent) { + final boolean shouldDebounce = debouncer.checkForDebounce(); + final String action = intent.getAction(); + final boolean isBatteryChanged = ACTION_BATTERY_CHANGED.equals(action); + if (isBatteryChanged && shouldDebounce) { + // aligning with iOS which only captures battery status changes every minute at maximum + return; + } + final Breadcrumb breadcrumb = new Breadcrumb(); breadcrumb.setType("system"); breadcrumb.setCategory("device.event"); - final String action = intent.getAction(); String shortAction = StringUtils.getStringAfterDot(action); if (shortAction != null) { breadcrumb.setData("action", shortAction); } - final Bundle extras = intent.getExtras(); - final Map newExtras = new HashMap<>(); - if (extras != null && !extras.isEmpty()) { - for (String item : extras.keySet()) { - try { - @SuppressWarnings("deprecation") - Object value = extras.get(item); - if (value != null) { - newExtras.put(item, value.toString()); + if (isBatteryChanged) { + final Float batteryLevel = DeviceInfoUtil.getBatteryLevel(intent, options); + if (batteryLevel != null) { + breadcrumb.setData("level", batteryLevel); + } + final Boolean isCharging = DeviceInfoUtil.isCharging(intent, options); + if (isCharging != null) { + breadcrumb.setData("charging", isCharging); + } + } else { + final Bundle extras = intent.getExtras(); + final Map newExtras = new HashMap<>(); + if (extras != null && !extras.isEmpty()) { + for (String item : extras.keySet()) { + try { + @SuppressWarnings("deprecation") + Object value = extras.get(item); + if (value != null) { + newExtras.put(item, value.toString()); + } + } catch (Throwable exception) { + options + .getLogger() + .log( + SentryLevel.ERROR, + exception, + "%s key of the %s action threw an error.", + item, + action); } - } catch (Throwable exception) { - logger.log( - SentryLevel.ERROR, - exception, - "%s key of the %s action threw an error.", - item, - action); } + breadcrumb.setData("extras", newExtras); } - breadcrumb.setData("extras", newExtras); } breadcrumb.setLevel(SentryLevel.INFO); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt index f8293f9b87..3dfca15fdb 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt @@ -2,18 +2,22 @@ package io.sentry.android.core import android.content.Context import android.content.Intent +import android.os.BatteryManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Breadcrumb import io.sentry.IHub import io.sentry.ISentryExecutorService import io.sentry.SentryLevel import io.sentry.test.DeferredExecutorService import io.sentry.test.ImmediateExecutorService +import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.check import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals @@ -21,6 +25,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull +@RunWith(AndroidJUnit4::class) class SystemEventsBreadcrumbsIntegrationTest { private class Fixture { @@ -111,6 +116,61 @@ class SystemEventsBreadcrumbsIntegrationTest { ) } + @Test + fun `handles battery changes`() { + val sut = fixture.getSut() + + sut.register(fixture.hub, fixture.options) + val intent = Intent().apply { + action = Intent.ACTION_BATTERY_CHANGED + putExtra(BatteryManager.EXTRA_LEVEL, 75) + putExtra(BatteryManager.EXTRA_SCALE, 100) + putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB) + } + sut.receiver!!.onReceive(fixture.context, intent) + + verify(fixture.hub).addBreadcrumb( + check { + assertEquals("device.event", it.category) + assertEquals("system", it.type) + assertEquals(SentryLevel.INFO, it.level) + assertEquals(it.data["level"], 75f) + assertEquals(it.data["charging"], true) + }, + anyOrNull() + ) + } + + @Test + fun `battery changes are debounced`() { + val sut = fixture.getSut() + + sut.register(fixture.hub, fixture.options) + val intent1 = Intent().apply { + action = Intent.ACTION_BATTERY_CHANGED + putExtra(BatteryManager.EXTRA_LEVEL, 80) + putExtra(BatteryManager.EXTRA_SCALE, 100) + } + val intent2 = Intent().apply { + action = Intent.ACTION_BATTERY_CHANGED + putExtra(BatteryManager.EXTRA_LEVEL, 75) + putExtra(BatteryManager.EXTRA_SCALE, 100) + putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB) + } + sut.receiver!!.onReceive(fixture.context, intent1) + sut.receiver!!.onReceive(fixture.context, intent2) + + // should only add the first crumb + verify(fixture.hub).addBreadcrumb( + check { + assertEquals(it.data["level"], 80f) + assertEquals(it.data["charging"], false) + }, + anyOrNull() + ) + verifyNoMoreInteractions(fixture.hub) + } + @Test fun `Do not crash if registerReceiver throws exception`() { val sut = fixture.getSut() diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt index fad8aaa67a..599d89c093 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt @@ -6,6 +6,7 @@ import io.sentry.DateUtils import io.sentry.Hint import io.sentry.IHub import io.sentry.ReplayRecording +import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.SentryReplayEvent import io.sentry.SentryReplayEvent.ReplayType @@ -201,12 +202,12 @@ internal abstract class BaseCaptureStrategy( hub?.configureScope { scope -> scope.breadcrumbs.forEach { breadcrumb -> - if (breadcrumb.timestamp.after(segmentTimestamp) && - breadcrumb.timestamp.before(endTimestamp) + if (breadcrumb.timestamp.time >= segmentTimestamp.time && + breadcrumb.timestamp.time < endTimestamp.time ) { - // TODO: rework this later when aligned with iOS and frontend var breadcrumbMessage: String? = null - val breadcrumbCategory: String? + var breadcrumbCategory: String? = null + var breadcrumbLevel: SentryLevel? = null val breadcrumbData = mutableMapOf() when { breadcrumb.category == "http" -> { @@ -216,39 +217,68 @@ internal abstract class BaseCaptureStrategy( return@forEach } - breadcrumb.category == "device.orientation" -> { + breadcrumb.type == "navigation" && + breadcrumb.category == "app.lifecycle" -> { + breadcrumbCategory = "app.${breadcrumb.data["state"]}" + } + + breadcrumb.type == "navigation" && + breadcrumb.category == "device.orientation" -> { breadcrumbCategory = breadcrumb.category!! - breadcrumbMessage = breadcrumb.data["position"] as? String ?: "" + val position = breadcrumb.data["position"] + if (position == "landscape" || position == "portrait") { + breadcrumbData["position"] = position + } else { + return@forEach + } } breadcrumb.type == "navigation" -> { breadcrumbCategory = "navigation" breadcrumbData["to"] = when { - breadcrumb.data["state"] == "resumed" -> breadcrumb.data["screen"] as? String - breadcrumb.category == "app.lifecycle" -> breadcrumb.data["state"] as? String + breadcrumb.data["state"] == "resumed" -> (breadcrumb.data["screen"] as? String)?.substringAfterLast('.') "to" in breadcrumb.data -> breadcrumb.data["to"] as? String else -> return@forEach } ?: return@forEach } - breadcrumb.category in setOf("ui.click", "ui.scroll", "ui.swipe") -> { - breadcrumbCategory = breadcrumb.category!! + breadcrumb.category == "ui.click" -> { + breadcrumbCategory = "ui.tap" breadcrumbMessage = ( breadcrumb.data["view.id"] - ?: breadcrumb.data["view.class"] ?: breadcrumb.data["view.tag"] - ) as? String ?: "" + ?: breadcrumb.data["view.class"] + ) as? String ?: return@forEach + breadcrumbData.putAll(breadcrumb.data) } - breadcrumb.type == "system" -> { - breadcrumbCategory = breadcrumb.type!! - breadcrumbMessage = - breadcrumb.data.entries.joinToString() as? String ?: "" + breadcrumb.type == "system" && breadcrumb.category == "network.event" -> { + breadcrumbCategory = "device.connectivity" + breadcrumbData["state"] = when { + breadcrumb.data["action"] == "NETWORK_LOST" -> "offline" + "network_type" in breadcrumb.data -> if (!(breadcrumb.data["network_type"] as? String).isNullOrEmpty()) { + breadcrumb.data["network_type"] + } else { + return@forEach + } + else -> return@forEach + } + } + + breadcrumb.data["action"] == "BATTERY_CHANGED" -> { + breadcrumbCategory = "device.battery" + breadcrumbData.putAll( + breadcrumb.data.filterKeys { + it == "level" || it == "charging" + } + ) } else -> { breadcrumbCategory = breadcrumb.category breadcrumbMessage = breadcrumb.message + breadcrumbLevel = breadcrumb.level + breadcrumbData.putAll(breadcrumb.data) } } if (!breadcrumbCategory.isNullOrEmpty()) { @@ -258,6 +288,7 @@ internal abstract class BaseCaptureStrategy( breadcrumbType = "default" category = breadcrumbCategory message = breadcrumbMessage + level = breadcrumbLevel data = breadcrumbData } } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 68c12600ab..2e73a86168 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2255,6 +2255,12 @@ public final class io/sentry/SentryLevel : java/lang/Enum, io/sentry/JsonSeriali public static fun values ()[Lio/sentry/SentryLevel; } +public final class io/sentry/SentryLevel$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryLevel; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + public final class io/sentry/SentryLockReason : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public static final field ANY I public static final field BLOCKED I @@ -5049,6 +5055,7 @@ public final class io/sentry/rrweb/RRWebBreadcrumbEvent : io/sentry/rrweb/RRWebE public fun getCategory ()Ljava/lang/String; public fun getData ()Ljava/util/Map; public fun getDataUnknown ()Ljava/util/Map; + public fun getLevel ()Lio/sentry/SentryLevel; public fun getMessage ()Ljava/lang/String; public fun getPayloadUnknown ()Ljava/util/Map; public fun getTag ()Ljava/lang/String; @@ -5059,6 +5066,7 @@ public final class io/sentry/rrweb/RRWebBreadcrumbEvent : io/sentry/rrweb/RRWebE public fun setCategory (Ljava/lang/String;)V public fun setData (Ljava/util/Map;)V public fun setDataUnknown (Ljava/util/Map;)V + public fun setLevel (Lio/sentry/SentryLevel;)V public fun setMessage (Ljava/lang/String;)V public fun setPayloadUnknown (Ljava/util/Map;)V public fun setTag (Ljava/lang/String;)V @@ -5074,6 +5082,7 @@ public final class io/sentry/rrweb/RRWebBreadcrumbEvent$Deserializer : io/sentry public final class io/sentry/rrweb/RRWebBreadcrumbEvent$JsonKeys { public static final field CATEGORY Ljava/lang/String; public static final field DATA Ljava/lang/String; + public static final field LEVEL Ljava/lang/String; public static final field MESSAGE Ljava/lang/String; public static final field PAYLOAD Ljava/lang/String; public static final field TIMESTAMP Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/SentryLevel.java b/sentry/src/main/java/io/sentry/SentryLevel.java index f1c6e04cb8..76b07c6b37 100644 --- a/sentry/src/main/java/io/sentry/SentryLevel.java +++ b/sentry/src/main/java/io/sentry/SentryLevel.java @@ -18,7 +18,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger writer.value(name().toLowerCase(Locale.ROOT)); } - static final class Deserializer implements JsonDeserializer { + public static final class Deserializer implements JsonDeserializer { @Override public @NotNull SentryLevel deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebBreadcrumbEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebBreadcrumbEvent.java index d98e91c0e5..6fb269c405 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebBreadcrumbEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebBreadcrumbEvent.java @@ -6,6 +6,7 @@ import io.sentry.JsonUnknown; import io.sentry.ObjectReader; import io.sentry.ObjectWriter; +import io.sentry.SentryLevel; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -25,6 +26,7 @@ public final class RRWebBreadcrumbEvent extends RRWebEvent private @Nullable String breadcrumbType; private @Nullable String category; private @Nullable String message; + private @Nullable SentryLevel level; private @Nullable Map data; // to support unknown json attributes with nesting, we have to have unknown map for each of the // nested object in json: { ..., "data": { ..., "payload": { ... } } } @@ -81,6 +83,15 @@ public void setMessage(final @Nullable String message) { this.message = message; } + @Nullable + public SentryLevel getLevel() { + return level; + } + + public void setLevel(final @Nullable SentryLevel level) { + this.level = level; + } + @Nullable public Map getData() { return data; @@ -126,6 +137,7 @@ public static final class JsonKeys { public static final String TYPE = "type"; public static final String CATEGORY = "category"; public static final String MESSAGE = "message"; + public static final String LEVEL = "level"; } @Override @@ -173,6 +185,9 @@ private void serializePayload(final @NotNull ObjectWriter writer, final @NotNull if (message != null) { writer.name(JsonKeys.MESSAGE).value(message); } + if (level != null) { + writer.name(JsonKeys.LEVEL).value(logger, level); + } if (data != null) { writer.name(JsonKeys.DATA).value(logger, data); } @@ -272,6 +287,13 @@ private void deserializePayload( case JsonKeys.MESSAGE: event.message = reader.nextStringOrNull(); break; + case JsonKeys.LEVEL: + try { + event.level = new SentryLevel.Deserializer().deserialize(reader, logger); + } catch (Exception exception) { + logger.log(SentryLevel.DEBUG, exception, "Error when deserializing SentryLevel"); + } + break; case JsonKeys.DATA: Map deserializedData = CollectionUtils.newConcurrentHashMap( diff --git a/sentry/src/test/java/io/sentry/rrweb/RRWebBreadcrumbEventSerializationTest.kt b/sentry/src/test/java/io/sentry/rrweb/RRWebBreadcrumbEventSerializationTest.kt index cf711fd27f..9dfffef8d2 100644 --- a/sentry/src/test/java/io/sentry/rrweb/RRWebBreadcrumbEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/rrweb/RRWebBreadcrumbEventSerializationTest.kt @@ -1,6 +1,7 @@ package io.sentry.rrweb import io.sentry.ILogger +import io.sentry.SentryLevel.INFO import io.sentry.protocol.SerializationUtils import org.junit.Test import org.mockito.kotlin.mock @@ -16,6 +17,7 @@ class RRWebBreadcrumbEventSerializationTest { breadcrumbTimestamp = 12345678.901 category = "navigation" message = "message" + level = INFO data = mapOf( "screen" to "MainActivity", "state" to "resumed" diff --git a/sentry/src/test/resources/json/replay_recording.json b/sentry/src/test/resources/json/replay_recording.json index fac90d3803..37f2f74330 100644 --- a/sentry/src/test/resources/json/replay_recording.json +++ b/sentry/src/test/resources/json/replay_recording.json @@ -1,2 +1,2 @@ {"segment_id":0} -[{"type":4,"timestamp":1234567890,"data":{"href":"https://sentry.io","height":1920,"width":1080}},{"type":5,"timestamp":12345678901,"data":{"tag":"video","payload":{"segmentId":0,"size":4000000,"duration":5000,"encoding":"h264","container":"mp4","height":1920,"width":1080,"frameCount":5,"frameRate":1,"frameRateType":"constant","left":100,"top":100}}},{"type":5,"timestamp":12345678901,"data":{"tag":"breadcrumb","payload":{"type":"default","timestamp":12345678.901,"category":"navigation","message":"message","data":{"screen":"MainActivity","state":"resumed"}}}},{"type":5,"timestamp":12345678901,"data":{"tag":"performanceSpan","payload":{"op":"resource.http","description":"https://api.github.com/users/getsentry/repos","startTimestamp":12345678.901,"endTimestamp":12345679.901,"data":{"status_code":200,"method":"POST"}}}}] +[{"type":4,"timestamp":1234567890,"data":{"href":"https://sentry.io","height":1920,"width":1080}},{"type":5,"timestamp":12345678901,"data":{"tag":"video","payload":{"segmentId":0,"size":4000000,"duration":5000,"encoding":"h264","container":"mp4","height":1920,"width":1080,"frameCount":5,"frameRate":1,"frameRateType":"constant","left":100,"top":100}}},{"type":5,"timestamp":12345678901,"data":{"tag":"breadcrumb","payload":{"type":"default","timestamp":12345678.901,"category":"navigation","message":"message","level":"info","data":{"screen":"MainActivity","state":"resumed"}}}},{"type":5,"timestamp":12345678901,"data":{"tag":"performanceSpan","payload":{"op":"resource.http","description":"https://api.github.com/users/getsentry/repos","startTimestamp":12345678.901,"endTimestamp":12345679.901,"data":{"status_code":200,"method":"POST"}}}}] diff --git a/sentry/src/test/resources/json/rrweb_breadcrumb_event.json b/sentry/src/test/resources/json/rrweb_breadcrumb_event.json index f41c6cb20d..e1fbe676fa 100644 --- a/sentry/src/test/resources/json/rrweb_breadcrumb_event.json +++ b/sentry/src/test/resources/json/rrweb_breadcrumb_event.json @@ -8,6 +8,7 @@ "timestamp": 12345678.901, "category": "navigation", "message": "message", + "level": "info", "data": { "screen": "MainActivity", "state": "resumed"