Skip to content

Commit db66737

Browse files
authored
[SR] Capture breadcrumbs (temporary)
2 parents 12c0eb7 + 4c7d1a0 commit db66737

File tree

7 files changed

+488
-24
lines changed

7 files changed

+488
-24
lines changed

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import io.sentry.android.replay.ScreenshotRecorderConfig
1313
import io.sentry.android.replay.util.gracefullyShutdown
1414
import io.sentry.android.replay.util.submitSafely
1515
import io.sentry.protocol.SentryId
16+
import io.sentry.rrweb.RRWebBreadcrumbEvent
17+
import io.sentry.rrweb.RRWebEvent
1618
import io.sentry.rrweb.RRWebMetaEvent
1719
import io.sentry.rrweb.RRWebVideoEvent
1820
import io.sentry.transport.ICurrentDateProvider
@@ -28,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference
2830

2931
internal abstract class BaseCaptureStrategy(
3032
private val options: SentryOptions,
33+
private val hub: IHub?,
3134
private val dateProvider: ICurrentDateProvider,
3235
protected var recorderConfig: ScreenshotRecorderConfig,
3336
executor: ScheduledExecutorService? = null
@@ -134,41 +137,107 @@ internal abstract class BaseCaptureStrategy(
134137
duration: Long,
135138
replayType: ReplayType
136139
): ReplaySegment {
140+
val endTimestamp = DateUtils.getDateTime(segmentTimestamp.time + duration)
137141
val replay = SentryReplayEvent().apply {
138142
eventId = currentReplayId
139143
replayId = currentReplayId
140144
this.segmentId = segmentId
141-
this.timestamp = DateUtils.getDateTime(segmentTimestamp.time + duration)
145+
this.timestamp = endTimestamp
142146
replayStartTimestamp = segmentTimestamp
143147
this.replayType = replayType
144148
videoFile = video
145149
}
146150

147-
val recording = ReplayRecording().apply {
151+
val recordingPayload = mutableListOf<RRWebEvent>()
152+
recordingPayload += RRWebMetaEvent().apply {
153+
this.timestamp = segmentTimestamp.time
154+
this.height = height
155+
this.width = width
156+
}
157+
recordingPayload += RRWebVideoEvent().apply {
158+
this.timestamp = segmentTimestamp.time
148159
this.segmentId = segmentId
149-
payload = listOf(
150-
RRWebMetaEvent().apply {
151-
this.timestamp = segmentTimestamp.time
152-
this.height = height
153-
this.width = width
154-
},
155-
RRWebVideoEvent().apply {
156-
this.timestamp = segmentTimestamp.time
157-
this.segmentId = segmentId
158-
this.durationMs = duration
159-
this.frameCount = frameCount
160-
size = video.length()
161-
frameRate = recorderConfig.frameRate
162-
this.height = height
163-
this.width = width
164-
// TODO: support non-fullscreen windows later
165-
left = 0
166-
top = 0
160+
this.durationMs = duration
161+
this.frameCount = frameCount
162+
size = video.length()
163+
frameRate = recorderConfig.frameRate
164+
this.height = height
165+
this.width = width
166+
// TODO: support non-fullscreen windows later
167+
left = 0
168+
top = 0
169+
}
170+
171+
hub?.configureScope { scope ->
172+
scope.breadcrumbs.forEach { breadcrumb ->
173+
if (breadcrumb.timestamp.after(segmentTimestamp) &&
174+
breadcrumb.timestamp.before(endTimestamp)
175+
) {
176+
// TODO: rework this later when aligned with iOS and frontend
177+
var breadcrumbMessage: String? = null
178+
val breadcrumbCategory: String?
179+
val breadcrumbData = mutableMapOf<String, Any?>()
180+
when {
181+
breadcrumb.category == "http" -> return@forEach
182+
183+
breadcrumb.category == "device.orientation" -> {
184+
breadcrumbCategory = breadcrumb.category!!
185+
breadcrumbMessage = breadcrumb.data["position"] as? String ?: ""
186+
}
187+
188+
breadcrumb.type == "navigation" -> {
189+
breadcrumbCategory = "navigation"
190+
breadcrumbData["to"] = when {
191+
breadcrumb.data["state"] == "resumed" -> breadcrumb.data["screen"] as? String
192+
breadcrumb.category == "app.lifecycle" -> breadcrumb.data["state"] as? String
193+
"to" in breadcrumb.data -> breadcrumb.data["to"] as? String
194+
else -> return@forEach
195+
} ?: return@forEach
196+
}
197+
198+
breadcrumb.category in setOf("ui.click", "ui.scroll", "ui.swipe") -> {
199+
breadcrumbCategory = breadcrumb.category!!
200+
breadcrumbMessage = (
201+
breadcrumb.data["view.id"]
202+
?: breadcrumb.data["view.class"]
203+
?: breadcrumb.data["view.tag"]
204+
) as? String ?: ""
205+
}
206+
207+
breadcrumb.type == "system" -> {
208+
breadcrumbCategory = breadcrumb.type!!
209+
breadcrumbMessage = breadcrumb.data.entries.joinToString() as? String ?: ""
210+
}
211+
212+
else -> {
213+
breadcrumbCategory = breadcrumb.category
214+
breadcrumbMessage = breadcrumb.message
215+
}
216+
}
217+
if (!breadcrumbCategory.isNullOrEmpty()) {
218+
recordingPayload += RRWebBreadcrumbEvent().apply {
219+
timestamp = breadcrumb.timestamp.time
220+
breadcrumbTimestamp = breadcrumb.timestamp.time / 1000.0
221+
breadcrumbType = "default"
222+
category = breadcrumbCategory
223+
message = breadcrumbMessage
224+
data = breadcrumbData
225+
}
226+
}
167227
}
168-
)
228+
}
169229
}
170230

171-
return ReplaySegment.Created(videoDuration = duration, replay = replay, recording = recording)
231+
val recording = ReplayRecording().apply {
232+
this.segmentId = segmentId
233+
payload = recordingPayload.sortedBy { it.timestamp }
234+
}
235+
236+
return ReplaySegment.Created(
237+
videoDuration = duration,
238+
replay = replay,
239+
recording = recording
240+
)
172241
}
173242

174243
override fun onConfigurationChanged(recorderConfig: ScreenshotRecorderConfig) {

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class BufferCaptureStrategy(
2323
private val dateProvider: ICurrentDateProvider,
2424
recorderConfig: ScreenshotRecorderConfig,
2525
private val random: SecureRandom
26-
) : BaseCaptureStrategy(options, dateProvider, recorderConfig) {
26+
) : BaseCaptureStrategy(options, hub, dateProvider, recorderConfig) {
2727

2828
private val bufferedSegments = mutableListOf<ReplaySegment.Created>()
2929

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/SessionCaptureStrategy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal class SessionCaptureStrategy(
2121
private val dateProvider: ICurrentDateProvider,
2222
recorderConfig: ScreenshotRecorderConfig,
2323
executor: ScheduledExecutorService? = null
24-
) : BaseCaptureStrategy(options, dateProvider, recorderConfig, executor) {
24+
) : BaseCaptureStrategy(options, hub, dateProvider, recorderConfig, executor) {
2525

2626
internal companion object {
2727
private const val TAG = "SessionCaptureStrategy"

sentry/api/sentry.api

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5035,6 +5035,46 @@ public final class io/sentry/protocol/ViewHierarchyNode$JsonKeys {
50355035
public fun <init> ()V
50365036
}
50375037

5038+
public final class io/sentry/rrweb/RRWebBreadcrumbEvent : io/sentry/rrweb/RRWebEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown {
5039+
public static final field EVENT_TAG Ljava/lang/String;
5040+
public fun <init> ()V
5041+
public fun getBreadcrumbTimestamp ()D
5042+
public fun getBreadcrumbType ()Ljava/lang/String;
5043+
public fun getCategory ()Ljava/lang/String;
5044+
public fun getData ()Ljava/util/Map;
5045+
public fun getDataUnknown ()Ljava/util/Map;
5046+
public fun getMessage ()Ljava/lang/String;
5047+
public fun getPayloadUnknown ()Ljava/util/Map;
5048+
public fun getTag ()Ljava/lang/String;
5049+
public fun getUnknown ()Ljava/util/Map;
5050+
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
5051+
public fun setBreadcrumbTimestamp (D)V
5052+
public fun setBreadcrumbType (Ljava/lang/String;)V
5053+
public fun setCategory (Ljava/lang/String;)V
5054+
public fun setData (Ljava/util/Map;)V
5055+
public fun setDataUnknown (Ljava/util/Map;)V
5056+
public fun setMessage (Ljava/lang/String;)V
5057+
public fun setPayloadUnknown (Ljava/util/Map;)V
5058+
public fun setTag (Ljava/lang/String;)V
5059+
public fun setUnknown (Ljava/util/Map;)V
5060+
}
5061+
5062+
public final class io/sentry/rrweb/RRWebBreadcrumbEvent$Deserializer : io/sentry/JsonDeserializer {
5063+
public fun <init> ()V
5064+
public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/rrweb/RRWebBreadcrumbEvent;
5065+
public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
5066+
}
5067+
5068+
public final class io/sentry/rrweb/RRWebBreadcrumbEvent$JsonKeys {
5069+
public static final field CATEGORY Ljava/lang/String;
5070+
public static final field DATA Ljava/lang/String;
5071+
public static final field MESSAGE Ljava/lang/String;
5072+
public static final field PAYLOAD Ljava/lang/String;
5073+
public static final field TIMESTAMP Ljava/lang/String;
5074+
public static final field TYPE Ljava/lang/String;
5075+
public fun <init> ()V
5076+
}
5077+
50385078
public abstract class io/sentry/rrweb/RRWebEvent {
50395079
protected fun <init> ()V
50405080
protected fun <init> (Lio/sentry/rrweb/RRWebEventType;)V

0 commit comments

Comments
 (0)