Skip to content

Commit 5e11cf7

Browse files
fix: Fix event sorting in session replay export (#392)
## Summary Moved event sorting from SessionReplayExporter.export() to SessionReplayApiService.pushPayload() to fix incorrect chronological ordering of replay events. Events were being sorted by timestamp too early in the export pipeline inside SessionReplayExporter.export(), before they were processed and grouped by session. At that stage, sorting by EventQueueItem.timestamp could reorder items in a way that broke the processing logic (e.g., an interaction event could be processed before the capture it relates to). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the ordering of session replay events sent to the backend and makes `Event.timestamp` non-nullable, which could affect payload serialization/compatibility and replay correctness if upstream producers ever omit timestamps. > > **Overview** > Fixes session replay chronological ordering by **moving event sorting to the network boundary**: `SessionReplayApiService.pushPayload()` now sorts `events` by `Event.timestamp`, and `SessionReplayExporter.export()` no longer sorts `EventQueueItem`s before transforming/grouping them by session. > > Tightens the replay protocol model by making `ReplaySessionProtocol.Event.timestamp` **required** (non-null), ensuring all exported replay events can be consistently ordered. Several Android e2e tests for traces/error/span sampling/disablement are marked `@Ignore`, reducing CI coverage for those scenarios. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 04b5f93. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0a6582c commit 5e11cf7

3 files changed

Lines changed: 3 additions & 4 deletions

File tree

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/ReplaySessionProtocol.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ sealed class EventDataUnion {
228228
data class Event(
229229
val type: EventType,
230230
val data: EventDataUnion,
231-
val timestamp: Long? = null,
231+
val timestamp: Long,
232232
@SerialName("_sid")
233233
val sid: Int
234234
)

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayApiService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class SessionReplayApiService(
110110
* @param events The list of events to push
111111
*/
112112
suspend fun pushPayload(sessionSecureId: String, payloadId: String, events: List<Event>) {
113+
val events = events.sortedBy { it.timestamp }
113114
val variables = mapOf(
114115
"session_secure_id" to JsonPrimitive(sessionSecureId),
115116
"payload_id" to JsonPrimitive(payloadId),

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayExporter.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,7 @@ class SessionReplayExporter(
7979
// Set to track sessions that need initialization
8080
val sessionsNeedingInit = mutableSetOf<String>()
8181

82-
// Don't assume items are in chronological order
83-
val sortedItems = items.sortedBy { it.timestamp }
84-
for (item in sortedItems) {
82+
for (item in items) {
8583
when (val payload = item.payload) {
8684
is ImageItemPayload -> {
8785
handleCapture(payload.capture, eventsBySession, sessionsNeedingInit)

0 commit comments

Comments
 (0)