Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b1ced85
Add sentry replay envelope and event
romtsn Feb 1, 2024
f8419d1
Merge branch 'rz/feat/session-replay-sources' into rz/feat/session-re…
romtsn Feb 13, 2024
a63cac1
WIP
romtsn Feb 15, 2024
fa72057
Add replay envelopes
romtsn Feb 19, 2024
6cfb511
Remove jsonValue
romtsn Feb 19, 2024
0d031d7
Remove
romtsn Feb 19, 2024
07e6b26
Fix json
romtsn Feb 19, 2024
18af924
Finalize replay envelopes
romtsn Feb 20, 2024
64cedfa
Introduce MapObjectReader
romtsn Feb 20, 2024
b8cb924
Add missing test
romtsn Feb 20, 2024
28d341f
Merge branch 'rz/feat/session-replay-envelopes' into rz/feat/session-…
romtsn Feb 20, 2024
1e76fc7
Add test for MapObjectReader
romtsn Feb 22, 2024
13c1971
Add MapObjectWriter change
romtsn Feb 22, 2024
a14e090
Merge branch 'rz/feat/session-replay-envelopes' into rz/feat/session-…
romtsn Feb 22, 2024
86baf7f
Add finals
romtsn Feb 22, 2024
f1ca9f6
Fix test
romtsn Feb 22, 2024
fbbe0d9
Fix test
romtsn Feb 22, 2024
688233f
Merge branch 'rz/feat/session-replay-envelopes' into rz/feat/session-…
romtsn Feb 22, 2024
fd63960
Address review
romtsn Feb 28, 2024
93785cc
Add finals and annotations
romtsn Feb 28, 2024
4db19e0
Merge pull request #3215 from getsentry/rz/feat/session-replay-map-ob…
romtsn Feb 28, 2024
62477b4
Remove public captureReplay method
romtsn Mar 1, 2024
af42fb3
Fix test
romtsn Mar 1, 2024
cd09739
Merge branch 'rz/feat/session-replay-sources' into rz/feat/session-re…
romtsn Mar 1, 2024
4e54c77
api dump
romtsn Mar 1, 2024
fb14ecb
Merge branch 'rz/feat/session-replay' into rz/feat/session-replay-env…
romtsn Mar 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add replay envelopes
  • Loading branch information
romtsn committed Feb 19, 2024
commit fa72057630fd5c81ec7c8c84eef7c442f84e5fc7
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,9 @@ private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) {

@Override
public @NotNull SentryReplayEvent process(
final @NotNull SentryReplayEvent event, final @NotNull Hint hint) {
final @NotNull SentryReplayEvent event, final @NotNull Hint hint) {
final boolean applyScopeData = shouldApplyScopeData(event, hint);
if (applyScopeData) {
// we only set memory data if it's not a hard crash, when it's a hard crash the event is
// enriched on restart, so non static data might be wrong, eg lowMemory or availMem will
// be different if the App. crashes because of OOM.
processNonCachedEvent(event, hint);
}

Expand Down
26 changes: 10 additions & 16 deletions sentry/src/main/java/io/sentry/Hint.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ public final class Hint {

private final @NotNull Map<String, Object> internalStorage = new HashMap<String, Object>();
private final @NotNull List<Attachment> attachments = new ArrayList<>();
private final @NotNull List<ReplayRecording> replayRecordings = new ArrayList<>();
private @Nullable Attachment screenshot = null;
private @Nullable Attachment viewHierarchy = null;

private @Nullable Attachment threadDump = null;
private @Nullable ReplayRecording replayRecording = null;

public static @NotNull Hint withAttachment(@Nullable Attachment attachment) {
@NotNull final Hint hint = new Hint();
Expand Down Expand Up @@ -71,12 +70,6 @@ public synchronized void remove(@NotNull String name) {
internalStorage.remove(name);
}

public void addReplayRecording(final @Nullable ReplayRecording recording) {
if (recording != null) {
replayRecordings.add(recording);
}
}

public void addAttachment(@Nullable Attachment attachment) {
if (attachment != null) {
attachments.add(attachment);
Expand All @@ -93,10 +86,6 @@ public void addAttachments(@Nullable List<Attachment> attachments) {
return new ArrayList<>(attachments);
}

public @NotNull List<ReplayRecording> getReplayRecordings() {
return new ArrayList<>(replayRecordings);
}

public void replaceAttachments(@Nullable List<Attachment> attachments) {
clearAttachments();
addAttachments(attachments);
Expand All @@ -106,10 +95,6 @@ public void clearAttachments() {
attachments.clear();
}

public void clearReplayRecordings() {
replayRecordings.clear();
}

/**
* Clears all attributes added via {@link #set(String, Object)} Note: SDK internal attributes are
* being kept. This is useful to avoid leaking any objects (e.g. Android activities) being
Expand Down Expand Up @@ -151,6 +136,15 @@ public void setThreadDump(final @Nullable Attachment threadDump) {
return threadDump;
}

@Nullable
public ReplayRecording getReplayRecording() {
return replayRecording;
}

public void setReplayRecording(final @Nullable ReplayRecording replayRecording) {
this.replayRecording = replayRecording;
}

private boolean isCastablePrimitive(@Nullable Object hintValue, @NotNull Class<?> clazz) {
Class<?> nonPrimitiveClass = PRIMITIVE_MAPPINGS.get(clazz.getCanonicalName());
return hintValue != null
Expand Down
10 changes: 5 additions & 5 deletions sentry/src/main/java/io/sentry/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -922,14 +922,14 @@ private IScope buildLocalScope(

@Override
public @NotNull SentryId captureReplay(
final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) {
final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) {
SentryId sentryId = SentryId.EMPTY_ID;
if (!isEnabled()) {
options
.getLogger()
.log(
SentryLevel.WARNING,
"Instance is disabled and this 'captureReplay' call is a no-op.");
.getLogger()
.log(
SentryLevel.WARNING,
"Instance is disabled and this 'captureReplay' call is a no-op.");
} else {
try {
StackItem item = stack.peek();
Expand Down
2 changes: 1 addition & 1 deletion sentry/src/main/java/io/sentry/HubAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public void reportFullyDisplayed() {

@Override
public @NotNull SentryId captureReplay(
final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) {
final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) {
return Sentry.getCurrentHub().captureReplay(replay, hint);
}

Expand Down
3 changes: 2 additions & 1 deletion sentry/src/main/java/io/sentry/ISentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ public interface ISentryClient {
return captureException(throwable, scope, null);
}

@NotNull SentryId captureReplayEvent(
@NotNull
SentryId captureReplayEvent(
@NotNull SentryReplayEvent event, @Nullable IScope scope, @Nullable Hint hint);

/**
Expand Down
3 changes: 2 additions & 1 deletion sentry/src/main/java/io/sentry/JsonObjectWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public JsonObjectWriter value(final @Nullable String value) throws IOException {
return this;
}

@Override public ObjectWriter jsonValue(@Nullable String value) throws IOException {
@Override
public ObjectWriter jsonValue(@Nullable String value) throws IOException {
jsonWriter.jsonValue(value);
return this;
}
Expand Down
2 changes: 2 additions & 0 deletions sentry/src/main/java/io/sentry/JsonSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.sentry.protocol.ViewHierarchyNode;
import io.sentry.rrweb.RRWebEventType;
import io.sentry.rrweb.RRWebMetaEvent;
import io.sentry.rrweb.RRWebVideoEvent;
import io.sentry.util.Objects;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
Expand Down Expand Up @@ -93,6 +94,7 @@ public JsonSerializer(@NotNull SentryOptions options) {
deserializersByClass.put(Request.class, new Request.Deserializer());
deserializersByClass.put(RRWebEventType.class, new RRWebEventType.Deserializer());
deserializersByClass.put(RRWebMetaEvent.class, new RRWebMetaEvent.Deserializer());
deserializersByClass.put(RRWebVideoEvent.class, new RRWebVideoEvent.Deserializer());
deserializersByClass.put(SdkInfo.class, new SdkInfo.Deserializer());
deserializersByClass.put(SdkVersion.class, new SdkVersion.Deserializer());
deserializersByClass.put(SentryEnvelopeHeader.class, new SentryEnvelopeHeader.Deserializer());
Expand Down
5 changes: 4 additions & 1 deletion sentry/src/main/java/io/sentry/MainEventProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,16 @@ private void processNonCachedEvent(final @NotNull SentryBaseEvent event) {
@Override
public @NotNull SentryReplayEvent process(@NotNull SentryReplayEvent event, @NotNull Hint hint) {
setCommons(event);
setDebugMeta(event);
// TODO: maybe later it's needed to deobfuscate something (e.g. view hierarchy), for now the
// TODO: protocol does not support it
// setDebugMeta(event);

if (shouldApplyScopeData(event, hint)) {
processNonCachedEvent(event);
}
return event;
}

private void setCommons(final @NotNull SentryBaseEvent event) {
setPlatform(event);
}
Expand Down
4 changes: 2 additions & 2 deletions sentry/src/main/java/io/sentry/NoOpSentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public SentryId captureEnvelope(@NotNull SentryEnvelope envelope, @Nullable Hint
}

@Override
public @NotNull SentryId captureReplayEvent(@NotNull SentryReplayEvent event,
@Nullable IScope scope, @Nullable Hint hint) {
public @NotNull SentryId captureReplayEvent(
@NotNull SentryReplayEvent event, @Nullable IScope scope, @Nullable Hint hint) {
return SentryId.EMPTY_ID;
}

Expand Down
1 change: 1 addition & 0 deletions sentry/src/main/java/io/sentry/ObjectWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface ObjectWriter {
ObjectWriter name(final @NotNull String name) throws IOException;

ObjectWriter value(final @Nullable String value) throws IOException;

ObjectWriter jsonValue(final @Nullable String value) throws IOException;

ObjectWriter nullValue() throws IOException;
Expand Down
9 changes: 0 additions & 9 deletions sentry/src/main/java/io/sentry/ReplayRecording.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,6 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
}
}
writer.endObject();

// session replay recording format
// {"segment_id":0}\n{json-serialized-gzipped-rrweb-protocol}

writer.jsonValue("\n");

if (payload != null) {
writer.value(logger, payload);
}
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ public interface OptionsConfiguration<T extends SentryOptions> {
}

public static void captureReplay(
final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) {
final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) {
getCurrentHub().captureReplay(replay, hint);
}
}
108 changes: 70 additions & 38 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul

@Override
public @NotNull SentryId captureReplayEvent(
@NotNull SentryReplayEvent event, final @Nullable IScope scope, @Nullable Hint hint) {
@NotNull SentryReplayEvent event, final @Nullable IScope scope, @Nullable Hint hint) {
Objects.requireNonNull(event, "SessionReplay is required.");

if (hint == null) {
Expand All @@ -262,8 +262,8 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul
options.getLogger().log(SentryLevel.DEBUG, "Capturing session replay: %s", event.getEventId());

SentryId sentryId = SentryId.EMPTY_ID;
if (event.getReplayId() != null) {
sentryId = event.getReplayId();
if (event.getEventId() != null) {
sentryId = event.getEventId();
}

event = processReplayEvent(event, hint, options.getEventProcessors());
Expand All @@ -274,15 +274,22 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul
}

try {
final SentryEnvelope envelope =
buildEnvelope(event, hint.getReplayRecordings());
@Nullable TraceContext traceContext = null;
if (scope != null) {
final @Nullable ITransaction transaction = scope.getTransaction();
if (transaction != null) {
traceContext = transaction.traceContext();
} else {
final @NotNull PropagationContext propagationContext =
TracingUtils.maybeUpdateBaggage(scope, options);
traceContext = propagationContext.traceContext();
}
}

final SentryEnvelope envelope = buildEnvelope(event, hint.getReplayRecording(), traceContext);

hint.clear();
if (envelope != null) {
transport.send(envelope, hint);
} else {
sentryId = SentryId.EMPTY_ID;
}
transport.send(envelope, hint);
} catch (IOException e) {
options.getLogger().log(SentryLevel.WARNING, e, "Capturing event %s failed.", sentryId);

Expand Down Expand Up @@ -566,38 +573,22 @@ public void captureUserFeedback(final @NotNull UserFeedback userFeedback) {
return new SentryEnvelope(envelopeHeader, envelopeItems);
}

private @Nullable SentryEnvelope buildEnvelope(
final @Nullable SentryReplayEvent event,
final @Nullable List<ReplayRecording> replayRecordings
) {
SentryId sentryId = null;
private @NotNull SentryEnvelope buildEnvelope(
final @NotNull SentryReplayEvent event,
final @Nullable ReplayRecording replayRecording,
final @Nullable TraceContext traceContext) {
final List<SentryEnvelopeItem> envelopeItems = new ArrayList<>();

if (event != null) {
final SentryEnvelopeItem eventItem =
SentryEnvelopeItem.fromEvent(options.getSerializer(), event);
envelopeItems.add(eventItem);
sentryId = event.getEventId();
}

if (replayRecordings != null) {
for (final ReplayRecording replayRecording : replayRecordings) {
final SentryEnvelopeItem replayItem =
SentryEnvelopeItem.fromReplayRecording(
options.getSerializer(), options.getLogger(), replayRecording);
envelopeItems.add(replayItem);
}
}
final SentryEnvelopeItem replayItem =
SentryEnvelopeItem.fromReplay(
options.getSerializer(), options.getLogger(), event, replayRecording);
envelopeItems.add(replayItem);
final SentryId sentryId = event.getEventId();

final SentryEnvelopeHeader envelopeHeader =
new SentryEnvelopeHeader(sentryId, options.getSdkVersion(), traceContext);

if (!envelopeItems.isEmpty()) {
final SentryEnvelopeHeader envelopeHeader =
new SentryEnvelopeHeader(sentryId, options.getSdkVersion());

return new SentryEnvelope(envelopeHeader, envelopeItems);
}

return null;
return new SentryEnvelope(envelopeHeader, envelopeItems);
}

/**
Expand Down Expand Up @@ -921,6 +912,47 @@ public void captureSession(final @NotNull Session session, final @Nullable Hint
return checkIn;
}

private @NotNull SentryReplayEvent applyScope(
final @NotNull SentryReplayEvent replayEvent, final @Nullable IScope scope) {
// no breadcrumbs and extras for replay events
if (scope != null) {
if (replayEvent.getRequest() == null) {
replayEvent.setRequest(scope.getRequest());
}
if (replayEvent.getUser() == null) {
replayEvent.setUser(scope.getUser());
}
if (replayEvent.getTags() == null) {
replayEvent.setTags(new HashMap<>(scope.getTags()));
} else {
for (Map.Entry<String, String> item : scope.getTags().entrySet()) {
if (!replayEvent.getTags().containsKey(item.getKey())) {
replayEvent.getTags().put(item.getKey(), item.getValue());
}
}
}
final Contexts contexts = replayEvent.getContexts();
for (Map.Entry<String, Object> entry : new Contexts(scope.getContexts()).entrySet()) {
if (!contexts.containsKey(entry.getKey())) {
contexts.put(entry.getKey(), entry.getValue());
}
}

// Set trace data from active span to connect replays with transactions
final ISpan span = scope.getSpan();
if (replayEvent.getContexts().getTrace() == null) {
if (span == null) {
replayEvent
.getContexts()
.setTrace(TransactionContext.fromPropagationContext(scope.getPropagationContext()));
} else {
replayEvent.getContexts().setTrace(span.getSpanContext());
}
}
}
return replayEvent;
}

private <T extends SentryBaseEvent> @NotNull T applyScope(
final @NotNull T sentryBaseEvent, final @Nullable IScope scope) {
if (scope != null) {
Expand Down
Loading