Skip to content

Commit

Permalink
Merge 4c562fb into c6b16ed
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn authored Apr 22, 2024
2 parents c6b16ed + 4c562fb commit 331bc76
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.android.replay.capture

import io.sentry.Breadcrumb
import io.sentry.DateUtils
import io.sentry.Hint
import io.sentry.IHub
Expand All @@ -16,6 +17,7 @@ import io.sentry.protocol.SentryId
import io.sentry.rrweb.RRWebBreadcrumbEvent
import io.sentry.rrweb.RRWebEvent
import io.sentry.rrweb.RRWebMetaEvent
import io.sentry.rrweb.RRWebSpanEvent
import io.sentry.rrweb.RRWebVideoEvent
import io.sentry.transport.ICurrentDateProvider
import io.sentry.util.FileUtils
Expand All @@ -39,6 +41,14 @@ internal abstract class BaseCaptureStrategy(

internal companion object {
private const val TAG = "CaptureStrategy"
private val supportedNetworkData = setOf(
"status_code",
"method",
"response_content_length",
"request_content_length",
"http.response_content_length",
"http.request_content_length"
)
}

protected var cache: ReplayCache? = null
Expand Down Expand Up @@ -74,7 +84,8 @@ internal abstract class BaseCaptureStrategy(
}
}

cache = replayCacheProvider?.invoke(replayId) ?: ReplayCache(options, replayId, recorderConfig)
cache =
replayCacheProvider?.invoke(replayId) ?: ReplayCache(options, replayId, recorderConfig)

// TODO: replace it with dateProvider.currentTimeMillis to also test it
segmentTimestamp.set(DateUtils.getCurrentDateTime())
Expand Down Expand Up @@ -180,7 +191,32 @@ internal abstract class BaseCaptureStrategy(
val breadcrumbCategory: String?
val breadcrumbData = mutableMapOf<String, Any?>()
when {
breadcrumb.category == "http" -> return@forEach
breadcrumb.category == "http" -> {
if (!breadcrumb.isValidForRRWebSpan()) {
return@forEach
}
recordingPayload += RRWebSpanEvent().apply {
timestamp = breadcrumb.timestamp.time
op = "resource.http"
description = breadcrumb.data["url"] as String
startTimestamp =
(breadcrumb.data["start_timestamp"] as Long) / 1000.0
this.endTimestamp =
(breadcrumb.data["end_timestamp"] as Long) / 1000.0
for ((key, value) in breadcrumb.data) {
if (key in supportedNetworkData) {
breadcrumbData[
key
.replace("content_length", "body_size")
.substringAfter(".")
.snakeToCamelCase()
] = value
}
}
data = breadcrumbData
}
return@forEach
}

breadcrumb.category == "device.orientation" -> {
breadcrumbCategory = breadcrumb.category!!
Expand Down Expand Up @@ -208,7 +244,8 @@ internal abstract class BaseCaptureStrategy(

breadcrumb.type == "system" -> {
breadcrumbCategory = breadcrumb.type!!
breadcrumbMessage = breadcrumb.data.entries.joinToString() as? String ?: ""
breadcrumbMessage =
breadcrumb.data.entries.joinToString() as? String ?: ""
}

else -> {
Expand Down Expand Up @@ -280,4 +317,15 @@ internal abstract class BaseCaptureStrategy(
}
}
}

private fun Breadcrumb.isValidForRRWebSpan(): Boolean {
return !(data["url"] as? String).isNullOrEmpty() &&
"start_timestamp" in data &&
"end_timestamp" in data
}

private fun String.snakeToCamelCase(): String {
val pattern = "_[a-z]".toRegex()
return replace(pattern) { it.value.last().uppercase() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVEN
import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT
import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT
import io.sentry.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT
import io.sentry.transport.CurrentDateProvider
import io.sentry.util.Platform
import io.sentry.util.UrlUtils
import okhttp3.Request
Expand Down Expand Up @@ -58,6 +59,8 @@ internal class SentryOkHttpEvent(private val hub: IHub, private val request: Req
breadcrumb = Breadcrumb.http(url, method)
breadcrumb.setData("host", host)
breadcrumb.setData("path", encodedPath)
// needs this as unix timestamp for rrweb
breadcrumb.setData("start_timestamp", CurrentDateProvider.getInstance().currentTimeMillis)

// We add the same data to the root call span
callRootSpan?.setData("url", url)
Expand Down Expand Up @@ -150,6 +153,8 @@ internal class SentryOkHttpEvent(private val hub: IHub, private val request: Req
hint.set(TypeCheckHint.OKHTTP_REQUEST, request)
response?.let { hint.set(TypeCheckHint.OKHTTP_RESPONSE, it) }

// needs this as unix timestamp for rrweb
breadcrumb.setData("end_timestamp", CurrentDateProvider.getInstance().currentTimeMillis)
// We send the breadcrumb even without spans.
hub.addBreadcrumb(breadcrumb, hint)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import io.sentry.SpanStatus
import io.sentry.TypeCheckHint.OKHTTP_REQUEST
import io.sentry.TypeCheckHint.OKHTTP_RESPONSE
import io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback
import io.sentry.transport.CurrentDateProvider
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
import io.sentry.util.Platform
import io.sentry.util.PropagationTargetsUtils
Expand Down Expand Up @@ -79,6 +80,7 @@ public open class SentryOkHttpInterceptor(
val parentSpan = if (Platform.isAndroid()) hub.transaction else hub.span
span = parentSpan?.startChild("http.client", "$method $url")
}
val startTimestamp = CurrentDateProvider.getInstance().currentTimeMillis

span?.spanContext?.origin = TRACE_ORIGIN

Expand Down Expand Up @@ -137,12 +139,17 @@ public open class SentryOkHttpInterceptor(

// The SentryOkHttpEventListener will send the breadcrumb itself if used for this call
if (!isFromEventListener) {
sendBreadcrumb(request, code, response)
sendBreadcrumb(request, code, response, startTimestamp)
}
}
}

private fun sendBreadcrumb(request: Request, code: Int?, response: Response?) {
private fun sendBreadcrumb(
request: Request,
code: Int?,
response: Response?,
startTimestamp: Long
) {
val breadcrumb = Breadcrumb.http(request.url.toString(), request.method, code)
request.body?.contentLength().ifHasValidLength {
breadcrumb.setData("http.request_content_length", it)
Expand All @@ -156,6 +163,9 @@ public open class SentryOkHttpInterceptor(

hint[OKHTTP_RESPONSE] = it
}
// needs this as unix timestamp for rrweb
breadcrumb.setData("start_timestamp", startTimestamp)
breadcrumb.setData("end_timestamp", CurrentDateProvider.getInstance().currentTimeMillis)

hub.addBreadcrumb(breadcrumb, hint)
}
Expand Down
40 changes: 40 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -5155,6 +5155,46 @@ public final class io/sentry/rrweb/RRWebMetaEvent$JsonKeys {
public fun <init> ()V
}

public final class io/sentry/rrweb/RRWebSpanEvent : io/sentry/rrweb/RRWebEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown {
public static final field EVENT_TAG Ljava/lang/String;
public fun <init> ()V
public fun getData ()Ljava/util/Map;
public fun getDataUnknown ()Ljava/util/Map;
public fun getDescription ()Ljava/lang/String;
public fun getEndTimestamp ()D
public fun getOp ()Ljava/lang/String;
public fun getPayloadUnknown ()Ljava/util/Map;
public fun getStartTimestamp ()D
public fun getTag ()Ljava/lang/String;
public fun getUnknown ()Ljava/util/Map;
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
public fun setData (Ljava/util/Map;)V
public fun setDataUnknown (Ljava/util/Map;)V
public fun setDescription (Ljava/lang/String;)V
public fun setEndTimestamp (D)V
public fun setOp (Ljava/lang/String;)V
public fun setPayloadUnknown (Ljava/util/Map;)V
public fun setStartTimestamp (D)V
public fun setTag (Ljava/lang/String;)V
public fun setUnknown (Ljava/util/Map;)V
}

public final class io/sentry/rrweb/RRWebSpanEvent$Deserializer : io/sentry/JsonDeserializer {
public fun <init> ()V
public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/rrweb/RRWebSpanEvent;
public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
}

public final class io/sentry/rrweb/RRWebSpanEvent$JsonKeys {
public static final field DATA Ljava/lang/String;
public static final field DESCRIPTION Ljava/lang/String;
public static final field END_TIMESTAMP Ljava/lang/String;
public static final field OP Ljava/lang/String;
public static final field PAYLOAD Ljava/lang/String;
public static final field START_TIMESTAMP Ljava/lang/String;
public fun <init> ()V
}

public final class io/sentry/rrweb/RRWebVideoEvent : io/sentry/rrweb/RRWebEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown {
public static final field EVENT_TAG Ljava/lang/String;
public static final field REPLAY_CONTAINER Ljava/lang/String;
Expand Down
42 changes: 29 additions & 13 deletions sentry/src/main/java/io/sentry/ReplayRecording.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.sentry;

import io.sentry.rrweb.RRWebBreadcrumbEvent;
import io.sentry.rrweb.RRWebEvent;
import io.sentry.rrweb.RRWebEventType;
import io.sentry.rrweb.RRWebMetaEvent;
import io.sentry.rrweb.RRWebSpanEvent;
import io.sentry.rrweb.RRWebVideoEvent;
import io.sentry.util.MapObjectReader;
import io.sentry.util.Objects;
Expand Down Expand Up @@ -148,19 +150,33 @@ public static final class Deserializer implements JsonDeserializer<ReplayRecordi
payload.add(metaEvent);
break;
case Custom:
final Map<String, Object> data =
(Map<String, Object>) eventMap.getOrDefault("data", Collections.emptyMap());
final String tag =
(String) data.getOrDefault(RRWebEvent.JsonKeys.TAG, "default");
switch (tag) {
case RRWebVideoEvent.EVENT_TAG:
final RRWebEvent videoEvent =
new RRWebVideoEvent.Deserializer().deserialize(mapReader, logger);
payload.add(videoEvent);
break;
default:
logger.log(SentryLevel.DEBUG, "Unsupported rrweb event type %s", type);
break;
Map<String, Object> data = (Map<String, Object>) eventMap.get("data");
if (data == null) {
data = Collections.emptyMap();
}
final String tag = (String) data.get(RRWebEvent.JsonKeys.TAG);
if (tag != null) {
switch (tag) {
case RRWebVideoEvent.EVENT_TAG:
final RRWebEvent videoEvent =
new RRWebVideoEvent.Deserializer().deserialize(mapReader, logger);
payload.add(videoEvent);
break;
case RRWebBreadcrumbEvent.EVENT_TAG:
final RRWebEvent breadcrumbEvent =
new RRWebBreadcrumbEvent.Deserializer()
.deserialize(mapReader, logger);
payload.add(breadcrumbEvent);
break;
case RRWebSpanEvent.EVENT_TAG:
final RRWebEvent spanEvent =
new RRWebSpanEvent.Deserializer().deserialize(mapReader, logger);
payload.add(spanEvent);
break;
default:
logger.log(SentryLevel.DEBUG, "Unsupported rrweb event type %s", type);
break;
}
}
break;
default:
Expand Down
Loading

0 comments on commit 331bc76

Please sign in to comment.