From b1ced8534d462686bf27ca0ad2867c65c0c6e29f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Feb 2024 16:47:15 +0100 Subject: [PATCH 01/19] Add sentry replay envelope and event --- .../core/DefaultAndroidEventProcessor.java | 17 + .../src/main/java/io/sentry/DataCategory.java | 1 + .../main/java/io/sentry/EventProcessor.java | 12 + sentry/src/main/java/io/sentry/Hint.java | 15 + sentry/src/main/java/io/sentry/Hub.java | 22 ++ .../src/main/java/io/sentry/HubAdapter.java | 6 + sentry/src/main/java/io/sentry/IHub.java | 3 + .../main/java/io/sentry/ISentryClient.java | 3 + .../java/io/sentry/MainEventProcessor.java | 10 + sentry/src/main/java/io/sentry/NoOpHub.java | 5 + .../main/java/io/sentry/NoOpSentryClient.java | 6 + .../main/java/io/sentry/ReplayRecording.java | 101 ++++++ sentry/src/main/java/io/sentry/Sentry.java | 5 + .../src/main/java/io/sentry/SentryClient.java | 115 +++++++ .../java/io/sentry/SentryEnvelopeItem.java | 83 ++++- .../main/java/io/sentry/SentryItemType.java | 2 + .../java/io/sentry/SentryReplayEvent.java | 290 ++++++++++++++++++ 17 files changed, 694 insertions(+), 2 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/ReplayRecording.java create mode 100644 sentry/src/main/java/io/sentry/SentryReplayEvent.java diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java index 8dcfe196c2..0e1ddc7a3a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java @@ -10,6 +10,7 @@ import io.sentry.SentryBaseEvent; import io.sentry.SentryEvent; import io.sentry.SentryLevel; +import io.sentry.SentryReplayEvent; import io.sentry.android.core.internal.util.AndroidMainThreadChecker; import io.sentry.android.core.performance.AppStartMetrics; import io.sentry.android.core.performance.TimeSpan; @@ -256,4 +257,20 @@ private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) { return transaction; } + + @Override + public @NotNull SentryReplayEvent process( + 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); + } + + setCommons(event, false, applyScopeData); + + return event; + } } diff --git a/sentry/src/main/java/io/sentry/DataCategory.java b/sentry/src/main/java/io/sentry/DataCategory.java index 6b119b43e4..3e54f9da4b 100644 --- a/sentry/src/main/java/io/sentry/DataCategory.java +++ b/sentry/src/main/java/io/sentry/DataCategory.java @@ -13,6 +13,7 @@ public enum DataCategory { Monitor("monitor"), Profile("profile"), Transaction("transaction"), + Replay("replay"), Security("security"), UserReport("user_report"), Unknown("unknown"); diff --git a/sentry/src/main/java/io/sentry/EventProcessor.java b/sentry/src/main/java/io/sentry/EventProcessor.java index ba67508614..9e52408edb 100644 --- a/sentry/src/main/java/io/sentry/EventProcessor.java +++ b/sentry/src/main/java/io/sentry/EventProcessor.java @@ -32,4 +32,16 @@ default SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { default SentryTransaction process(@NotNull SentryTransaction transaction, @NotNull Hint hint) { return transaction; } + + /** + * May mutate or drop a SentryEvent + * + * @param event the SentryEvent + * @param hint the Hint + * @return the event itself, a mutated SentryEvent or null + */ + @Nullable + default SentryReplayEvent process(@NotNull SentryReplayEvent event, @NotNull Hint hint) { + return event; + } } diff --git a/sentry/src/main/java/io/sentry/Hint.java b/sentry/src/main/java/io/sentry/Hint.java index 07dde3cb80..a638d240ab 100644 --- a/sentry/src/main/java/io/sentry/Hint.java +++ b/sentry/src/main/java/io/sentry/Hint.java @@ -27,6 +27,7 @@ public final class Hint { private final @NotNull Map internalStorage = new HashMap(); private final @NotNull List attachments = new ArrayList<>(); + private final @NotNull List replayRecordings = new ArrayList<>(); private @Nullable Attachment screenshot = null; private @Nullable Attachment viewHierarchy = null; @@ -70,6 +71,12 @@ 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); @@ -86,6 +93,10 @@ public void addAttachments(@Nullable List attachments) { return new ArrayList<>(attachments); } + public @NotNull List getReplayRecordings() { + return new ArrayList<>(replayRecordings); + } + public void replaceAttachments(@Nullable List attachments) { clearAttachments(); addAttachments(attachments); @@ -95,6 +106,10 @@ 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 diff --git a/sentry/src/main/java/io/sentry/Hub.java b/sentry/src/main/java/io/sentry/Hub.java index e7942722fc..1d449d401d 100644 --- a/sentry/src/main/java/io/sentry/Hub.java +++ b/sentry/src/main/java/io/sentry/Hub.java @@ -920,6 +920,28 @@ private IScope buildLocalScope( return sentryId; } + @Override + public @NotNull SentryId captureReplay( + 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."); + } else { + try { + StackItem item = stack.peek(); + sentryId = item.getClient().captureReplayEvent(replay, item.getScope(), null); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error while capturing check-in for slug", e); + } + } + this.lastEventId = sentryId; + return sentryId; + } + @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 68a9bdf11d..ecae05cc98 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -262,6 +262,12 @@ public void reportFullyDisplayed() { return Sentry.captureCheckIn(checkIn); } + @Override + public @NotNull SentryId captureReplay( + final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) { + return Sentry.getCurrentHub().captureReplay(replay, hint); + } + @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/IHub.java b/sentry/src/main/java/io/sentry/IHub.java index 01431043f0..f6b2eab81d 100644 --- a/sentry/src/main/java/io/sentry/IHub.java +++ b/sentry/src/main/java/io/sentry/IHub.java @@ -572,6 +572,9 @@ TransactionContext continueTrace( @NotNull SentryId captureCheckIn(final @NotNull CheckIn checkIn); + @NotNull + SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint); + @ApiStatus.Internal @Nullable RateLimiter getRateLimiter(); diff --git a/sentry/src/main/java/io/sentry/ISentryClient.java b/sentry/src/main/java/io/sentry/ISentryClient.java index 15b5f25c4b..5ea2e5f847 100644 --- a/sentry/src/main/java/io/sentry/ISentryClient.java +++ b/sentry/src/main/java/io/sentry/ISentryClient.java @@ -147,6 +147,9 @@ public interface ISentryClient { return captureException(throwable, scope, null); } + @NotNull SentryId captureReplayEvent( + @NotNull SentryReplayEvent event, @Nullable IScope scope, @Nullable Hint hint); + /** * Captures a manually created user feedback and sends it to Sentry. * diff --git a/sentry/src/main/java/io/sentry/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java index abbf21c84e..813d3aaf26 100644 --- a/sentry/src/main/java/io/sentry/MainEventProcessor.java +++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java @@ -149,6 +149,16 @@ private void processNonCachedEvent(final @NotNull SentryBaseEvent event) { return transaction; } + @Override + public @NotNull SentryReplayEvent process(@NotNull SentryReplayEvent event, @NotNull Hint hint) { + setCommons(event); + setDebugMeta(event); + + if (shouldApplyScopeData(event, hint)) { + processNonCachedEvent(event); + } + return event; + } private void setCommons(final @NotNull SentryBaseEvent event) { setPlatform(event); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index d186c69ca2..b238704cc5 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -218,6 +218,11 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } + @Override + public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { + return SentryId.EMPTY_ID; + } + @Override public @Nullable RateLimiter getRateLimiter() { return null; diff --git a/sentry/src/main/java/io/sentry/NoOpSentryClient.java b/sentry/src/main/java/io/sentry/NoOpSentryClient.java index a37d09eb89..757b074f82 100644 --- a/sentry/src/main/java/io/sentry/NoOpSentryClient.java +++ b/sentry/src/main/java/io/sentry/NoOpSentryClient.java @@ -62,6 +62,12 @@ public SentryId captureEnvelope(@NotNull SentryEnvelope envelope, @Nullable Hint return SentryId.EMPTY_ID; } + @Override + public @NotNull SentryId captureReplayEvent(@NotNull SentryReplayEvent event, + @Nullable IScope scope, @Nullable Hint hint) { + return SentryId.EMPTY_ID; + } + @Override public @Nullable RateLimiter getRateLimiter() { return null; diff --git a/sentry/src/main/java/io/sentry/ReplayRecording.java b/sentry/src/main/java/io/sentry/ReplayRecording.java new file mode 100644 index 0000000000..26b4d73370 --- /dev/null +++ b/sentry/src/main/java/io/sentry/ReplayRecording.java @@ -0,0 +1,101 @@ +package io.sentry; + +import io.sentry.vendor.gson.stream.JsonToken; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class ReplayRecording implements JsonUnknown, JsonSerializable { + + public static final class JsonKeys { + public static final String SEGMENT_ID = "segment_id"; + } + + private @Nullable Integer segmentId; + private @Nullable Map unknown; + + // TODO spec it out, good enough for now + private @Nullable List payload; + + @Nullable + public Integer getSegmentId() { + return segmentId; + } + + public void setSegmentId(@Nullable Integer segmentId) { + this.segmentId = segmentId; + } + + @Nullable + public List getPayload() { + return payload; + } + + public void setPayload(@Nullable List payload) { + this.payload = payload; + } + + @Override + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { + writer.beginObject(); + if (segmentId != null) { + writer.name(JsonKeys.SEGMENT_ID).value(segmentId); + } + + if (unknown != null) { + for (String key : unknown.keySet()) { + Object value = unknown.get(key); + writer.name(key).value(logger, value); + } + } + writer.endObject(); + } + + @Override + public @Nullable Map getUnknown() { + return unknown; + } + + @Override + public void setUnknown(@Nullable Map unknown) { + this.unknown = unknown; + } + + public static final class Deserializer implements JsonDeserializer { + + @Override + public @NotNull ReplayRecording deserialize( + @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + + final ReplayRecording replay = new ReplayRecording(); + + @Nullable Map unknown = null; + @Nullable Integer segmentId = null; + + reader.beginObject(); + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.SEGMENT_ID: + segmentId = reader.nextIntegerOrNull(); + break; + default: + if (unknown == null) { + unknown = new HashMap<>(); + } + reader.nextUnknown(logger, unknown, nextName); + break; + } + } + reader.endObject(); + + replay.setSegmentId(segmentId); + replay.setUnknown(unknown); + return replay; + } + } +} diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 0aff89c0d0..392dd0cef2 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1025,4 +1025,9 @@ public interface OptionsConfiguration { public static @NotNull SentryId captureCheckIn(final @NotNull CheckIn checkIn) { return getCurrentHub().captureCheckIn(checkIn); } + + public static void captureReplay( + final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) { + getCurrentHub().captureReplay(replay, hint); + } } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 2973c1f8bc..0b8da468b6 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -246,6 +246,53 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul return sentryId; } + @Override + public @NotNull SentryId captureReplayEvent( + @NotNull SentryReplayEvent event, final @Nullable IScope scope, @Nullable Hint hint) { + Objects.requireNonNull(event, "SessionReplay is required."); + + if (hint == null) { + hint = new Hint(); + } + + if (shouldApplyScopeData(event, hint)) { + applyScope(event, scope); + } + + options.getLogger().log(SentryLevel.DEBUG, "Capturing session replay: %s", event.getEventId()); + + SentryId sentryId = SentryId.EMPTY_ID; + if (event.getReplayId() != null) { + sentryId = event.getReplayId(); + } + + event = processReplayEvent(event, hint, options.getEventProcessors()); + + if (event == null) { + options.getLogger().log(SentryLevel.DEBUG, "Replay was dropped by Event processors."); + return SentryId.EMPTY_ID; + } + + try { + final SentryEnvelope envelope = + buildEnvelope(event, hint.getReplayRecordings()); + + hint.clear(); + if (envelope != null) { + transport.send(envelope, hint); + } else { + sentryId = SentryId.EMPTY_ID; + } + } catch (IOException e) { + options.getLogger().log(SentryLevel.WARNING, e, "Capturing event %s failed.", sentryId); + + // if there was an error capturing the event, we return an emptyId + sentryId = SentryId.EMPTY_ID; + } + + return sentryId; + } + private void addScopeAttachmentsToHint(@Nullable IScope scope, @NotNull Hint hint) { if (scope != null) { hint.addAttachments(scope.getAttachments()); @@ -432,6 +479,40 @@ private SentryTransaction processTransaction( return transaction; } + @Nullable + private SentryReplayEvent processReplayEvent( + @NotNull SentryReplayEvent replayEvent, + final @NotNull Hint hint, + final @NotNull List eventProcessors) { + for (final EventProcessor processor : eventProcessors) { + try { + replayEvent = processor.process(replayEvent, hint); + } catch (Throwable e) { + options + .getLogger() + .log( + SentryLevel.ERROR, + e, + "An exception occurred while processing replay event by processor: %s", + processor.getClass().getName()); + } + + if (replayEvent == null) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Replay event was dropped by a processor: %s", + processor.getClass().getName()); + options + .getClientReportRecorder() + .recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Replay); + break; + } + } + return replayEvent; + } + @Override public void captureUserFeedback(final @NotNull UserFeedback userFeedback) { Objects.requireNonNull(userFeedback, "SentryEvent is required."); @@ -485,6 +566,40 @@ public void captureUserFeedback(final @NotNull UserFeedback userFeedback) { return new SentryEnvelope(envelopeHeader, envelopeItems); } + private @Nullable SentryEnvelope buildEnvelope( + final @Nullable SentryReplayEvent event, + final @Nullable List replayRecordings + ) { + SentryId sentryId = null; + final List 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); + } + } + + + if (!envelopeItems.isEmpty()) { + final SentryEnvelopeHeader envelopeHeader = + new SentryEnvelopeHeader(sentryId, options.getSdkVersion()); + + return new SentryEnvelope(envelopeHeader, envelopeItems); + } + + return null; + } + /** * Updates the session data based on the event, hint and scope data * diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index acd6b36aa9..f583ff0f0a 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -19,6 +19,7 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; +import java.io.StringWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.concurrent.Callable; @@ -102,8 +103,7 @@ public final class SentryEnvelopeItem { } public static @NotNull SentryEnvelopeItem fromEvent( - final @NotNull ISerializer serializer, final @NotNull SentryBaseEvent event) - throws IOException { + final @NotNull ISerializer serializer, final @NotNull SentryBaseEvent event) { Objects.requireNonNull(serializer, "ISerializer is required."); Objects.requireNonNull(event, "SentryEvent is required."); @@ -342,6 +342,85 @@ public ClientReport getClientReport(final @NotNull ISerializer serializer) throw } } + public static SentryEnvelopeItem fromReplayRecording( + final @NotNull ISerializer serializer, + final @NotNull ILogger logger, + final @NotNull ReplayRecording replayRecording) { + + final CachedItem cachedItem = + new CachedItem( + () -> { + try { + try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + final Writer writer = + new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + + // session replay recording format + // {"segment_id":0}\n{json-serialized-gzipped-rrweb-protocol} + + serializer.serialize(replayRecording, writer); + writer.write("\n"); + if (replayRecording.getPayload() != null) { + serializer.serialize(replayRecording.getPayload(), writer); + } + + // final byte[] payload = compressRecordingPayload(serializer, replayRecording); + // stream.write(payload); + + writer.flush(); + stream.flush(); + return stream.toByteArray(); + } + } catch (Throwable t) { + logger.log(SentryLevel.ERROR, "Could not serialize replay recording", t); + return null; + } + }); + + // try { + // final byte[] data = cachedItem.getBytes(); + // final String dataStr = new String(data, UTF_8); + // + // final String[] items = dataStr.split("\n", 2); + // final String header = items[0]; + // final String payload = items[1]; + // + // final ByteArrayInputStream byteArrayInputStream = new + // ByteArrayInputStream(payload.getBytes(UTF_8)); + // final GZIPInputStream inputStream = new GZIPInputStream(byteArrayInputStream); + // + // final ByteArrayOutputStream decodedData = new ByteArrayOutputStream(); + // + // byte[] buf = new byte[4096]; + // int readLen; + // while ((readLen = inputStream.read(buf, 0, buf.length)) != -1) { + // decodedData.write(buf, 0, readLen); + // } + // + // } catch (Exception e) { + // + // } + + final SentryEnvelopeItemHeader itemHeader = + new SentryEnvelopeItemHeader( + SentryItemType.ReplayRecording, () -> cachedItem.getBytes().length, null, null); + + // avoid method refs on Android due to some issues with older AGP setups + // noinspection Convert2MethodRef + SentryEnvelopeItem item = new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes()); + + try { + StringWriter writer = new StringWriter(); + serializer.serialize(item.header, writer); + writer.flush(); + writer.flush(); + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "f", e); + } + return item; + } + + private static class CachedItem { private @Nullable byte[] bytes; private final @Nullable Callable dataFactory; diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index c4535cb6a1..69aa7b7a92 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -32,6 +32,8 @@ public static SentryItemType resolve(Object item) { return Session; } else if (item instanceof ClientReport) { return ClientReport; + } else if (item instanceof SentryReplayEvent) { + return ReplayEvent; } else { return Attachment; } diff --git a/sentry/src/main/java/io/sentry/SentryReplayEvent.java b/sentry/src/main/java/io/sentry/SentryReplayEvent.java new file mode 100644 index 0000000000..8de7599422 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryReplayEvent.java @@ -0,0 +1,290 @@ +package io.sentry; + +import io.sentry.protocol.SentryId; +import io.sentry.vendor.gson.stream.JsonToken; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class SentryReplayEvent extends SentryBaseEvent + implements JsonUnknown, JsonSerializable { + + public static final class JsonKeys { + public static final String TYPE = "type"; + public static final String REPLAY_TYPE = "replay_type"; + public static final String REPLAY_ID = "replay_id"; + public static final String SEGMENT_ID = "segment_id"; + public static final String TIMESTAMP = "timestamp"; + public static final String REPLAY_START_TIMESTAMP = "replay_start_timestamp"; + public static final String URLS = "urls"; + public static final String ERROR_IDS = "error_ids"; + public static final String TRACE_IDS = "trace_ids"; + } + + private @Nullable String type; + private @Nullable String replayType; + private @Nullable SentryId replayId; + private @Nullable Integer segmentId; + private @Nullable Double timestamp; + private @Nullable Double replayStartTimestamp; + private @Nullable List urls; + private @Nullable List errorIds; + private @Nullable List traceIds; + private @Nullable Map unknown; + + public SentryReplayEvent() { + super(); + this.replayId = this.getEventId(); + this.type = "replay_event"; + this.replayType = "session"; + this.errorIds = new ArrayList<>(); + this.traceIds = new ArrayList<>(); + this.urls = new ArrayList<>(); + } + + @Nullable + public String getType() { + return type; + } + + public void setType(final @Nullable String type) { + this.type = type; + } + + @Nullable + public SentryId getReplayId() { + return replayId; + } + + public void setReplayId(final @Nullable SentryId replayId) { + this.replayId = replayId; + } + + @Nullable + public Integer getSegmentId() { + return segmentId; + } + + public void setSegmentId(final @Nullable Integer segmentId) { + this.segmentId = segmentId; + } + + @Nullable + public Double getTimestamp() { + return timestamp; + } + + public void setTimestamp(final @Nullable Double timestamp) { + this.timestamp = timestamp; + } + + @Nullable + public Double getReplayStartTimestamp() { + return replayStartTimestamp; + } + + public void setReplayStartTimestamp(final @Nullable Double replayStartTimestamp) { + this.replayStartTimestamp = replayStartTimestamp; + } + + @Nullable + public List getUrls() { + return urls; + } + + public void setUrls(final @Nullable List urls) { + this.urls = urls; + } + + @Nullable + public List getErrorIds() { + return errorIds; + } + + public void setErrorIds(final @Nullable List errorIds) { + this.errorIds = errorIds; + } + + @Nullable + public List getTraceIds() { + return traceIds; + } + + public void setTraceIds(final @Nullable List traceIds) { + this.traceIds = traceIds; + } + + @Nullable + public String getReplayType() { + return replayType; + } + + public void setReplayType(@Nullable String replayType) { + this.replayType = replayType; + } + + @Override + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { + writer.beginObject(); + if (type != null) { + writer.name(JsonKeys.TYPE).value(type); + } + if (replayType != null) { + writer.name(JsonKeys.REPLAY_TYPE).value(replayType); + } + if (replayId != null) { + writer.name(JsonKeys.REPLAY_ID).value(logger, replayId); + } + if (segmentId != null) { + writer.name(JsonKeys.SEGMENT_ID).value(segmentId); + } + if (timestamp != null) { + writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); + } + if (replayStartTimestamp != null) { + writer.name(JsonKeys.REPLAY_START_TIMESTAMP).value(logger, replayStartTimestamp); + } + if (urls != null) { + writer.name(JsonKeys.URLS).value(logger, urls); + } + if (errorIds != null) { + writer.name(JsonKeys.ERROR_IDS).value(logger, errorIds); + } + if (traceIds != null) { + writer.name(JsonKeys.TRACE_IDS).value(logger, traceIds); + } + + new SentryBaseEvent.Serializer().serialize(this, writer, logger); + + if (unknown != null) { + for (String key : unknown.keySet()) { + Object value = unknown.get(key); + writer.name(key).value(logger, value); + } + } + writer.endObject(); + } + + @Override + public @Nullable Map getUnknown() { + return unknown; + } + + @Override + public void setUnknown(final @Nullable Map unknown) { + this.unknown = unknown; + } + + public static final class Deserializer implements JsonDeserializer { + + @Override + public @NotNull SentryReplayEvent deserialize( + final @NotNull JsonObjectReader reader, final @NotNull ILogger logger) throws Exception { + + SentryBaseEvent.Deserializer baseEventDeserializer = new SentryBaseEvent.Deserializer(); + + final SentryReplayEvent replay = new SentryReplayEvent(); + + @Nullable Map unknown = null; + @Nullable String type = null; + @Nullable String replayType = null; + @Nullable SentryId replayId = null; + @Nullable Integer segmentId = null; + @Nullable Double timestamp = null; + @Nullable Double replayStartTimestamp = null; + @Nullable List urls = null; + @Nullable List errorIds = null; + @Nullable List traceIds = null; + + reader.beginObject(); + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.TYPE: + type = reader.nextStringOrNull(); + break; + case JsonKeys.REPLAY_TYPE: + replayType = reader.nextStringOrNull(); + break; + case JsonKeys.REPLAY_ID: + replayId = reader.nextOrNull(logger, new SentryId.Deserializer()); + break; + case JsonKeys.SEGMENT_ID: + segmentId = reader.nextIntegerOrNull(); + break; + case JsonKeys.TIMESTAMP: + timestamp = nextTimestamp(reader, logger); + break; + case JsonKeys.REPLAY_START_TIMESTAMP: + replayStartTimestamp = nextTimestamp(reader, logger); + break; + case JsonKeys.URLS: + urls = nextStringList(reader); + break; + case JsonKeys.ERROR_IDS: + errorIds = nextStringList(reader); + break; + case JsonKeys.TRACE_IDS: + traceIds = nextStringList(reader); + break; + default: + if (!baseEventDeserializer.deserializeValue(replay, nextName, reader, logger)) { + if (unknown == null) { + unknown = new HashMap<>(); + } + reader.nextUnknown(logger, unknown, nextName); + } + break; + } + } + reader.endObject(); + + replay.setType(type); + replay.setReplayType(replayType); + replay.setReplayId(replayId); + replay.setSegmentId(segmentId); + replay.setTimestamp(timestamp); + replay.setReplayStartTimestamp(replayStartTimestamp); + replay.setUrls(urls); + replay.setErrorIds(errorIds); + replay.setTraceIds(traceIds); + replay.setUnknown(unknown); + return replay; + } + + @Nullable + private static Double nextTimestamp( + final @NotNull JsonObjectReader reader, final @NotNull ILogger logger) throws IOException { + @Nullable Double result; + try { + result = reader.nextDoubleOrNull(); + } catch (NumberFormatException e) { + final Date date = reader.nextDateOrNull(logger); + result = date != null ? DateUtils.dateToSeconds(date) : null; + } + return result; + } + + @Nullable + private static List nextStringList(final @NotNull JsonObjectReader reader) + throws IOException { + @Nullable List result = null; + final @Nullable Object data = reader.nextObjectOrNull(); + if (data instanceof List) { + result = new ArrayList<>(((List) data).size()); + for (Object item : (List) data) { + if (item instanceof String) { + result.add((String) item); + } + } + } + return result; + } + } +} From a63cac1e40df9f7562a3d4acead57a9bf9687438 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 15 Feb 2024 18:00:04 +0100 Subject: [PATCH 02/19] WIP --- sentry/src/main/java/io/sentry/Hub.java | 4 +- .../main/java/io/sentry/JsonObjectWriter.java | 5 + .../main/java/io/sentry/JsonSerializer.java | 4 + .../src/main/java/io/sentry/ObjectWriter.java | 1 + .../main/java/io/sentry/ReplayRecording.java | 18 ++- .../java/io/sentry/SentryEnvelopeItem.java | 38 ----- .../main/java/io/sentry/SentryItemType.java | 3 + .../main/java/io/sentry/rrweb/RRWebEvent.java | 91 ++++++++++++ .../java/io/sentry/rrweb/RRWebEventType.java | 32 +++++ .../java/io/sentry/rrweb/RRWebMetaEvent.java | 133 ++++++++++++++++++ .../java/io/sentry/rrweb/RRWebVideoEvent.java | 46 ++++++ .../java/io/sentry/util/MapObjectWriter.java | 6 + .../ReplayRecordingSerializationTest.kt | 22 +++ .../rrweb/RRWebEventSerializationTest.kt | 78 ++++++++++ .../rrweb/RRWebMetaEventSerializationTest.kt | 42 ++++++ .../src/test/resources/json/rrweb_event.json | 4 + .../test/resources/json/rrweb_meta_event.json | 9 ++ 17 files changed, 491 insertions(+), 45 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java create mode 100644 sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java create mode 100644 sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java create mode 100644 sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java create mode 100644 sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt create mode 100644 sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt create mode 100644 sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt create mode 100644 sentry/src/test/resources/json/rrweb_event.json create mode 100644 sentry/src/test/resources/json/rrweb_meta_event.json diff --git a/sentry/src/main/java/io/sentry/Hub.java b/sentry/src/main/java/io/sentry/Hub.java index 1d449d401d..25778256d6 100644 --- a/sentry/src/main/java/io/sentry/Hub.java +++ b/sentry/src/main/java/io/sentry/Hub.java @@ -933,9 +933,9 @@ private IScope buildLocalScope( } else { try { StackItem item = stack.peek(); - sentryId = item.getClient().captureReplayEvent(replay, item.getScope(), null); + sentryId = item.getClient().captureReplayEvent(replay, item.getScope(), hint); } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error while capturing check-in for slug", e); + options.getLogger().log(SentryLevel.ERROR, "Error while capturing replay", e); } } this.lastEventId = sentryId; diff --git a/sentry/src/main/java/io/sentry/JsonObjectWriter.java b/sentry/src/main/java/io/sentry/JsonObjectWriter.java index b174ddb484..3c0a326a5d 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectWriter.java +++ b/sentry/src/main/java/io/sentry/JsonObjectWriter.java @@ -52,6 +52,11 @@ public JsonObjectWriter value(final @Nullable String value) throws IOException { return this; } + @Override public ObjectWriter jsonValue(@Nullable String value) throws IOException { + jsonWriter.jsonValue(value); + return this; + } + @Override public JsonObjectWriter nullValue() throws IOException { jsonWriter.nullValue(); diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index c22eb095b5..62d6ddcb9d 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -29,6 +29,8 @@ import io.sentry.protocol.User; import io.sentry.protocol.ViewHierarchy; import io.sentry.protocol.ViewHierarchyNode; +import io.sentry.rrweb.RRWebEventType; +import io.sentry.rrweb.RRWebMetaEvent; import io.sentry.util.Objects; import java.io.BufferedOutputStream; import java.io.BufferedWriter; @@ -89,6 +91,8 @@ public JsonSerializer(@NotNull SentryOptions options) { deserializersByClass.put( ProfileMeasurementValue.class, new ProfileMeasurementValue.Deserializer()); deserializersByClass.put(Request.class, new Request.Deserializer()); + deserializersByClass.put(RRWebEventType.class, new RRWebEventType.Deserializer()); + deserializersByClass.put(RRWebMetaEvent.class, new RRWebMetaEvent.Deserializer()); deserializersByClass.put(SdkInfo.class, new SdkInfo.Deserializer()); deserializersByClass.put(SdkVersion.class, new SdkVersion.Deserializer()); deserializersByClass.put(SentryEnvelopeHeader.class, new SentryEnvelopeHeader.Deserializer()); diff --git a/sentry/src/main/java/io/sentry/ObjectWriter.java b/sentry/src/main/java/io/sentry/ObjectWriter.java index ea8d4e83ea..0c424461bd 100644 --- a/sentry/src/main/java/io/sentry/ObjectWriter.java +++ b/sentry/src/main/java/io/sentry/ObjectWriter.java @@ -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; diff --git a/sentry/src/main/java/io/sentry/ReplayRecording.java b/sentry/src/main/java/io/sentry/ReplayRecording.java index 26b4d73370..64b7c31cda 100644 --- a/sentry/src/main/java/io/sentry/ReplayRecording.java +++ b/sentry/src/main/java/io/sentry/ReplayRecording.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.rrweb.RRWebEvent; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.HashMap; @@ -15,11 +16,9 @@ public static final class JsonKeys { } private @Nullable Integer segmentId; + private @Nullable List payload; private @Nullable Map unknown; - // TODO spec it out, good enough for now - private @Nullable List payload; - @Nullable public Integer getSegmentId() { return segmentId; @@ -30,11 +29,11 @@ public void setSegmentId(@Nullable Integer segmentId) { } @Nullable - public List getPayload() { + public List getPayload() { return payload; } - public void setPayload(@Nullable List payload) { + public void setPayload(@Nullable List payload) { this.payload = payload; } @@ -53,6 +52,15 @@ 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 diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index f583ff0f0a..91a80a866e 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -354,21 +354,7 @@ public static SentryEnvelopeItem fromReplayRecording( try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { - - // session replay recording format - // {"segment_id":0}\n{json-serialized-gzipped-rrweb-protocol} - serializer.serialize(replayRecording, writer); - writer.write("\n"); - if (replayRecording.getPayload() != null) { - serializer.serialize(replayRecording.getPayload(), writer); - } - - // final byte[] payload = compressRecordingPayload(serializer, replayRecording); - // stream.write(payload); - - writer.flush(); - stream.flush(); return stream.toByteArray(); } } catch (Throwable t) { @@ -377,30 +363,6 @@ public static SentryEnvelopeItem fromReplayRecording( } }); - // try { - // final byte[] data = cachedItem.getBytes(); - // final String dataStr = new String(data, UTF_8); - // - // final String[] items = dataStr.split("\n", 2); - // final String header = items[0]; - // final String payload = items[1]; - // - // final ByteArrayInputStream byteArrayInputStream = new - // ByteArrayInputStream(payload.getBytes(UTF_8)); - // final GZIPInputStream inputStream = new GZIPInputStream(byteArrayInputStream); - // - // final ByteArrayOutputStream decodedData = new ByteArrayOutputStream(); - // - // byte[] buf = new byte[4096]; - // int readLen; - // while ((readLen = inputStream.read(buf, 0, buf.length)) != -1) { - // decodedData.write(buf, 0, readLen); - // } - // - // } catch (Exception e) { - // - // } - final SentryEnvelopeItemHeader itemHeader = new SentryEnvelopeItemHeader( SentryItemType.ReplayRecording, () -> cachedItem.getBytes().length, null, null); diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index 69aa7b7a92..7cd58a9de2 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -18,6 +18,7 @@ public enum SentryItemType implements JsonSerializable { ClientReport("client_report"), ReplayEvent("replay_event"), ReplayRecording("replay_recording"), + ReplayVideo("replay_video"), CheckIn("check_in"), Unknown("__unknown__"); // DataCategory.Unknown @@ -34,6 +35,8 @@ public static SentryItemType resolve(Object item) { return ClientReport; } else if (item instanceof SentryReplayEvent) { return ReplayEvent; + } else if (item instanceof ReplayRecording) { + return ReplayRecording; } else { return Attachment; } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java new file mode 100644 index 0000000000..ba7f7227ab --- /dev/null +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java @@ -0,0 +1,91 @@ +package io.sentry.rrweb; + +import io.sentry.Breadcrumb; +import io.sentry.ILogger; +import io.sentry.JsonObjectReader; +import io.sentry.JsonSerializable; +import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; +import io.sentry.SentryBaseEvent; +import io.sentry.SentryLongDate; +import io.sentry.protocol.Contexts; +import io.sentry.protocol.DebugMeta; +import io.sentry.protocol.Request; +import io.sentry.protocol.SdkVersion; +import io.sentry.protocol.SentryId; +import io.sentry.protocol.User; +import io.sentry.util.CollectionUtils; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class RRWebEvent { + + private @NotNull RRWebEventType type; + private long timestamp; + + protected RRWebEvent(final @NotNull RRWebEventType type) { + this.type = type; + this.timestamp = System.currentTimeMillis(); + } + + protected RRWebEvent() { + this(RRWebEventType.Custom); + } + + @NotNull + public RRWebEventType getType() { + return type; + } + + public void setType(final @NotNull RRWebEventType type) { + this.type = type; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(final long timestamp) { + this.timestamp = timestamp; + } + + // region json + public static final class JsonKeys { + public static final String TYPE = "type"; + public static final String TIMESTAMP = "timestamp"; + } + + public static final class Serializer { + public void serialize( + @NotNull RRWebEvent baseEvent, @NotNull ObjectWriter writer, @NotNull ILogger logger) + throws IOException { + writer.name(JsonKeys.TYPE).value(logger, baseEvent.type); + writer.name(JsonKeys.TIMESTAMP).value(baseEvent.timestamp); + } + } + + public static final class Deserializer { + @SuppressWarnings("unchecked") + public boolean deserializeValue( + @NotNull RRWebEvent baseEvent, + @NotNull String nextName, + @NotNull JsonObjectReader reader, + @NotNull ILogger logger) + throws Exception { + switch (nextName) { + case JsonKeys.TYPE: + baseEvent.type = + Objects.requireNonNull(reader.nextOrNull(logger, new RRWebEventType.Deserializer())); + return true; + case JsonKeys.TIMESTAMP: + baseEvent.timestamp = reader.nextLong(); + return true; + } + return false; + } + } + // endregion json +} diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java b/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java new file mode 100644 index 0000000000..412ab234ab --- /dev/null +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java @@ -0,0 +1,32 @@ +package io.sentry.rrweb; + +import io.sentry.ILogger; +import io.sentry.JsonDeserializer; +import io.sentry.JsonObjectReader; +import io.sentry.JsonSerializable; +import io.sentry.ObjectWriter; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public enum RRWebEventType implements JsonSerializable { + DomContentLoaded, + Load, + FullSnapshot, + IncrementalSnapshot, + Meta, + Custom, + Plugin; + + @Override public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) + throws IOException { + writer.value(ordinal()); + } + + public static final class Deserializer implements JsonDeserializer { + @Override + public @NotNull RRWebEventType deserialize( + @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + return RRWebEventType.values()[reader.nextInt()]; + } + } +} diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java new file mode 100644 index 0000000000..67f7a82e4d --- /dev/null +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java @@ -0,0 +1,133 @@ +package io.sentry.rrweb; + +import io.sentry.ILogger; +import io.sentry.JsonDeserializer; +import io.sentry.JsonObjectReader; +import io.sentry.JsonSerializable; +import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; +import io.sentry.vendor.gson.stream.JsonToken; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class RRWebMetaEvent extends RRWebEvent implements JsonUnknown, JsonSerializable { + + private @NotNull String href; + private int height; + private int width; + private @Nullable Map unknown; + + public RRWebMetaEvent() { + super(RRWebEventType.Meta); + this.href = ""; + } + + @NotNull + public String getHref() { + return href; + } + + public void setHref(final @NotNull String href) { + this.href = href; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public static final class JsonKeys { + public static final String DATA = "data"; + public static final String HREF = "href"; + public static final String HEIGHT = "height"; + public static final String WIDTH = "width"; + } + + @Override + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) + throws IOException { + writer.name(JsonKeys.DATA); + writer.beginObject(); + writer.name(JsonKeys.HREF).value(href); + writer.name(JsonKeys.HEIGHT).value(height); + writer.name(JsonKeys.WIDTH).value(width); + new RRWebEvent.Serializer().serialize(this, writer, logger); + if (unknown != null) { + for (String key : unknown.keySet()) { + Object value = unknown.get(key); + writer.name(key); + writer.value(logger, value); + } + } + writer.endObject(); + } + + @Override + public @Nullable Map getUnknown() { + return unknown; + } + + @Override + public void setUnknown(final @Nullable Map unknown) { + this.unknown = unknown; + } + + public static final class Deserializer implements JsonDeserializer { + + @SuppressWarnings("unchecked") + @Override + public @NotNull RRWebMetaEvent deserialize( + @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + reader.beginObject(); + RRWebMetaEvent event = new RRWebMetaEvent(); + Map unknown = null; + + RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); + + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.DATA: + break; + case JsonKeys.HREF: + final String href = reader.nextStringOrNull(); + event.href = href == null ? "" : href; + break; + case JsonKeys.HEIGHT: + final Integer height = reader.nextIntegerOrNull(); + event.height = height == null ? 0 : height; + break; + case JsonKeys.WIDTH: + final Integer width = reader.nextIntegerOrNull(); + event.width = width == null ? 0 : width; + break; + default: + if (!baseEventDeserializer.deserializeValue(event, nextName, reader, logger)) { + if (unknown == null) { + unknown = new ConcurrentHashMap<>(); + } + reader.nextUnknown(logger, unknown, nextName); + } + break; + } + } + event.setUnknown(unknown); + reader.endObject(); + return event; + } + } +} diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java new file mode 100644 index 0000000000..17a54320b0 --- /dev/null +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java @@ -0,0 +1,46 @@ +package io.sentry.rrweb; + +import io.sentry.ILogger; +import io.sentry.JsonSerializable; +import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; +import java.io.IOException; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class RRWebVideoEvent extends RRWebEvent implements JsonUnknown, JsonSerializable { + + @Override public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) + throws IOException { + + } + + @Override public @Nullable Map getUnknown() { + return null; + } + + @Override public void setUnknown(@Nullable Map unknown) { + + } + + // rrweb uses camelCase hence the json keys are in camelCase here + public static final class JsonKeys { + public static final String TAG = "tag"; + public static final String PAYLOAD = "payload"; + public static final String SEGMENT_ID = "segmentId"; + public static final String SIZE = "size"; + public static final String DURATION = "duration"; + public static final String ENCODING = "encoding"; + public static final String CONTAINER = "container"; + public static final String HEIGHT = "height"; + public static final String WIDTH = "width"; + public static final String FRAME_COUNT = "frameCount"; + public static final String FRAME_RATE_TYPE = "frameRateType"; + public static final String FRAME_RATE = "frameRate"; + public static final String LEFT = "left"; + public static final String TOP = "top"; + } + + +} diff --git a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java index 26f80eddc2..7d25c7d9dd 100644 --- a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java +++ b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java @@ -151,6 +151,12 @@ public MapObjectWriter value(final @Nullable String value) throws IOException { return this; } + @Override + public ObjectWriter jsonValue(@Nullable String value) throws IOException { + // no-op + return this; + } + @Override public MapObjectWriter nullValue() throws IOException { postValue((Object) null); diff --git a/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt new file mode 100644 index 0000000000..b731cabdb4 --- /dev/null +++ b/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt @@ -0,0 +1,22 @@ +package io.sentry.protocol + +import io.sentry.DateUtils +import io.sentry.ILogger +import io.sentry.ReplayRecording +import io.sentry.SentryEvent +import io.sentry.SentryLevel +import org.mockito.kotlin.mock + +class ReplayRecordingSerializationTest { + class Fixture { + val logger = mock() + + fun getSut() = ReplayRecording().apply { + segmentId = 0 + payload = listOf( + + ) + } + } + private val fixture = Fixture() +} diff --git a/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt b/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt new file mode 100644 index 0000000000..1223075c6b --- /dev/null +++ b/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt @@ -0,0 +1,78 @@ +package io.sentry.rrweb + +import io.sentry.ILogger +import io.sentry.JsonDeserializer +import io.sentry.JsonObjectReader +import io.sentry.JsonSerializable +import io.sentry.ObjectWriter +import io.sentry.protocol.SerializationUtils.deserializeJson +import io.sentry.protocol.SerializationUtils.sanitizedFile +import io.sentry.protocol.SerializationUtils.serializeToString +import io.sentry.rrweb.RRWebEventType.Custom +import io.sentry.vendor.gson.stream.JsonToken +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertEquals + +class RRWebEventSerializationTest { + + /** + * Make subclass, as `RRWebEvent` initializers are protected. + */ + class Sut : RRWebEvent(), JsonSerializable { + override fun serialize(writer: ObjectWriter, logger: ILogger) { + writer.beginObject() + Serializer().serialize(this, writer, logger) + writer.endObject() + } + + class Deserializer : JsonDeserializer { + override fun deserialize(reader: JsonObjectReader, logger: ILogger): Sut { + val sut = Sut() + reader.beginObject() + + val baseEventDeserializer = RRWebEvent.Deserializer() + do { + val nextName = reader.nextName() + baseEventDeserializer.deserializeValue(sut, nextName, reader, logger) + } while (reader.hasNext() && reader.peek() == JsonToken.NAME) + reader.endObject() + return sut + } + } + } + + class Fixture { + val logger = mock() + + fun update(rrWebEvent: RRWebEvent) { + rrWebEvent.apply { + type = Custom + timestamp = 9999999 + } + } + } + private val fixture = Fixture() + + @Test + fun serialize() { + val expected = sanitizedFile("json/rrweb_event.json") + val sut = Sut().apply { fixture.update(this) } + val actual = serializeToString(sut, fixture.logger) + + assertEquals(expected, actual) + } + + @Test + fun deserialize() { + val expectedJson = sanitizedFile("json/rrweb_event.json") + val actual = deserializeJson( + expectedJson, + Sut.Deserializer(), + fixture.logger + ) + val actualJson = serializeToString(actual, fixture.logger) + + assertEquals(expectedJson, actualJson) + } +} diff --git a/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt b/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt new file mode 100644 index 0000000000..3ab8ad03cb --- /dev/null +++ b/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt @@ -0,0 +1,42 @@ +package io.sentry.rrweb + +import io.sentry.ILogger +import io.sentry.protocol.SerializationUtils.deserializeJson +import io.sentry.protocol.SerializationUtils.sanitizedFile +import io.sentry.protocol.SerializationUtils.serializeToString +import io.sentry.rrweb.RRWebEventType.Meta +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertEquals + +class RRWebMetaEventSerializationTest { + + class Fixture { + val logger = mock() + + fun getSut() = RRWebMetaEvent().apply { + href = "https://sentry.io" + width = 1080 + height = 1920 + type = Meta + timestamp = 1234567890 + } + } + + private val fixture = Fixture() + + @Test + fun serialize() { + val expected = sanitizedFile("json/rrweb_meta_event.json") + val actual = serializeToString(fixture.getSut(), fixture.logger) + assertEquals(expected, actual) + } + + @Test + fun deserialize() { + val expectedJson = sanitizedFile("json/rrweb_meta_event.json") + val actual = deserializeJson(expectedJson, RRWebMetaEvent.Deserializer(), fixture.logger) + val actualJson = serializeToString(actual, fixture.logger) + assertEquals(expectedJson, actualJson) + } +} diff --git a/sentry/src/test/resources/json/rrweb_event.json b/sentry/src/test/resources/json/rrweb_event.json new file mode 100644 index 0000000000..d5610238e9 --- /dev/null +++ b/sentry/src/test/resources/json/rrweb_event.json @@ -0,0 +1,4 @@ +{ + "type": 5, + "timestamp": 9999999 +} diff --git a/sentry/src/test/resources/json/rrweb_meta_event.json b/sentry/src/test/resources/json/rrweb_meta_event.json new file mode 100644 index 0000000000..a1d9621f52 --- /dev/null +++ b/sentry/src/test/resources/json/rrweb_meta_event.json @@ -0,0 +1,9 @@ +{ + "type": 4, + "timestamp": 1234567890, + "data": { + "href": "https://sentry.io", + "width": 1080, + "height": 1920 + } +} From fa72057630fd5c81ec7c8c84eef7c442f84e5fc7 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Mon, 19 Feb 2024 14:51:00 +0100 Subject: [PATCH 03/19] Add replay envelopes --- .../core/DefaultAndroidEventProcessor.java | 5 +- sentry/src/main/java/io/sentry/Hint.java | 26 +- sentry/src/main/java/io/sentry/Hub.java | 10 +- .../src/main/java/io/sentry/HubAdapter.java | 2 +- .../main/java/io/sentry/ISentryClient.java | 3 +- .../main/java/io/sentry/JsonObjectWriter.java | 3 +- .../main/java/io/sentry/JsonSerializer.java | 2 + .../java/io/sentry/MainEventProcessor.java | 5 +- .../main/java/io/sentry/NoOpSentryClient.java | 4 +- .../src/main/java/io/sentry/ObjectWriter.java | 1 + .../main/java/io/sentry/ReplayRecording.java | 9 - sentry/src/main/java/io/sentry/Sentry.java | 2 +- .../src/main/java/io/sentry/SentryClient.java | 108 ++++-- .../java/io/sentry/SentryEnvelopeItem.java | 127 +++++-- .../main/java/io/sentry/SentryItemType.java | 2 - .../java/io/sentry/SentryReplayEvent.java | 192 +++++----- .../main/java/io/sentry/rrweb/RRWebEvent.java | 32 +- .../java/io/sentry/rrweb/RRWebEventType.java | 6 +- .../java/io/sentry/rrweb/RRWebMetaEvent.java | 47 ++- .../java/io/sentry/rrweb/RRWebVideoEvent.java | 329 +++++++++++++++++- .../ReplayRecordingSerializationTest.kt | 41 ++- .../SentryReplayEventSerializationTest.kt | 58 +++ .../rrweb/RRWebMetaEventSerializationTest.kt | 2 +- .../rrweb/RRWebVideoEventSerializationTest.kt | 47 +++ .../test/resources/json/replay_recording.json | 3 + .../json/replay_recording_payload.json | 32 ++ .../test/resources/json/rrweb_meta_event.json | 4 +- .../resources/json/rrweb_video_event.json | 21 ++ .../resources/json/sentry_replay_event.json | 258 ++++++++++++++ 29 files changed, 1127 insertions(+), 254 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt create mode 100644 sentry/src/test/java/io/sentry/rrweb/RRWebVideoEventSerializationTest.kt create mode 100644 sentry/src/test/resources/json/replay_recording.json create mode 100644 sentry/src/test/resources/json/replay_recording_payload.json create mode 100644 sentry/src/test/resources/json/rrweb_video_event.json create mode 100644 sentry/src/test/resources/json/sentry_replay_event.json diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java index 0e1ddc7a3a..0fde2c33db 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java @@ -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); } diff --git a/sentry/src/main/java/io/sentry/Hint.java b/sentry/src/main/java/io/sentry/Hint.java index a638d240ab..750017d00d 100644 --- a/sentry/src/main/java/io/sentry/Hint.java +++ b/sentry/src/main/java/io/sentry/Hint.java @@ -27,11 +27,10 @@ public final class Hint { private final @NotNull Map internalStorage = new HashMap(); private final @NotNull List attachments = new ArrayList<>(); - private final @NotNull List 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(); @@ -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); @@ -93,10 +86,6 @@ public void addAttachments(@Nullable List attachments) { return new ArrayList<>(attachments); } - public @NotNull List getReplayRecordings() { - return new ArrayList<>(replayRecordings); - } - public void replaceAttachments(@Nullable List attachments) { clearAttachments(); addAttachments(attachments); @@ -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 @@ -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 diff --git a/sentry/src/main/java/io/sentry/Hub.java b/sentry/src/main/java/io/sentry/Hub.java index 25778256d6..b91b551705 100644 --- a/sentry/src/main/java/io/sentry/Hub.java +++ b/sentry/src/main/java/io/sentry/Hub.java @@ -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(); diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index ecae05cc98..5d7796f164 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -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); } diff --git a/sentry/src/main/java/io/sentry/ISentryClient.java b/sentry/src/main/java/io/sentry/ISentryClient.java index 5ea2e5f847..e568746e9a 100644 --- a/sentry/src/main/java/io/sentry/ISentryClient.java +++ b/sentry/src/main/java/io/sentry/ISentryClient.java @@ -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); /** diff --git a/sentry/src/main/java/io/sentry/JsonObjectWriter.java b/sentry/src/main/java/io/sentry/JsonObjectWriter.java index 3c0a326a5d..ff5114606c 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectWriter.java +++ b/sentry/src/main/java/io/sentry/JsonObjectWriter.java @@ -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; } diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index 62d6ddcb9d..95c0c538ac 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -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; @@ -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()); diff --git a/sentry/src/main/java/io/sentry/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java index 813d3aaf26..e79ebc37bd 100644 --- a/sentry/src/main/java/io/sentry/MainEventProcessor.java +++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java @@ -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); } diff --git a/sentry/src/main/java/io/sentry/NoOpSentryClient.java b/sentry/src/main/java/io/sentry/NoOpSentryClient.java index 757b074f82..24b43f2b84 100644 --- a/sentry/src/main/java/io/sentry/NoOpSentryClient.java +++ b/sentry/src/main/java/io/sentry/NoOpSentryClient.java @@ -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; } diff --git a/sentry/src/main/java/io/sentry/ObjectWriter.java b/sentry/src/main/java/io/sentry/ObjectWriter.java index 0c424461bd..a5b8d12a4e 100644 --- a/sentry/src/main/java/io/sentry/ObjectWriter.java +++ b/sentry/src/main/java/io/sentry/ObjectWriter.java @@ -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; diff --git a/sentry/src/main/java/io/sentry/ReplayRecording.java b/sentry/src/main/java/io/sentry/ReplayRecording.java index 64b7c31cda..c110c00700 100644 --- a/sentry/src/main/java/io/sentry/ReplayRecording.java +++ b/sentry/src/main/java/io/sentry/ReplayRecording.java @@ -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 diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 392dd0cef2..6058199d62 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1027,7 +1027,7 @@ public interface OptionsConfiguration { } public static void captureReplay( - final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) { + final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) { getCurrentHub().captureReplay(replay, hint); } } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 0b8da468b6..129d415a8a 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -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) { @@ -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()); @@ -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); @@ -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 replayRecordings - ) { - SentryId sentryId = null; + private @NotNull SentryEnvelope buildEnvelope( + final @NotNull SentryReplayEvent event, + final @Nullable ReplayRecording replayRecording, + final @Nullable TraceContext traceContext) { final List 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); } /** @@ -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 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 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 @NotNull T applyScope( final @NotNull T sentryBaseEvent, final @Nullable IScope scope) { if (scope != null) { diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index 91a80a866e..53b2d24ea5 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -19,9 +19,11 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; -import java.io.StringWriter; import java.io.Writer; +import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Callable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -342,47 +344,70 @@ public ClientReport getClientReport(final @NotNull ISerializer serializer) throw } } - public static SentryEnvelopeItem fromReplayRecording( - final @NotNull ISerializer serializer, - final @NotNull ILogger logger, - final @NotNull ReplayRecording replayRecording) { + public static SentryEnvelopeItem fromReplay( + final @NotNull ISerializer serializer, + final @NotNull ILogger logger, + final @NotNull SentryReplayEvent replayEvent, + final @Nullable ReplayRecording replayRecording) { + + final File replayVideo = replayEvent.getVideoFile(); final CachedItem cachedItem = - new CachedItem( - () -> { - try { - try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = - new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { - serializer.serialize(replayRecording, writer); - return stream.toByteArray(); - } - } catch (Throwable t) { - logger.log(SentryLevel.ERROR, "Could not serialize replay recording", t); - return null; - } - }); + new CachedItem( + () -> { + try { + try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + final Writer writer = + new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Map replayPayload = new HashMap<>(); + // first serialize replay event json bytes + serializer.serialize(replayEvent, writer); + replayPayload.put(SentryItemType.ReplayEvent.getItemType(), stream.toByteArray()); + stream.reset(); + + // next serialize replay recording in the following format: + // {"segment_id":0}\n{json-serialized-rrweb-protocol} + if (replayRecording != null) { + serializer.serialize(replayRecording, writer); + writer.write("\n"); + writer.flush(); + if (replayRecording.getPayload() != null) { + serializer.serialize(replayRecording.getPayload(), writer); + } + replayPayload.put( + SentryItemType.ReplayRecording.getItemType(), stream.toByteArray()); + stream.reset(); + } + + // next serialize replay video bytes from given file + if (replayVideo.exists()) { + final byte[] videoBytes = + readBytesFromFile( + replayVideo.getPath(), SentryReplayEvent.REPLAY_VIDEO_MAX_SIZE); + if (videoBytes.length > 0) { + replayPayload.put(SentryItemType.ReplayVideo.getItemType(), videoBytes); + } + } + + return serializeToMsgpack(replayPayload); + } + } catch (Throwable t) { + logger.log(SentryLevel.ERROR, "Could not serialize replay recording", t); + return null; + } finally { + replayVideo.delete(); + } + }); final SentryEnvelopeItemHeader itemHeader = - new SentryEnvelopeItemHeader( - SentryItemType.ReplayRecording, () -> cachedItem.getBytes().length, null, null); + new SentryEnvelopeItemHeader( + SentryItemType.ReplayVideo, () -> cachedItem.getBytes().length, null, null); // avoid method refs on Android due to some issues with older AGP setups // noinspection Convert2MethodRef - SentryEnvelopeItem item = new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes()); - - try { - StringWriter writer = new StringWriter(); - serializer.serialize(item.header, writer); - writer.flush(); - writer.flush(); - } catch (Exception e) { - logger.log(SentryLevel.ERROR, "f", e); - } - return item; + return new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes()); } - private static class CachedItem { private @Nullable byte[] bytes; private final @Nullable Callable dataFactory; @@ -402,4 +427,40 @@ public CachedItem(final @Nullable Callable dataFactory) { return bytes != null ? bytes : new byte[] {}; } } + + @SuppressWarnings("CharsetObjectCanBeUsed") + private static byte[] serializeToMsgpack(Map map) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write map header + baos.write((byte) (0x80 | map.size())); + + // Iterate over the map and serialize each key-value pair + for (Map.Entry entry : map.entrySet()) { + // Pack the key as a string + byte[] keyBytes = entry.getKey().getBytes(Charset.forName("UTF-8")); + int keyLength = keyBytes.length; + if (keyLength <= 31) { + baos.write((byte) (0xA0 | keyLength)); + } else { + baos.write((byte) (0xD9)); + baos.write((byte) (keyLength)); + } + baos.write(keyBytes); + + // Pack the value as a binary string + byte[] valueBytes = entry.getValue(); + int valueLength = valueBytes.length; + if (valueLength <= 255) { + baos.write((byte) (0xC4)); + baos.write((byte) (valueLength)); + } else { + baos.write((byte) (0xC5)); + baos.write(ByteBuffer.allocate(4).putInt(valueLength).array()); + } + baos.write(valueBytes); + } + + return baos.toByteArray(); + } } diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index 7cd58a9de2..79deda34c0 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -35,8 +35,6 @@ public static SentryItemType resolve(Object item) { return ClientReport; } else if (item instanceof SentryReplayEvent) { return ReplayEvent; - } else if (item instanceof ReplayRecording) { - return ReplayRecording; } else { return Attachment; } diff --git a/sentry/src/main/java/io/sentry/SentryReplayEvent.java b/sentry/src/main/java/io/sentry/SentryReplayEvent.java index 8de7599422..623986d868 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayEvent.java +++ b/sentry/src/main/java/io/sentry/SentryReplayEvent.java @@ -2,36 +2,50 @@ import io.sentry.protocol.SentryId; import io.sentry.vendor.gson.stream.JsonToken; +import java.io.File; import java.io.IOException; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public final class SentryReplayEvent extends SentryBaseEvent - implements JsonUnknown, JsonSerializable { + implements JsonUnknown, JsonSerializable { - public static final class JsonKeys { - public static final String TYPE = "type"; - public static final String REPLAY_TYPE = "replay_type"; - public static final String REPLAY_ID = "replay_id"; - public static final String SEGMENT_ID = "segment_id"; - public static final String TIMESTAMP = "timestamp"; - public static final String REPLAY_START_TIMESTAMP = "replay_start_timestamp"; - public static final String URLS = "urls"; - public static final String ERROR_IDS = "error_ids"; - public static final String TRACE_IDS = "trace_ids"; + public enum ReplayType implements JsonSerializable { + SESSION, + BUFFER; + + @Override + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) + throws IOException { + writer.value(name().toLowerCase(Locale.ROOT)); + } + + public static final class Deserializer implements JsonDeserializer { + @Override + public @NotNull ReplayType deserialize( + @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + return ReplayType.valueOf(reader.nextString().toUpperCase(Locale.ROOT)); + } + } } - private @Nullable String type; - private @Nullable String replayType; + public static final long REPLAY_VIDEO_MAX_SIZE = 10 * 1024 * 1024; + public static final String REPLAY_EVENT_TYPE = "replay_event"; + + private @NotNull File videoFile; + private @NotNull String type; + private @NotNull ReplayType replayType; private @Nullable SentryId replayId; - private @Nullable Integer segmentId; - private @Nullable Double timestamp; - private @Nullable Double replayStartTimestamp; + private int segmentId; + private @NotNull Date timestamp; + private @Nullable Date replayStartTimestamp; private @Nullable List urls; private @Nullable List errorIds; private @Nullable List traceIds; @@ -39,20 +53,30 @@ public static final class JsonKeys { public SentryReplayEvent() { super(); - this.replayId = this.getEventId(); - this.type = "replay_event"; - this.replayType = "session"; + this.replayId = new SentryId(); + this.type = REPLAY_EVENT_TYPE; + this.replayType = ReplayType.SESSION; this.errorIds = new ArrayList<>(); this.traceIds = new ArrayList<>(); this.urls = new ArrayList<>(); + timestamp = DateUtils.getCurrentDateTime(); } - @Nullable + @NotNull + public File getVideoFile() { + return videoFile; + } + + public void setVideoFile(final @NotNull File videoFile) { + this.videoFile = videoFile; + } + + @NotNull public String getType() { return type; } - public void setType(final @Nullable String type) { + public void setType(final @NotNull String type) { this.type = type; } @@ -65,30 +89,29 @@ public void setReplayId(final @Nullable SentryId replayId) { this.replayId = replayId; } - @Nullable - public Integer getSegmentId() { + public int getSegmentId() { return segmentId; } - public void setSegmentId(final @Nullable Integer segmentId) { + public void setSegmentId(final int segmentId) { this.segmentId = segmentId; } - @Nullable - public Double getTimestamp() { + @NotNull + public Date getTimestamp() { return timestamp; } - public void setTimestamp(final @Nullable Double timestamp) { + public void setTimestamp(final @NotNull Date timestamp) { this.timestamp = timestamp; } @Nullable - public Double getReplayStartTimestamp() { + public Date getReplayStartTimestamp() { return replayStartTimestamp; } - public void setReplayStartTimestamp(final @Nullable Double replayStartTimestamp) { + public void setReplayStartTimestamp(final @Nullable Date replayStartTimestamp) { this.replayStartTimestamp = replayStartTimestamp; } @@ -119,36 +142,46 @@ public void setTraceIds(final @Nullable List traceIds) { this.traceIds = traceIds; } - @Nullable - public String getReplayType() { + @NotNull + public ReplayType getReplayType() { return replayType; } - public void setReplayType(@Nullable String replayType) { + public void setReplayType(final @NotNull ReplayType replayType) { this.replayType = replayType; } + // region json + public static final class JsonKeys { + public static final String TYPE = "type"; + public static final String REPLAY_TYPE = "replay_type"; + public static final String REPLAY_ID = "replay_id"; + public static final String SEGMENT_ID = "segment_id"; + public static final String TIMESTAMP = "timestamp"; + public static final String REPLAY_START_TIMESTAMP = "replay_start_timestamp"; + public static final String URLS = "urls"; + public static final String ERROR_IDS = "error_ids"; + public static final String TRACE_IDS = "trace_ids"; + } + @Override + @SuppressWarnings("JdkObsolete") public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) - throws IOException { + throws IOException { writer.beginObject(); - if (type != null) { - writer.name(JsonKeys.TYPE).value(type); - } - if (replayType != null) { - writer.name(JsonKeys.REPLAY_TYPE).value(replayType); - } + writer.name(JsonKeys.TYPE).value(type); + writer.name(JsonKeys.REPLAY_TYPE).value(logger, replayType); + writer.name(JsonKeys.SEGMENT_ID).value(segmentId); + writer + .name(JsonKeys.TIMESTAMP) + .value(logger, BigDecimal.valueOf(DateUtils.dateToSeconds(timestamp))); if (replayId != null) { writer.name(JsonKeys.REPLAY_ID).value(logger, replayId); } - if (segmentId != null) { - writer.name(JsonKeys.SEGMENT_ID).value(segmentId); - } - if (timestamp != null) { - writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); - } if (replayStartTimestamp != null) { - writer.name(JsonKeys.REPLAY_START_TIMESTAMP).value(logger, replayStartTimestamp); + writer + .name(JsonKeys.REPLAY_START_TIMESTAMP) + .value(logger, BigDecimal.valueOf(DateUtils.dateToSeconds(replayStartTimestamp))); } if (urls != null) { writer.name(JsonKeys.URLS).value(logger, urls); @@ -183,9 +216,10 @@ public void setUnknown(final @Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { + @SuppressWarnings("unchecked") @Override public @NotNull SentryReplayEvent deserialize( - final @NotNull JsonObjectReader reader, final @NotNull ILogger logger) throws Exception { + final @NotNull JsonObjectReader reader, final @NotNull ILogger logger) throws Exception { SentryBaseEvent.Deserializer baseEventDeserializer = new SentryBaseEvent.Deserializer(); @@ -193,11 +227,11 @@ public static final class Deserializer implements JsonDeserializer unknown = null; @Nullable String type = null; - @Nullable String replayType = null; + @Nullable ReplayType replayType = null; @Nullable SentryId replayId = null; @Nullable Integer segmentId = null; - @Nullable Double timestamp = null; - @Nullable Double replayStartTimestamp = null; + @Nullable Date timestamp = null; + @Nullable Date replayStartTimestamp = null; @Nullable List urls = null; @Nullable List errorIds = null; @Nullable List traceIds = null; @@ -210,7 +244,7 @@ public static final class Deserializer implements JsonDeserializer) reader.nextObjectOrNull(); break; case JsonKeys.ERROR_IDS: - errorIds = nextStringList(reader); + errorIds = (List) reader.nextObjectOrNull(); break; case JsonKeys.TRACE_IDS: - traceIds = nextStringList(reader); + traceIds = (List) reader.nextObjectOrNull(); break; default: if (!baseEventDeserializer.deserializeValue(replay, nextName, reader, logger)) { @@ -245,11 +279,19 @@ public static final class Deserializer implements JsonDeserializer nextStringList(final @NotNull JsonObjectReader reader) - throws IOException { - @Nullable List result = null; - final @Nullable Object data = reader.nextObjectOrNull(); - if (data instanceof List) { - result = new ArrayList<>(((List) data).size()); - for (Object item : (List) data) { - if (item instanceof String) { - result.add((String) item); - } - } - } - return result; - } } + // endregion json } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java index ba7f7227ab..999149331c 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebEvent.java @@ -1,25 +1,11 @@ package io.sentry.rrweb; -import io.sentry.Breadcrumb; import io.sentry.ILogger; import io.sentry.JsonObjectReader; -import io.sentry.JsonSerializable; -import io.sentry.JsonUnknown; import io.sentry.ObjectWriter; -import io.sentry.SentryBaseEvent; -import io.sentry.SentryLongDate; -import io.sentry.protocol.Contexts; -import io.sentry.protocol.DebugMeta; -import io.sentry.protocol.Request; -import io.sentry.protocol.SdkVersion; -import io.sentry.protocol.SentryId; -import io.sentry.protocol.User; -import io.sentry.util.CollectionUtils; import java.io.IOException; -import java.util.Map; import java.util.Objects; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public abstract class RRWebEvent { @@ -32,7 +18,7 @@ protected RRWebEvent(final @NotNull RRWebEventType type) { } protected RRWebEvent() { - this(RRWebEventType.Custom); + this(RRWebEventType.Custom); } @NotNull @@ -60,8 +46,8 @@ public static final class JsonKeys { public static final class Serializer { public void serialize( - @NotNull RRWebEvent baseEvent, @NotNull ObjectWriter writer, @NotNull ILogger logger) - throws IOException { + @NotNull RRWebEvent baseEvent, @NotNull ObjectWriter writer, @NotNull ILogger logger) + throws IOException { writer.name(JsonKeys.TYPE).value(logger, baseEvent.type); writer.name(JsonKeys.TIMESTAMP).value(baseEvent.timestamp); } @@ -70,15 +56,15 @@ public void serialize( public static final class Deserializer { @SuppressWarnings("unchecked") public boolean deserializeValue( - @NotNull RRWebEvent baseEvent, - @NotNull String nextName, - @NotNull JsonObjectReader reader, - @NotNull ILogger logger) - throws Exception { + @NotNull RRWebEvent baseEvent, + @NotNull String nextName, + @NotNull JsonObjectReader reader, + @NotNull ILogger logger) + throws Exception { switch (nextName) { case JsonKeys.TYPE: baseEvent.type = - Objects.requireNonNull(reader.nextOrNull(logger, new RRWebEventType.Deserializer())); + Objects.requireNonNull(reader.nextOrNull(logger, new RRWebEventType.Deserializer())); return true; case JsonKeys.TIMESTAMP: baseEvent.timestamp = reader.nextLong(); diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java b/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java index 412ab234ab..a81e5f30e8 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebEventType.java @@ -17,15 +17,15 @@ public enum RRWebEventType implements JsonSerializable { Custom, Plugin; - @Override public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) - throws IOException { + @Override + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(ordinal()); } public static final class Deserializer implements JsonDeserializer { @Override public @NotNull RRWebEventType deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { return RRWebEventType.values()[reader.nextInt()]; } } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java index 67f7a82e4d..d64400e323 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java @@ -58,14 +58,20 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { + writer.beginObject(); + new RRWebEvent.Serializer().serialize(this, writer, logger); writer.name(JsonKeys.DATA); + serializeData(writer, logger); + writer.endObject(); + } + + private void serializeData(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { writer.beginObject(); writer.name(JsonKeys.HREF).value(href); writer.name(JsonKeys.HEIGHT).value(height); writer.name(JsonKeys.WIDTH).value(width); - new RRWebEvent.Serializer().serialize(this, writer, logger); if (unknown != null) { for (String key : unknown.keySet()) { Object value = unknown.get(key); @@ -91,18 +97,37 @@ public static final class Deserializer implements JsonDeserializer unknown = null; - RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); while (reader.peek() == JsonToken.NAME) { final String nextName = reader.nextName(); switch (nextName) { case JsonKeys.DATA: + deserializeData(event, reader, logger); + break; + default: + baseEventDeserializer.deserializeValue(event, nextName, reader, logger); break; + } + } + reader.endObject(); + return event; + } + + private void deserializeData( + final @NotNull RRWebMetaEvent event, + final @NotNull JsonObjectReader reader, + final @NotNull ILogger logger) + throws Exception { + Map unknown = null; + + reader.beginObject(); + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { case JsonKeys.HREF: final String href = reader.nextStringOrNull(); event.href = href == null ? "" : href; @@ -116,18 +141,14 @@ public static final class Deserializer implements JsonDeserializer(); - } - reader.nextUnknown(logger, unknown, nextName); + if (unknown == null) { + unknown = new ConcurrentHashMap<>(); } - break; + reader.nextUnknown(logger, unknown, nextName); } } event.setUnknown(unknown); reader.endObject(); - return event; } } } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java index 17a54320b0..a20c705e5a 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java @@ -1,31 +1,178 @@ package io.sentry.rrweb; import io.sentry.ILogger; +import io.sentry.JsonDeserializer; +import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; import io.sentry.ObjectWriter; +import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public final class RRWebVideoEvent extends RRWebEvent implements JsonUnknown, JsonSerializable { - @Override public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public static final String EVENT_TAG = "video"; + public static final String REPLAY_ENCODING = "h264"; + public static final String REPLAY_CONTAINER = "mp4"; + public static final String REPLAY_FRAME_RATE_TYPE_CONSTANT = "constant"; + public static final String REPLAY_FRAME_RATE_TYPE_VARIABLE = "variable"; + private @NotNull String tag; + private int segmentId; + private long size; + private int duration; + private @NotNull String encoding = REPLAY_ENCODING; + private @NotNull String container = REPLAY_CONTAINER; + private int height; + private int width; + private int frameCount; + private @NotNull String frameRateType = REPLAY_FRAME_RATE_TYPE_CONSTANT; + private int frameRate; + private int left; + private int top; + private @Nullable Map payloadUnknown; + private @Nullable Map dataUnknown; + + public RRWebVideoEvent() { + super(RRWebEventType.Custom); + tag = EVENT_TAG; + } + + @NotNull + public String getTag() { + return tag; + } + + public void setTag(final @NotNull String tag) { + this.tag = tag; + } + + public int getSegmentId() { + return segmentId; + } + + public void setSegmentId(final int segmentId) { + this.segmentId = segmentId; } - @Override public @Nullable Map getUnknown() { - return null; + public long getSize() { + return size; } - @Override public void setUnknown(@Nullable Map unknown) { + public void setSize(final long size) { + this.size = size; + } + + public int getDuration() { + return duration; + } + + public void setDuration(final int duration) { + this.duration = duration; + } + + @NotNull + public String getEncoding() { + return encoding; + } + + public void setEncoding(final @NotNull String encoding) { + this.encoding = encoding; + } + @NotNull + public String getContainer() { + return container; } + public void setContainer(final @NotNull String container) { + this.container = container; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public int getFrameCount() { + return frameCount; + } + + public void setFrameCount(final int frameCount) { + this.frameCount = frameCount; + } + + @NotNull + public String getFrameRateType() { + return frameRateType; + } + + public void setFrameRateType(final @NotNull String frameRateType) { + this.frameRateType = frameRateType; + } + + public int getFrameRate() { + return frameRate; + } + + public void setFrameRate(final int frameRate) { + this.frameRate = frameRate; + } + + public int getLeft() { + return left; + } + + public void setLeft(final int left) { + this.left = left; + } + + public int getTop() { + return top; + } + + public void setTop(final int top) { + this.top = top; + } + + public @Nullable Map getPayloadUnknown() { + return payloadUnknown; + } + + public void setPayloadUnknown(final @Nullable Map payloadUnknown) { + this.payloadUnknown = payloadUnknown; + } + + @Override + public @Nullable Map getUnknown() { + return dataUnknown; + } + + @Override + public void setUnknown(final @Nullable Map unknown) { + this.dataUnknown = unknown; + } + + // region json + // rrweb uses camelCase hence the json keys are in camelCase here public static final class JsonKeys { + public static final String DATA = "data"; public static final String TAG = "tag"; public static final String PAYLOAD = "payload"; public static final String SEGMENT_ID = "segmentId"; @@ -42,5 +189,177 @@ public static final class JsonKeys { public static final String TOP = "top"; } + @Override + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { + writer.beginObject(); + new RRWebEvent.Serializer().serialize(this, writer, logger); + writer.name(JsonKeys.DATA); + serializeData(writer, logger); + writer.endObject(); + } + + private void serializeData(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { + writer.beginObject(); + writer.name(JsonKeys.TAG).value(tag); + writer.name(JsonKeys.PAYLOAD); + serializePayload(writer, logger); + if (dataUnknown != null) { + for (String key : dataUnknown.keySet()) { + Object value = dataUnknown.get(key); + writer.name(key); + writer.value(logger, value); + } + } + writer.endObject(); + } + private void serializePayload(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { + writer.beginObject(); + writer.name(JsonKeys.SEGMENT_ID).value(segmentId); + writer.name(JsonKeys.SIZE).value(size); + writer.name(JsonKeys.DURATION).value(duration); + writer.name(JsonKeys.ENCODING).value(encoding); + writer.name(JsonKeys.CONTAINER).value(container); + writer.name(JsonKeys.HEIGHT).value(height); + writer.name(JsonKeys.WIDTH).value(width); + writer.name(JsonKeys.FRAME_COUNT).value(frameCount); + writer.name(JsonKeys.FRAME_RATE).value(frameRate); + writer.name(JsonKeys.FRAME_RATE_TYPE).value(frameRateType); + writer.name(JsonKeys.LEFT).value(left); + writer.name(JsonKeys.TOP).value(top); + if (payloadUnknown != null) { + for (String key : payloadUnknown.keySet()) { + Object value = payloadUnknown.get(key); + writer.name(key); + writer.value(logger, value); + } + } + writer.endObject(); + } + + public static final class Deserializer implements JsonDeserializer { + + @SuppressWarnings("unchecked") + @Override + public @NotNull RRWebVideoEvent deserialize( + @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + reader.beginObject(); + RRWebVideoEvent event = new RRWebVideoEvent(); + RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); + + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case RRWebMetaEvent.JsonKeys.DATA: + deserializeData(event, reader, logger); + break; + default: + baseEventDeserializer.deserializeValue(event, nextName, reader, logger); + break; + } + } + reader.endObject(); + return event; + } + + private void deserializeData( + final @NotNull RRWebVideoEvent event, + final @NotNull JsonObjectReader reader, + final @NotNull ILogger logger) + throws Exception { + Map dataUknown = null; + + reader.beginObject(); + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.TAG: + final String tag = reader.nextStringOrNull(); + event.tag = tag == null ? "" : tag; + break; + case JsonKeys.PAYLOAD: + deserializePayload(event, reader, logger); + break; + default: + if (dataUknown == null) { + dataUknown = new ConcurrentHashMap<>(); + } + reader.nextUnknown(logger, dataUknown, nextName); + } + } + event.setUnknown(dataUknown); + reader.endObject(); + } + + private void deserializePayload( + final @NotNull RRWebVideoEvent event, + final @NotNull JsonObjectReader reader, + final @NotNull ILogger logger) + throws Exception { + Map payloadUnknown = null; + + reader.beginObject(); + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.SEGMENT_ID: + event.segmentId = reader.nextInt(); + break; + case JsonKeys.SIZE: + final Long size = reader.nextLongOrNull(); + event.size = size == null ? 0 : size; + break; + case JsonKeys.DURATION: + event.duration = reader.nextInt(); + break; + case JsonKeys.CONTAINER: + final String container = reader.nextStringOrNull(); + event.container = container == null ? "" : container; + break; + case JsonKeys.ENCODING: + final String encoding = reader.nextStringOrNull(); + event.encoding = encoding == null ? "" : encoding; + break; + case JsonKeys.HEIGHT: + final Integer height = reader.nextIntegerOrNull(); + event.height = height == null ? 0 : height; + break; + case JsonKeys.WIDTH: + final Integer width = reader.nextIntegerOrNull(); + event.width = width == null ? 0 : width; + break; + case JsonKeys.FRAME_COUNT: + final Integer frameCount = reader.nextIntegerOrNull(); + event.frameCount = frameCount == null ? 0 : frameCount; + break; + case JsonKeys.FRAME_RATE: + final Integer frameRate = reader.nextIntegerOrNull(); + event.frameRate = frameRate == null ? 0 : frameRate; + break; + case JsonKeys.FRAME_RATE_TYPE: + final String frameRateType = reader.nextStringOrNull(); + event.frameRateType = frameRateType == null ? "" : frameRateType; + break; + case JsonKeys.LEFT: + final Integer left = reader.nextIntegerOrNull(); + event.left = left == null ? 0 : left; + break; + case JsonKeys.TOP: + final Integer top = reader.nextIntegerOrNull(); + event.top = top == null ? 0 : top; + break; + default: + if (payloadUnknown == null) { + payloadUnknown = new ConcurrentHashMap<>(); + } + reader.nextUnknown(logger, payloadUnknown, nextName); + } + } + event.setUnknown(payloadUnknown); + reader.endObject(); + } + } + // endregion json } diff --git a/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt index b731cabdb4..68b45fd874 100644 --- a/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt @@ -1,11 +1,18 @@ package io.sentry.protocol -import io.sentry.DateUtils import io.sentry.ILogger +import io.sentry.JsonSerializer import io.sentry.ReplayRecording -import io.sentry.SentryEvent -import io.sentry.SentryLevel +import io.sentry.SentryOptions +import io.sentry.protocol.SerializationUtils.deserializeJson +import io.sentry.protocol.SerializationUtils.sanitizedFile +import io.sentry.protocol.SerializationUtils.serializeToString +import io.sentry.rrweb.RRWebMetaEventSerializationTest +import io.sentry.rrweb.RRWebVideoEventSerializationTest +import org.junit.Test import org.mockito.kotlin.mock +import java.io.StringWriter +import kotlin.test.assertEquals class ReplayRecordingSerializationTest { class Fixture { @@ -14,9 +21,35 @@ class ReplayRecordingSerializationTest { fun getSut() = ReplayRecording().apply { segmentId = 0 payload = listOf( - + RRWebMetaEventSerializationTest.Fixture().getSut(), + RRWebVideoEventSerializationTest.Fixture().getSut() ) } } + private val fixture = Fixture() + + @Test + fun serialize() { + val expected = sanitizedFile("json/replay_recording.json") + val actual = serializeToString(fixture.getSut(), fixture.logger) + assertEquals(expected, actual) + } + + @Test + fun deserialize() { + val expectedJson = sanitizedFile("json/replay_recording.json") + val actual = deserializeJson(expectedJson, ReplayRecording.Deserializer(), fixture.logger) + val actualJson = serializeToString(actual, fixture.logger) + assertEquals(expectedJson, actualJson) + } + + @Test + fun serializePayload() { + val expected = sanitizedFile("json/replay_recording_payload.json") + val writer = StringWriter() + JsonSerializer(SentryOptions()).serialize(fixture.getSut().payload as Any, writer) + val actual = writer.toString() + assertEquals(expected, actual) + } } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt new file mode 100644 index 0000000000..82b32e96c7 --- /dev/null +++ b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt @@ -0,0 +1,58 @@ +package io.sentry.protocol + +import io.sentry.DateUtils +import io.sentry.ILogger +import io.sentry.SentryIntegrationPackageStorage +import io.sentry.SentryReplayEvent +import io.sentry.protocol.SerializationUtils.deserializeJson +import io.sentry.protocol.SerializationUtils.sanitizedFile +import io.sentry.protocol.SerializationUtils.serializeToString +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertEquals + +class SentryReplayEventSerializationTest { + + class Fixture { + val logger = mock() + + fun getSut() = SentryReplayEvent().apply { + replayId = SentryId("f715e1d64ef64ea3ad7744b5230813c3") + segmentId = 0 + timestamp = DateUtils.getDateTimeWithMillisPrecision("987654321.123") + replayStartTimestamp = DateUtils.getDateTimeWithMillisPrecision("987654321.123") + urls = listOf("ScreenOne") + errorIds = listOf("ab3a347a4cc14fd4b4cf1dc56b670c5b") + traceIds = listOf("340cfef948204549ac07c3b353c81c50") + SentryBaseEventSerializationTest.Fixture().update(this) + } + } + private val fixture = Fixture() + + @Before + fun setup() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @Test + fun serialize() { + val expected = sanitizedFile("json/sentry_replay_event.json") + val actual = serializeToString(fixture.getSut(), fixture.logger) + assertEquals(expected, actual) + } + + @Test + fun deserialize() { + val expectedJson = sanitizedFile("json/sentry_replay_event.json") + val actual = deserializeJson(expectedJson, SentryReplayEvent.Deserializer(), fixture.logger) + val actualJson = serializeToString(actual, fixture.logger) + assertEquals(expectedJson, actualJson) + } +} diff --git a/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt b/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt index 3ab8ad03cb..29ec354333 100644 --- a/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/rrweb/RRWebMetaEventSerializationTest.kt @@ -16,8 +16,8 @@ class RRWebMetaEventSerializationTest { fun getSut() = RRWebMetaEvent().apply { href = "https://sentry.io" - width = 1080 height = 1920 + width = 1080 type = Meta timestamp = 1234567890 } diff --git a/sentry/src/test/java/io/sentry/rrweb/RRWebVideoEventSerializationTest.kt b/sentry/src/test/java/io/sentry/rrweb/RRWebVideoEventSerializationTest.kt new file mode 100644 index 0000000000..79bfd02456 --- /dev/null +++ b/sentry/src/test/java/io/sentry/rrweb/RRWebVideoEventSerializationTest.kt @@ -0,0 +1,47 @@ +package io.sentry.rrweb + +import io.sentry.ILogger +import io.sentry.protocol.SerializationUtils +import io.sentry.rrweb.RRWebEventType.Custom +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertEquals + +class RRWebVideoEventSerializationTest { + class Fixture { + val logger = mock() + + fun getSut() = RRWebVideoEvent().apply { + type = Custom + timestamp = 12345678901 + tag = "video" + segmentId = 0 + size = 4_000_000L + duration = 5000 + height = 1920 + width = 1080 + frameCount = 5 + frameRate = 1 + left = 100 + top = 100 + } + } + + private val fixture = Fixture() + + @Test + fun serialize() { + val expected = SerializationUtils.sanitizedFile("json/rrweb_video_event.json") + val actual = SerializationUtils.serializeToString(fixture.getSut(), fixture.logger) + assertEquals(expected, actual) + } + + @Test + fun deserialize() { + val expectedJson = SerializationUtils.sanitizedFile("json/rrweb_video_event.json") + val actual = + SerializationUtils.deserializeJson(expectedJson, RRWebVideoEvent.Deserializer(), fixture.logger) + val actualJson = SerializationUtils.serializeToString(actual, fixture.logger) + assertEquals(expectedJson, actualJson) + } +} diff --git a/sentry/src/test/resources/json/replay_recording.json b/sentry/src/test/resources/json/replay_recording.json new file mode 100644 index 0000000000..b0de77bc87 --- /dev/null +++ b/sentry/src/test/resources/json/replay_recording.json @@ -0,0 +1,3 @@ +{ + "segment_id": 0 +} diff --git a/sentry/src/test/resources/json/replay_recording_payload.json b/sentry/src/test/resources/json/replay_recording_payload.json new file mode 100644 index 0000000000..fe790b52d6 --- /dev/null +++ b/sentry/src/test/resources/json/replay_recording_payload.json @@ -0,0 +1,32 @@ +[ + { + "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 + } + } + } +] diff --git a/sentry/src/test/resources/json/rrweb_meta_event.json b/sentry/src/test/resources/json/rrweb_meta_event.json index a1d9621f52..5eb561a78d 100644 --- a/sentry/src/test/resources/json/rrweb_meta_event.json +++ b/sentry/src/test/resources/json/rrweb_meta_event.json @@ -3,7 +3,7 @@ "timestamp": 1234567890, "data": { "href": "https://sentry.io", - "width": 1080, - "height": 1920 + "height": 1920, + "width": 1080 } } diff --git a/sentry/src/test/resources/json/rrweb_video_event.json b/sentry/src/test/resources/json/rrweb_video_event.json new file mode 100644 index 0000000000..692dafe879 --- /dev/null +++ b/sentry/src/test/resources/json/rrweb_video_event.json @@ -0,0 +1,21 @@ +{ + "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 + } + } +} diff --git a/sentry/src/test/resources/json/sentry_replay_event.json b/sentry/src/test/resources/json/sentry_replay_event.json new file mode 100644 index 0000000000..b3d1c9ddb5 --- /dev/null +++ b/sentry/src/test/resources/json/sentry_replay_event.json @@ -0,0 +1,258 @@ +{ + "type": "replay_event", + "replay_type": "session", + "segment_id": 0, + "timestamp": 987654321.123, + "replay_id": "f715e1d64ef64ea3ad7744b5230813c3", + "replay_start_timestamp": 987654321.123, + "urls": + [ + "ScreenOne" + ], + "error_ids": + [ + "ab3a347a4cc14fd4b4cf1dc56b670c5b" + ], + "trace_ids": + [ + "340cfef948204549ac07c3b353c81c50" + ], + "event_id": "afcb46b1140ade5187c4bbb5daa804df", + "contexts": + { + "app": + { + "app_identifier": "3b7a3313-53b4-43f4-a6a1-7a7c36a9b0db", + "app_start_time": "1918-11-17T07:46:04.000Z", + "device_app_hash": "3d1fcf36-2c25-4378-bdf8-1e65239f1df4", + "build_type": "d78c56cd-eb0f-4213-8899-cd10ddf20763", + "app_name": "873656fd-f620-4edf-bb7a-a0d13325dba0", + "app_version": "801aab22-ad4b-44fb-995c-bacb5387e20c", + "app_build": "660f0cde-eedb-49dc-a973-8aa1c04f4a28", + "permissions": + { + "WRITE_EXTERNAL_STORAGE": "not_granted", + "CAMERA": "granted" + }, + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] + }, + "browser": + { + "name": "e1c723db-7408-4043-baa7-f4e96234e5dc", + "version": "724a48e9-2d35-416b-9f79-132beba2473a" + }, + "device": + { + "name": "83f1de77-fdb0-470e-8249-8f5c5d894ec4", + "manufacturer": "e21b2405-e378-4a0b-ad2c-4822d97cd38c", + "brand": "1abbd13e-d1ca-4d81-bd1b-24aa2c339cf9", + "family": "67a4b8ea-6c38-4c33-8579-7697f538685c", + "model": "d6ca2f35-bcc5-4dd3-ad64-7c3b585e02fd", + "model_id": "d3f133bd-b0a2-4aa4-9eed-875eba93652e", + "archs": + [ + "856e5da3-774c-4663-a830-d19f0b7dbb5b", + "b345bd5a-90a5-4301-a5a2-6c102d7589b6", + "fd7ed64e-a591-49e0-8dc1-578234356d23", + "8cec4101-0305-480b-91ee-f3c007f668c3", + "22583b9b-195e-49bf-bfe8-825ae3a346f2", + "8675b7aa-5b94-42d0-bc14-72ea1bb7112e" + ], + "battery_level": 0.45770407, + "charging": false, + "online": true, + "orientation": "portrait", + "simulator": true, + "memory_size": -6712323365568152393, + "free_memory": -953384122080236886, + "usable_memory": -8999512249221323968, + "low_memory": false, + "storage_size": -3227905175393990709, + "free_storage": -3749039933924297357, + "external_storage_size": -7739608324159255302, + "external_free_storage": -1562576688560812557, + "screen_width_pixels": 1101873181, + "screen_height_pixels": 1902392170, + "screen_density": 0.9829039, + "screen_dpi": -2092079070, + "boot_time": "2004-11-04T08:38:00.000Z", + "timezone": "Europe/Vienna", + "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", + "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", + "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" + }, + "gpu": + { + "name": "d623a6b5-e1ab-4402-931b-c06f5a43a5ae", + "id": -596576280, + "vendor_id": "1874778041", + "vendor_name": "d732cf76-07dc-48e2-8920-96d6bfc2439d", + "memory_size": -1484004451, + "api_type": "95dfc8bc-88ae-4d66-b85f-6c88ad45b80f", + "multi_threaded_rendering": true, + "version": "3f3f73c3-83a2-423a-8a6f-bb3de0d4a6ae", + "npot_support": "e06b074a-463c-45de-a959-cbabd461d99d" + }, + "os": + { + "name": "686a11a8-eae7-4393-aa10-a1368d523cb2", + "version": "3033f32d-6a27-4715-80c8-b232ce84ca61", + "raw_description": "eb2d0c5e-f5d4-49c7-b876-d8a654ee87cf", + "build": "bd197b97-eb68-49c3-9d07-ef789caf3069", + "kernel_version": "1df24aec-3a6f-49a9-8b50-69ae5f9dde08", + "rooted": true + }, + "response": + { + "cookies": "PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;", + "headers": { + "content-type": "text/html" + }, + "status_code": 500, + "body_size": 1000, + "data": + { + "d9d709db-b666-40cc-bcbb-093bb12aad26": "1631d0e6-96b7-4632-85f8-ef69e8bcfb16" + }, + "arbitrary_field": "arbitrary" + }, + "runtime": + { + "name": "4ed019c4-9af9-43e0-830e-bfde9fe4461c", + "version": "16534f6b-1670-4bb8-aec2-647a1b97669b", + "raw_description": "773b5b05-a0f9-4ee6-9f3b-13155c37ad6e" + }, + "trace": + { + "trace_id": "afcb46b1140ade5187c4bbb5daa804df", + "span_id": "bf6b582d-8ce3-412b-a334-f4c5539b9602", + "parent_span_id": "c7500f2a-d4e6-4f5f-a0f4-6bb67e98d5a2", + "op": "e481581d-35a4-4e97-8a1c-b554bf49f23e", + "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", + "status": "resource_exhausted", + "origin": "auto.test.unit.spancontext", + "tags": + { + "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", + "29106d7d-7fa4-444f-9d34-b9d7510c69ab": "218c23ea-694a-497e-bf6d-e5f26f1ad7bd", + "ba9ce913-269f-4c03-882d-8ca5e6991b14": "35a74e90-8db8-4610-a411-872cbc1030ac" + } + } + }, + "sdk": + { + "name": "3e934135-3f2b-49bc-8756-9f025b55143e", + "version": "3e31738e-4106-42d0-8be2-4a3a1bc648d3", + "packages": + [ + { + "name": "b59a1949-9950-4203-b394-ddd8d02c9633", + "version": "3d7790f3-7f32-43f7-b82f-9f5bc85205a8" + } + ], + "integrations": + [ + "daec50ae-8729-49b5-82f7-991446745cd5", + "8fc94968-3499-4a2c-b4d7-ecc058d9c1b0" + ] + }, + "request": + { + "url": "67369bc9-64d3-4d31-bfba-37393b145682", + "method": "8185abc3-5411-4041-a0d9-374180081044", + "query_string": "e3dc7659-f42e-413c-a07c-52b24bf9d60d", + "data": + { + "d9d709db-b666-40cc-bcbb-093bb12aad26": "1631d0e6-96b7-4632-85f8-ef69e8bcfb16" + }, + "cookies": "d84f4cfc-5310-4818-ad4f-3f8d22ceaca8", + "headers": + { + "c4991f66-9af9-4914-ac5e-e4854a5a4822": "37714d22-25a7-469b-b762-289b456fbec3" + }, + "env": + { + "6d569c89-5d5e-40e0-a4fc-109b20a53778": "ccadf763-44e4-475c-830c-de6ba0dbd202" + }, + "other": + { + "669ff1c1-517b-46dc-a889-131555364a56": "89043294-f6e1-4e2e-b152-1fdf9b1102fc" + }, + "fragment": "fragment", + "body_size": 1000, + "api_target": "graphql" + }, + "tags": + { + "79ba41db-8dc6-4156-b53e-6cf6d742eb88": "690ce82f-4d5d-4d81-b467-461a41dd9419" + }, + "release": "be9b8133-72f5-497b-adeb-b0a245eebad6", + "environment": "89204175-e462-4628-8acb-3a7fa8d8da7d", + "platform": "38decc78-2711-4a6a-a0be-abb61bfa5a6e", + "user": + { + "email": "c4d61c1b-c144-431e-868f-37a46be5e5f2", + "id": "efb2084b-1871-4b59-8897-b4bd9f196a01", + "username": "60c05dff-7140-4d94-9a61-c9cdd9ca9b96", + "ip_address": "51d22b77-f663-4dbe-8103-8b749d1d9a48", + "name": "c8c60762-b1cf-11ed-afa1-0242ac120002", + "geo": { + "city": "0e6ed0b0-b1c5-11ed-afa1-0242ac120002", + "country_code": "JP", + "region": "273a3d0a-b1c5-11ed-afa1-0242ac120002" + }, + "data": + { + "dc2813d0-0f66-4a3f-a995-71268f61a8fa": "991659ad-7c59-4dd3-bb89-0bd5c74014bd" + } + }, + "server_name": "e6f0ae04-0f40-421b-aad1-f68c15117937", + "dist": "27022a08-aace-40c6-8d0a-358a27fcaa7a", + "breadcrumbs": + [ + { + "timestamp": "2009-11-16T01:08:47.000Z", + "message": "46f233c0-7c2d-488a-b05a-7be559173e16", + "type": "ace57e2e-305e-4048-abf0-6c8538ea7bf4", + "data": + { + "6607d106-d426-462b-af74-f29fce978e48": "149bb94a-1387-4484-90be-2df15d1322ab" + }, + "category": "b6eea851-5ae5-40ed-8fdd-5e1a655a879c", + "level": "debug" + } + ], + "debug_meta": + { + "sdk_info": + { + "sdk_name": "182c4407-c1e1-4427-9b5a-ad2e22b1046a", + "version_major": 2045114005, + "version_minor": 1436566288, + "version_patchlevel": 1637914973 + }, + "images": + [ + { + "uuid": "8994027e-1cd9-4be8-b611-88ce08cf16e6", + "type": "fd6e053b-a7fe-4754-916e-bfb3ab77177d", + "debug_id": "8c653f5a-3418-4823-ba91-29a84c9c1235", + "debug_file": "55cc15dd-51f3-4cad-803c-6fd90eac21f6", + "code_id": "01230ece-f729-4af4-8b48-df74700aa4bf", + "code_file": "415c8995-1cb4-4bed-ba5c-5b3d6ba1ad47", + "image_addr": "8a258c81-641d-4e54-b06e-a0f56b1ee2ef", + "image_size": -7905338721846826571, + "arch": "d00d5bea-fb5c-43c9-85f0-dc1350d957a4" + } + ] + }, + "extra": + { + "34a7d067-fad2-49d9-97b9-71eff243127b": "fe3dc1cf-4a99-4213-85bb-e0957b8349b8" + } +} From 6cfb511d03b0bb6068994b58ce51f1beaac0e05f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Mon, 19 Feb 2024 14:52:56 +0100 Subject: [PATCH 04/19] Remove jsonValue --- sentry/src/main/java/io/sentry/JsonObjectWriter.java | 6 ------ sentry/src/main/java/io/sentry/ObjectWriter.java | 2 -- sentry/src/main/java/io/sentry/util/MapObjectWriter.java | 6 ------ 3 files changed, 14 deletions(-) diff --git a/sentry/src/main/java/io/sentry/JsonObjectWriter.java b/sentry/src/main/java/io/sentry/JsonObjectWriter.java index ff5114606c..b174ddb484 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectWriter.java +++ b/sentry/src/main/java/io/sentry/JsonObjectWriter.java @@ -52,12 +52,6 @@ public JsonObjectWriter value(final @Nullable String value) throws IOException { return this; } - @Override - public ObjectWriter jsonValue(@Nullable String value) throws IOException { - jsonWriter.jsonValue(value); - return this; - } - @Override public JsonObjectWriter nullValue() throws IOException { jsonWriter.nullValue(); diff --git a/sentry/src/main/java/io/sentry/ObjectWriter.java b/sentry/src/main/java/io/sentry/ObjectWriter.java index a5b8d12a4e..ea8d4e83ea 100644 --- a/sentry/src/main/java/io/sentry/ObjectWriter.java +++ b/sentry/src/main/java/io/sentry/ObjectWriter.java @@ -17,8 +17,6 @@ public interface ObjectWriter { ObjectWriter value(final @Nullable String value) throws IOException; - ObjectWriter jsonValue(final @Nullable String value) throws IOException; - ObjectWriter nullValue() throws IOException; ObjectWriter value(final boolean value) throws IOException; diff --git a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java index 7d25c7d9dd..26f80eddc2 100644 --- a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java +++ b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java @@ -151,12 +151,6 @@ public MapObjectWriter value(final @Nullable String value) throws IOException { return this; } - @Override - public ObjectWriter jsonValue(@Nullable String value) throws IOException { - // no-op - return this; - } - @Override public MapObjectWriter nullValue() throws IOException { postValue((Object) null); From 0d031d770405212dd1d37af3d7c6dcbd90ec4e25 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Mon, 19 Feb 2024 14:55:40 +0100 Subject: [PATCH 05/19] Remove --- sentry/src/main/java/io/sentry/SentryItemType.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index 79deda34c0..ab5fb3bc73 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -33,8 +33,6 @@ public static SentryItemType resolve(Object item) { return Session; } else if (item instanceof ClientReport) { return ClientReport; - } else if (item instanceof SentryReplayEvent) { - return ReplayEvent; } else { return Attachment; } From 07e6b261803a095c7b4774c13ae73805d365bb35 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Mon, 19 Feb 2024 14:58:22 +0100 Subject: [PATCH 06/19] Fix json --- .../resources/json/sentry_replay_event.json | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/sentry/src/test/resources/json/sentry_replay_event.json b/sentry/src/test/resources/json/sentry_replay_event.json index b3d1c9ddb5..04c96968b9 100644 --- a/sentry/src/test/resources/json/sentry_replay_event.json +++ b/sentry/src/test/resources/json/sentry_replay_event.json @@ -211,22 +211,7 @@ "dc2813d0-0f66-4a3f-a995-71268f61a8fa": "991659ad-7c59-4dd3-bb89-0bd5c74014bd" } }, - "server_name": "e6f0ae04-0f40-421b-aad1-f68c15117937", "dist": "27022a08-aace-40c6-8d0a-358a27fcaa7a", - "breadcrumbs": - [ - { - "timestamp": "2009-11-16T01:08:47.000Z", - "message": "46f233c0-7c2d-488a-b05a-7be559173e16", - "type": "ace57e2e-305e-4048-abf0-6c8538ea7bf4", - "data": - { - "6607d106-d426-462b-af74-f29fce978e48": "149bb94a-1387-4484-90be-2df15d1322ab" - }, - "category": "b6eea851-5ae5-40ed-8fdd-5e1a655a879c", - "level": "debug" - } - ], "debug_meta": { "sdk_info": @@ -250,9 +235,5 @@ "arch": "d00d5bea-fb5c-43c9-85f0-dc1350d957a4" } ] - }, - "extra": - { - "34a7d067-fad2-49d9-97b9-71eff243127b": "fe3dc1cf-4a99-4213-85bb-e0957b8349b8" } } From 18af924efa5d49b0922703d3cf23b92699e5951f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 21 Feb 2024 00:19:02 +0100 Subject: [PATCH 07/19] Finalize replay envelopes --- buildSrc/src/main/java/Config.kt | 1 + sentry/build.gradle.kts | 1 + .../main/java/io/sentry/JsonObjectWriter.java | 11 ++ .../main/java/io/sentry/JsonSerializer.java | 2 + .../src/main/java/io/sentry/ObjectWriter.java | 4 + .../main/java/io/sentry/ReplayRecording.java | 84 +++++++++++- .../java/io/sentry/SentryEnvelopeItem.java | 36 ++---- .../java/io/sentry/SentryReplayEvent.java | 32 ++++- .../main/java/io/sentry/rrweb/RRWebEvent.java | 23 +++- .../java/io/sentry/rrweb/RRWebEventType.java | 4 +- .../java/io/sentry/rrweb/RRWebMetaEvent.java | 45 ++++++- .../java/io/sentry/rrweb/RRWebVideoEvent.java | 89 +++++++++++-- .../test/java/io/sentry/SentryClientTest.kt | 120 +++++++++++++++++- .../ReplayRecordingSerializationTest.kt | 20 +-- .../rrweb/RRWebEventSerializationTest.kt | 4 +- .../test/resources/json/replay_recording.json | 5 +- 16 files changed, 407 insertions(+), 74 deletions(-) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 8a02ce0e65..408d36051a 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -195,6 +195,7 @@ object Config { val jsonUnit = "net.javacrumbs.json-unit:json-unit:2.32.0" val hsqldb = "org.hsqldb:hsqldb:2.6.1" val javaFaker = "com.github.javafaker:javafaker:1.0.2" + val msgpack = "org.msgpack:msgpack-core:0.9.8" } object QualityPlugins { diff --git a/sentry/build.gradle.kts b/sentry/build.gradle.kts index 2f35cbd4f7..08efc550d5 100644 --- a/sentry/build.gradle.kts +++ b/sentry/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { testImplementation(Config.TestLibs.mockitoInline) testImplementation(Config.TestLibs.awaitility) testImplementation(Config.TestLibs.javaFaker) + testImplementation(Config.TestLibs.msgpack) testImplementation(projects.sentryTestSupport) } diff --git a/sentry/src/main/java/io/sentry/JsonObjectWriter.java b/sentry/src/main/java/io/sentry/JsonObjectWriter.java index b174ddb484..f1e84e6d5a 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectWriter.java +++ b/sentry/src/main/java/io/sentry/JsonObjectWriter.java @@ -52,6 +52,12 @@ public JsonObjectWriter value(final @Nullable String value) throws IOException { return this; } + @Override + public ObjectWriter jsonValue(@Nullable String value) throws IOException { + jsonWriter.jsonValue(value); + return this; + } + @Override public JsonObjectWriter nullValue() throws IOException { jsonWriter.nullValue(); @@ -103,6 +109,11 @@ public JsonObjectWriter value(final @NotNull ILogger logger, final @Nullable Obj return this; } + @Override + public void setLenient(final boolean lenient) { + jsonWriter.setLenient(lenient); + } + public void setIndent(final @NotNull String indent) { jsonWriter.setIndent(indent); } diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index 95c0c538ac..9f0e93f59a 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -92,6 +92,7 @@ public JsonSerializer(@NotNull SentryOptions options) { deserializersByClass.put( ProfileMeasurementValue.class, new ProfileMeasurementValue.Deserializer()); deserializersByClass.put(Request.class, new Request.Deserializer()); + deserializersByClass.put(ReplayRecording.class, new ReplayRecording.Deserializer()); deserializersByClass.put(RRWebEventType.class, new RRWebEventType.Deserializer()); deserializersByClass.put(RRWebMetaEvent.class, new RRWebMetaEvent.Deserializer()); deserializersByClass.put(RRWebVideoEvent.class, new RRWebVideoEvent.Deserializer()); @@ -107,6 +108,7 @@ public JsonSerializer(@NotNull SentryOptions options) { deserializersByClass.put(SentryLockReason.class, new SentryLockReason.Deserializer()); deserializersByClass.put(SentryPackage.class, new SentryPackage.Deserializer()); deserializersByClass.put(SentryRuntime.class, new SentryRuntime.Deserializer()); + deserializersByClass.put(SentryReplayEvent.class, new SentryReplayEvent.Deserializer()); deserializersByClass.put(SentrySpan.class, new SentrySpan.Deserializer()); deserializersByClass.put(SentryStackFrame.class, new SentryStackFrame.Deserializer()); deserializersByClass.put(SentryStackTrace.class, new SentryStackTrace.Deserializer()); diff --git a/sentry/src/main/java/io/sentry/ObjectWriter.java b/sentry/src/main/java/io/sentry/ObjectWriter.java index ea8d4e83ea..91e64a0c8b 100644 --- a/sentry/src/main/java/io/sentry/ObjectWriter.java +++ b/sentry/src/main/java/io/sentry/ObjectWriter.java @@ -17,6 +17,8 @@ public interface ObjectWriter { ObjectWriter value(final @Nullable String value) throws IOException; + ObjectWriter jsonValue(final @Nullable String value) throws IOException; + ObjectWriter nullValue() throws IOException; ObjectWriter value(final boolean value) throws IOException; @@ -31,4 +33,6 @@ public interface ObjectWriter { ObjectWriter value(final @NotNull ILogger logger, final @Nullable Object object) throws IOException; + + void setLenient(boolean lenient); } diff --git a/sentry/src/main/java/io/sentry/ReplayRecording.java b/sentry/src/main/java/io/sentry/ReplayRecording.java index c110c00700..d8892191f2 100644 --- a/sentry/src/main/java/io/sentry/ReplayRecording.java +++ b/sentry/src/main/java/io/sentry/ReplayRecording.java @@ -1,8 +1,15 @@ package io.sentry; import io.sentry.rrweb.RRWebEvent; +import io.sentry.rrweb.RRWebEventType; +import io.sentry.rrweb.RRWebMetaEvent; +import io.sentry.rrweb.RRWebVideoEvent; +import io.sentry.util.MapObjectReader; +import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,6 +44,19 @@ public void setPayload(@Nullable List payload) { this.payload = payload; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ReplayRecording that = (ReplayRecording) o; + return Objects.equals(segmentId, that.segmentId) && Objects.equals(payload, that.payload); + } + + @Override + public int hashCode() { + return Objects.hash(segmentId, payload); + } + @Override public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { @@ -52,6 +72,15 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger } } writer.endObject(); + + // {"segment_id":0}\n{json-serialized-rrweb-protocol} + + writer.setLenient(true); + writer.jsonValue("\n"); + if (payload != null) { + writer.value(logger, payload); + } + writer.setLenient(false); } @Override @@ -66,14 +95,16 @@ public void setUnknown(@Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { + @SuppressWarnings("unchecked") @Override public @NotNull ReplayRecording deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { final ReplayRecording replay = new ReplayRecording(); @Nullable Map unknown = null; @Nullable Integer segmentId = null; + @Nullable List payload = null; reader.beginObject(); while (reader.peek() == JsonToken.NAME) { @@ -92,7 +123,58 @@ public static final class Deserializer implements JsonDeserializer events = (List) reader.nextObjectOrNull(); + reader.setLenient(false); + + // since we lose the type of an rrweb event at runtime, we have to recover it from a map + if (events != null) { + payload = new ArrayList<>(events.size()); + for (Object event : events) { + if (event instanceof Map) { + final Map eventMap = (Map) event; + final ObjectReader mapReader = new MapObjectReader(eventMap); + for (Map.Entry entry : eventMap.entrySet()) { + final String key = entry.getKey(); + final Object value = entry.getValue(); + if (key.equals("type")) { + RRWebEventType type = RRWebEventType.values()[(int) value]; + switch (type) { + case Meta: + final RRWebEvent metaEvent = + new RRWebMetaEvent.Deserializer().deserialize(mapReader, logger); + payload.add(metaEvent); + break; + case Custom: + final Map data = + (Map) 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; + } + break; + default: + logger.log(SentryLevel.DEBUG, "Unsupported rrweb event type %s", type); + break; + } + } + } + } + } + } + replay.setSegmentId(segmentId); + replay.setPayload(payload); replay.setUnknown(unknown); return replay; } diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index 53b2d24ea5..be6838670e 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -21,6 +21,7 @@ import java.io.Reader; import java.io.Writer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; @@ -365,22 +366,16 @@ public static SentryEnvelopeItem fromReplay( replayPayload.put(SentryItemType.ReplayEvent.getItemType(), stream.toByteArray()); stream.reset(); - // next serialize replay recording in the following format: - // {"segment_id":0}\n{json-serialized-rrweb-protocol} + // next serialize replay recording if (replayRecording != null) { serializer.serialize(replayRecording, writer); - writer.write("\n"); - writer.flush(); - if (replayRecording.getPayload() != null) { - serializer.serialize(replayRecording.getPayload(), writer); - } replayPayload.put( SentryItemType.ReplayRecording.getItemType(), stream.toByteArray()); stream.reset(); } // next serialize replay video bytes from given file - if (replayVideo.exists()) { + if (replayVideo != null && replayVideo.exists()) { final byte[] videoBytes = readBytesFromFile( replayVideo.getPath(), SentryReplayEvent.REPLAY_VIDEO_MAX_SIZE); @@ -395,7 +390,9 @@ public static SentryEnvelopeItem fromReplay( logger.log(SentryLevel.ERROR, "Could not serialize replay recording", t); return null; } finally { - replayVideo.delete(); + if (replayVideo != null) { + replayVideo.delete(); + } } }); @@ -428,7 +425,7 @@ public CachedItem(final @Nullable Callable dataFactory) { } } - @SuppressWarnings("CharsetObjectCanBeUsed") + @SuppressWarnings({"CharsetObjectCanBeUsed", "UnnecessaryParentheses"}) private static byte[] serializeToMsgpack(Map map) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -440,24 +437,17 @@ private static byte[] serializeToMsgpack(Map map) throws IOExcep // Pack the key as a string byte[] keyBytes = entry.getKey().getBytes(Charset.forName("UTF-8")); int keyLength = keyBytes.length; - if (keyLength <= 31) { - baos.write((byte) (0xA0 | keyLength)); - } else { - baos.write((byte) (0xD9)); - baos.write((byte) (keyLength)); - } + // string up to 255 chars + baos.write((byte) (0xd9)); + baos.write((byte) (keyLength)); baos.write(keyBytes); // Pack the value as a binary string byte[] valueBytes = entry.getValue(); int valueLength = valueBytes.length; - if (valueLength <= 255) { - baos.write((byte) (0xC4)); - baos.write((byte) (valueLength)); - } else { - baos.write((byte) (0xC5)); - baos.write(ByteBuffer.allocate(4).putInt(valueLength).array()); - } + // We will always use the 4 bytes data length for simplicity. + baos.write((byte) (0xc6)); + baos.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(valueLength).array()); baos.write(valueBytes); } diff --git a/sentry/src/main/java/io/sentry/SentryReplayEvent.java b/sentry/src/main/java/io/sentry/SentryReplayEvent.java index 623986d868..a351a21d54 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayEvent.java +++ b/sentry/src/main/java/io/sentry/SentryReplayEvent.java @@ -1,6 +1,7 @@ package io.sentry; import io.sentry.protocol.SentryId; +import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; import java.io.File; import java.io.IOException; @@ -29,8 +30,8 @@ public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull ReplayType deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull ReplayType deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { return ReplayType.valueOf(reader.nextString().toUpperCase(Locale.ROOT)); } } @@ -39,7 +40,7 @@ public static final class Deserializer implements JsonDeserializer { public static final long REPLAY_VIDEO_MAX_SIZE = 10 * 1024 * 1024; public static final String REPLAY_EVENT_TYPE = "replay_event"; - private @NotNull File videoFile; + private @Nullable File videoFile; private @NotNull String type; private @NotNull ReplayType replayType; private @Nullable SentryId replayId; @@ -62,12 +63,12 @@ public SentryReplayEvent() { timestamp = DateUtils.getCurrentDateTime(); } - @NotNull + @Nullable public File getVideoFile() { return videoFile; } - public void setVideoFile(final @NotNull File videoFile) { + public void setVideoFile(final @Nullable File videoFile) { this.videoFile = videoFile; } @@ -151,6 +152,25 @@ public void setReplayType(final @NotNull ReplayType replayType) { this.replayType = replayType; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SentryReplayEvent that = (SentryReplayEvent) o; + return segmentId == that.segmentId + && Objects.equals(type, that.type) + && replayType == that.replayType + && Objects.equals(replayId, that.replayId) + && Objects.equals(urls, that.urls) + && Objects.equals(errorIds, that.errorIds) + && Objects.equals(traceIds, that.traceIds); + } + + @Override + public int hashCode() { + return Objects.hash(type, replayType, replayId, segmentId, urls, errorIds, traceIds); + } + // region json public static final class JsonKeys { public static final String TYPE = "type"; @@ -219,7 +239,7 @@ public static final class Deserializer implements JsonDeserializer { @Override public @NotNull RRWebEventType deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { return RRWebEventType.values()[reader.nextInt()]; } } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java index d64400e323..0a1c914cb3 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java @@ -2,12 +2,14 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; +import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; @@ -19,6 +21,7 @@ public final class RRWebMetaEvent extends RRWebEvent implements JsonUnknown, Jso private int height; private int width; private @Nullable Map unknown; + private @Nullable Map dataUnknown; public RRWebMetaEvent() { super(RRWebEventType.Meta); @@ -50,6 +53,31 @@ public void setWidth(final int width) { this.width = width; } + @Nullable + public Map getDataUnknown() { + return dataUnknown; + } + + public void setDataUnknown(final @Nullable Map dataUnknown) { + this.dataUnknown = dataUnknown; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + RRWebMetaEvent metaEvent = (RRWebMetaEvent) o; + return height == metaEvent.height + && width == metaEvent.width + && Objects.equals(href, metaEvent.href); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), href, height, width); + } + public static final class JsonKeys { public static final String DATA = "data"; public static final String HREF = "href"; @@ -97,8 +125,9 @@ public static final class Deserializer implements JsonDeserializer unknown = null; RRWebMetaEvent event = new RRWebMetaEvent(); RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); @@ -109,17 +138,23 @@ public static final class Deserializer implements JsonDeserializer(); + } + reader.nextUnknown(logger, unknown, nextName); + } break; } } + event.setUnknown(unknown); reader.endObject(); return event; } private void deserializeData( final @NotNull RRWebMetaEvent event, - final @NotNull JsonObjectReader reader, + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { Map unknown = null; @@ -147,7 +182,7 @@ private void deserializeData( reader.nextUnknown(logger, unknown, nextName); } } - event.setUnknown(unknown); + event.setDataUnknown(unknown); reader.endObject(); } } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java index a20c705e5a..f9d61e591e 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java @@ -2,12 +2,14 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; +import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; @@ -34,6 +36,7 @@ public final class RRWebVideoEvent extends RRWebEvent implements JsonUnknown, Js private int frameRate; private int left; private int top; + private @Nullable Map unknown; private @Nullable Map payloadUnknown; private @Nullable Map dataUnknown; @@ -158,14 +161,62 @@ public void setPayloadUnknown(final @Nullable Map payloadUnknown this.payloadUnknown = payloadUnknown; } + public @Nullable Map getDataUnknown() { + return dataUnknown; + } + + public void setDataUnknown(final @Nullable Map dataUnknown) { + this.dataUnknown = dataUnknown; + } + @Override public @Nullable Map getUnknown() { - return dataUnknown; + return unknown; } @Override public void setUnknown(final @Nullable Map unknown) { - this.dataUnknown = unknown; + this.unknown = unknown; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + RRWebVideoEvent that = (RRWebVideoEvent) o; + return segmentId == that.segmentId + && size == that.size + && duration == that.duration + && height == that.height + && width == that.width + && frameCount == that.frameCount + && frameRate == that.frameRate + && left == that.left + && top == that.top + && Objects.equals(tag, that.tag) + && Objects.equals(encoding, that.encoding) + && Objects.equals(container, that.container) + && Objects.equals(frameRateType, that.frameRateType); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + tag, + segmentId, + size, + duration, + encoding, + container, + height, + width, + frameCount, + frameRateType, + frameRate, + left, + top); } // region json @@ -173,7 +224,6 @@ public void setUnknown(final @Nullable Map unknown) { // rrweb uses camelCase hence the json keys are in camelCase here public static final class JsonKeys { public static final String DATA = "data"; - public static final String TAG = "tag"; public static final String PAYLOAD = "payload"; public static final String SEGMENT_ID = "segmentId"; public static final String SIZE = "size"; @@ -195,13 +245,20 @@ public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) thr new RRWebEvent.Serializer().serialize(this, writer, logger); writer.name(JsonKeys.DATA); serializeData(writer, logger); + if (unknown != null) { + for (String key : unknown.keySet()) { + Object value = unknown.get(key); + writer.name(key); + writer.value(logger, value); + } + } writer.endObject(); } private void serializeData(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); - writer.name(JsonKeys.TAG).value(tag); + writer.name(RRWebEvent.JsonKeys.TAG).value(tag); writer.name(JsonKeys.PAYLOAD); serializePayload(writer, logger); if (dataUnknown != null) { @@ -244,8 +301,10 @@ public static final class Deserializer implements JsonDeserializer unknown = null; + RRWebVideoEvent event = new RRWebVideoEvent(); RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); @@ -256,17 +315,23 @@ public static final class Deserializer implements JsonDeserializer(); + } + reader.nextUnknown(logger, unknown, nextName); + } break; } } + event.setUnknown(unknown); reader.endObject(); return event; } private void deserializeData( final @NotNull RRWebVideoEvent event, - final @NotNull JsonObjectReader reader, + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { Map dataUknown = null; @@ -275,7 +340,7 @@ private void deserializeData( while (reader.peek() == JsonToken.NAME) { final String nextName = reader.nextName(); switch (nextName) { - case JsonKeys.TAG: + case RRWebEvent.JsonKeys.TAG: final String tag = reader.nextStringOrNull(); event.tag = tag == null ? "" : tag; break; @@ -289,13 +354,13 @@ private void deserializeData( reader.nextUnknown(logger, dataUknown, nextName); } } - event.setUnknown(dataUknown); + event.setDataUnknown(dataUknown); reader.endObject(); } private void deserializePayload( final @NotNull RRWebVideoEvent event, - final @NotNull JsonObjectReader reader, + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { Map payloadUnknown = null; @@ -357,7 +422,7 @@ private void deserializePayload( reader.nextUnknown(logger, payloadUnknown, nextName); } } - event.setUnknown(payloadUnknown); + event.setPayloadUnknown(payloadUnknown); reader.endObject(); } } diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index 8fab30790f..0733e6ea45 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -5,7 +5,6 @@ import io.sentry.Session.State.Crashed import io.sentry.clientreport.ClientReportTestHelper.Companion.assertClientReport import io.sentry.clientreport.DiscardReason import io.sentry.clientreport.DiscardedEvent -import io.sentry.clientreport.DropEverythingEventProcessor import io.sentry.exception.SentryEnvelopeException import io.sentry.hints.AbnormalExit import io.sentry.hints.ApplyScopeData @@ -41,6 +40,7 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever +import org.msgpack.core.MessagePack import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File @@ -2480,6 +2480,105 @@ class SentryClientTest { ) } + @Test + fun `when captureReplayEvent, envelope is sent`() { + val sut = fixture.getSut() + val replayEvent = createReplayEvent() + + sut.captureReplayEvent(replayEvent, null, null) + + verify(fixture.transport).send( + check { actual -> + assertEquals(replayEvent.eventId, actual.header.eventId) + assertEquals(fixture.sentryOptions.sdkVersion, actual.header.sdkVersion) + + assertEquals(1, actual.items.count()) + val item = actual.items.first() + assertEquals(SentryItemType.ReplayVideo, item.header.type) + + val unpacker = MessagePack.newDefaultUnpacker(item.data) + val mapSize = unpacker.unpackMapHeader() + assertEquals(1, mapSize) + }, + any() + ) + } + + @Test + fun `when captureReplayEvent with recording, adds it to payload`() { + val sut = fixture.getSut() + val replayEvent = createReplayEvent() + + val hint = Hint().apply { replayRecording = createReplayRecording() } + sut.captureReplayEvent(replayEvent, null, hint) + + verify(fixture.transport).send( + check { actual -> + assertEquals(replayEvent.eventId, actual.header.eventId) + assertEquals(fixture.sentryOptions.sdkVersion, actual.header.sdkVersion) + + assertEquals(1, actual.items.count()) + val item = actual.items.first() + assertEquals(SentryItemType.ReplayVideo, item.header.type) + + val unpacker = MessagePack.newDefaultUnpacker(item.data) + val mapSize = unpacker.unpackMapHeader() + assertEquals(2, mapSize) + }, + any() + ) + } + + @Test + fun `when captureReplayEvent, omits breadcrumbs and extras from scope`() { + val sut = fixture.getSut() + val replayEvent = createReplayEvent() + + sut.captureReplayEvent(replayEvent, createScope(), null) + + verify(fixture.transport).send( + check { actual -> + val item = actual.items.first() + + val unpacker = MessagePack.newDefaultUnpacker(item.data) + val mapSize = unpacker.unpackMapHeader() + for (i in 0 until mapSize) { + val key = unpacker.unpackString() + when (key) { + SentryItemType.ReplayEvent.itemType -> { + val replayEventLength = unpacker.unpackBinaryHeader() + val replayEventBytes = unpacker.readPayload(replayEventLength) + val actualReplayEvent = fixture.sentryOptions.serializer.deserialize( + InputStreamReader(replayEventBytes.inputStream()), + SentryReplayEvent::class.java + ) + // sanity check + assertEquals("id", actualReplayEvent!!.user!!.id) + + assertNull(actualReplayEvent.breadcrumbs) + assertNull(actualReplayEvent.extras) + } + } + } + }, + any() + ) + } + + @Test + fun `when replay event is dropped, captures client report with datacategory replay`() { + fixture.sentryOptions.addEventProcessor(DropEverythingEventProcessor()) + val sut = fixture.getSut() + val replayEvent = createReplayEvent() + + sut.captureReplayEvent(replayEvent, createScope(), null) + + assertClientReport( + fixture.sentryOptions.clientReportRecorder, + listOf(DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Replay.category, 1)) + ) + } + private fun givenScopeWithStartedSession(errored: Boolean = false, crashed: Boolean = false): IScope { val scope = createScope(fixture.sentryOptions) scope.startSession() @@ -2538,6 +2637,21 @@ class SentryClientTest { } } + private fun createReplayEvent(): SentryReplayEvent = SentryReplayEvent().apply { + replayId = SentryId("f715e1d64ef64ea3ad7744b5230813c3") + segmentId = 0 + timestamp = DateUtils.getDateTimeWithMillisPrecision("987654321.123") + replayStartTimestamp = DateUtils.getDateTimeWithMillisPrecision("987654321.123") + urls = listOf("ScreenOne") + errorIds = listOf("ab3a347a4cc14fd4b4cf1dc56b670c5b") + traceIds = listOf("340cfef948204549ac07c3b353c81c50") + } + + private fun createReplayRecording(): ReplayRecording = ReplayRecording().apply { + segmentId = 0 + payload = emptyList() + } + private fun createScope(options: SentryOptions = SentryOptions()): IScope { return Scope(options).apply { addBreadcrumb( @@ -2721,4 +2835,8 @@ class DropEverythingEventProcessor : EventProcessor { ): SentryTransaction? { return null } + + override fun process(event: SentryReplayEvent, hint: Hint): SentryReplayEvent? { + return null + } } diff --git a/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt index 68b45fd874..d8f93ddfb5 100644 --- a/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/ReplayRecordingSerializationTest.kt @@ -1,17 +1,14 @@ package io.sentry.protocol +import io.sentry.FileFromResources import io.sentry.ILogger -import io.sentry.JsonSerializer import io.sentry.ReplayRecording -import io.sentry.SentryOptions import io.sentry.protocol.SerializationUtils.deserializeJson -import io.sentry.protocol.SerializationUtils.sanitizedFile import io.sentry.protocol.SerializationUtils.serializeToString import io.sentry.rrweb.RRWebMetaEventSerializationTest import io.sentry.rrweb.RRWebVideoEventSerializationTest import org.junit.Test import org.mockito.kotlin.mock -import java.io.StringWriter import kotlin.test.assertEquals class ReplayRecordingSerializationTest { @@ -31,25 +28,18 @@ class ReplayRecordingSerializationTest { @Test fun serialize() { - val expected = sanitizedFile("json/replay_recording.json") + val expected = FileFromResources.invoke("json/replay_recording.json") + .substringBeforeLast("\n") val actual = serializeToString(fixture.getSut(), fixture.logger) assertEquals(expected, actual) } @Test fun deserialize() { - val expectedJson = sanitizedFile("json/replay_recording.json") + val expectedJson = FileFromResources.invoke("json/replay_recording.json") + .substringBeforeLast("\n") val actual = deserializeJson(expectedJson, ReplayRecording.Deserializer(), fixture.logger) val actualJson = serializeToString(actual, fixture.logger) assertEquals(expectedJson, actualJson) } - - @Test - fun serializePayload() { - val expected = sanitizedFile("json/replay_recording_payload.json") - val writer = StringWriter() - JsonSerializer(SentryOptions()).serialize(fixture.getSut().payload as Any, writer) - val actual = writer.toString() - assertEquals(expected, actual) - } } diff --git a/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt b/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt index 1223075c6b..2c2b60cd28 100644 --- a/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/rrweb/RRWebEventSerializationTest.kt @@ -2,8 +2,8 @@ package io.sentry.rrweb import io.sentry.ILogger import io.sentry.JsonDeserializer -import io.sentry.JsonObjectReader import io.sentry.JsonSerializable +import io.sentry.ObjectReader import io.sentry.ObjectWriter import io.sentry.protocol.SerializationUtils.deserializeJson import io.sentry.protocol.SerializationUtils.sanitizedFile @@ -27,7 +27,7 @@ class RRWebEventSerializationTest { } class Deserializer : JsonDeserializer { - override fun deserialize(reader: JsonObjectReader, logger: ILogger): Sut { + override fun deserialize(reader: ObjectReader, logger: ILogger): Sut { val sut = Sut() reader.beginObject() diff --git a/sentry/src/test/resources/json/replay_recording.json b/sentry/src/test/resources/json/replay_recording.json index b0de77bc87..287419e1eb 100644 --- a/sentry/src/test/resources/json/replay_recording.json +++ b/sentry/src/test/resources/json/replay_recording.json @@ -1,3 +1,2 @@ -{ - "segment_id": 0 -} +{"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}}}] From 64cedfa4f88599bb7ebb65f98f33e76d3fa69f97 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 21 Feb 2024 00:20:07 +0100 Subject: [PATCH 08/19] Introduce MapObjectReader --- sentry/api/sentry.api | 554 ++++++++++++++---- .../src/main/java/io/sentry/Breadcrumb.java | 7 +- sentry/src/main/java/io/sentry/CheckIn.java | 2 +- .../main/java/io/sentry/JsonDeserializer.java | 2 +- .../main/java/io/sentry/JsonObjectReader.java | 196 +++++-- .../main/java/io/sentry/MonitorConfig.java | 4 +- .../main/java/io/sentry/MonitorContexts.java | 2 +- .../main/java/io/sentry/MonitorSchedule.java | 2 +- .../src/main/java/io/sentry/ObjectReader.java | 101 ++++ .../java/io/sentry/ProfilingTraceData.java | 2 +- .../io/sentry/ProfilingTransactionData.java | 2 +- .../SentryAppStartProfilingOptions.java | 2 +- .../main/java/io/sentry/SentryBaseEvent.java | 2 +- .../java/io/sentry/SentryEnvelopeHeader.java | 2 +- .../io/sentry/SentryEnvelopeItemHeader.java | 2 +- .../src/main/java/io/sentry/SentryEvent.java | 4 +- .../main/java/io/sentry/SentryItemType.java | 2 +- .../src/main/java/io/sentry/SentryLevel.java | 4 +- .../main/java/io/sentry/SentryLockReason.java | 2 +- sentry/src/main/java/io/sentry/Session.java | 2 +- .../src/main/java/io/sentry/SpanContext.java | 4 +- sentry/src/main/java/io/sentry/SpanId.java | 2 +- .../src/main/java/io/sentry/SpanStatus.java | 4 +- .../src/main/java/io/sentry/TraceContext.java | 6 +- .../src/main/java/io/sentry/UserFeedback.java | 4 +- .../io/sentry/clientreport/ClientReport.java | 6 +- .../sentry/clientreport/DiscardedEvent.java | 4 +- .../ProfileMeasurement.java | 4 +- .../ProfileMeasurementValue.java | 4 +- .../src/main/java/io/sentry/protocol/App.java | 4 +- .../main/java/io/sentry/protocol/Browser.java | 4 +- .../java/io/sentry/protocol/Contexts.java | 4 +- .../java/io/sentry/protocol/DebugImage.java | 6 +- .../java/io/sentry/protocol/DebugMeta.java | 4 +- .../main/java/io/sentry/protocol/Device.java | 6 +- .../src/main/java/io/sentry/protocol/Geo.java | 4 +- .../src/main/java/io/sentry/protocol/Gpu.java | 4 +- .../io/sentry/protocol/MeasurementValue.java | 4 +- .../java/io/sentry/protocol/Mechanism.java | 4 +- .../main/java/io/sentry/protocol/Message.java | 4 +- .../io/sentry/protocol/OperatingSystem.java | 4 +- .../main/java/io/sentry/protocol/Request.java | 4 +- .../java/io/sentry/protocol/Response.java | 4 +- .../main/java/io/sentry/protocol/SdkInfo.java | 4 +- .../java/io/sentry/protocol/SdkVersion.java | 6 +- .../io/sentry/protocol/SentryException.java | 4 +- .../java/io/sentry/protocol/SentryId.java | 4 +- .../io/sentry/protocol/SentryPackage.java | 6 +- .../io/sentry/protocol/SentryRuntime.java | 6 +- .../java/io/sentry/protocol/SentrySpan.java | 6 +- .../io/sentry/protocol/SentryStackFrame.java | 4 +- .../io/sentry/protocol/SentryStackTrace.java | 4 +- .../java/io/sentry/protocol/SentryThread.java | 6 +- .../io/sentry/protocol/SentryTransaction.java | 4 +- .../io/sentry/protocol/TransactionInfo.java | 4 +- .../main/java/io/sentry/protocol/User.java | 4 +- .../io/sentry/protocol/ViewHierarchy.java | 6 +- .../io/sentry/protocol/ViewHierarchyNode.java | 4 +- .../java/io/sentry/util/MapObjectReader.java | 350 +++++++++++ .../java/io/sentry/util/MapObjectWriter.java | 11 + .../java/io/sentry/JsonObjectReaderTest.kt | 2 +- .../java/io/sentry/SentryEnvelopeItemTest.kt | 235 +++++++- .../SentryBaseEventSerializationTest.kt | 4 +- 63 files changed, 1369 insertions(+), 299 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/ObjectReader.java create mode 100644 sentry/src/main/java/io/sentry/util/MapObjectReader.java diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 8dc0851098..20e2d8b53b 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -136,8 +136,8 @@ public final class io/sentry/Breadcrumb : io/sentry/JsonSerializable, io/sentry/ public final class io/sentry/Breadcrumb$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/Breadcrumb; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/Breadcrumb; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/Breadcrumb$JsonKeys { @@ -181,8 +181,8 @@ public final class io/sentry/CheckIn : io/sentry/JsonSerializable, io/sentry/Jso public final class io/sentry/CheckIn$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/CheckIn; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/CheckIn; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/CheckIn$JsonKeys { @@ -226,6 +226,7 @@ public final class io/sentry/DataCategory : java/lang/Enum { public static final field Error Lio/sentry/DataCategory; public static final field Monitor Lio/sentry/DataCategory; public static final field Profile Lio/sentry/DataCategory; + public static final field Replay Lio/sentry/DataCategory; public static final field Security Lio/sentry/DataCategory; public static final field Session Lio/sentry/DataCategory; public static final field Transaction Lio/sentry/DataCategory; @@ -300,6 +301,7 @@ public final class io/sentry/EnvelopeSender : io/sentry/IEnvelopeSender { public abstract interface class io/sentry/EventProcessor { public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; + public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; } @@ -387,12 +389,14 @@ public final class io/sentry/Hint { public fun get (Ljava/lang/String;)Ljava/lang/Object; public fun getAs (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; public fun getAttachments ()Ljava/util/List; + public fun getReplayRecording ()Lio/sentry/ReplayRecording; public fun getScreenshot ()Lio/sentry/Attachment; public fun getThreadDump ()Lio/sentry/Attachment; public fun getViewHierarchy ()Lio/sentry/Attachment; public fun remove (Ljava/lang/String;)V public fun replaceAttachments (Ljava/util/List;)V public fun set (Ljava/lang/String;Ljava/lang/Object;)V + public fun setReplayRecording (Lio/sentry/ReplayRecording;)V public fun setScreenshot (Lio/sentry/Attachment;)V public fun setThreadDump (Lio/sentry/Attachment;)V public fun setViewHierarchy (Lio/sentry/Attachment;)V @@ -421,6 +425,7 @@ public final class io/sentry/Hub : io/sentry/IHub { public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; + public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V @@ -471,6 +476,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; + public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V @@ -559,6 +565,7 @@ public abstract interface class io/sentry/IHub { public fun captureMessage (Ljava/lang/String;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public abstract fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public abstract fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; + public abstract fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; @@ -723,6 +730,7 @@ public abstract interface class io/sentry/ISentryClient { public fun captureException (Ljava/lang/Throwable;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId; + public abstract fun captureReplayEvent (Lio/sentry/SentryReplayEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureSession (Lio/sentry/Session;)V public abstract fun captureSession (Lio/sentry/Session;Lio/sentry/Hint;)V public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;)Lio/sentry/protocol/SentryId; @@ -843,7 +851,7 @@ public final class io/sentry/JavaMemoryCollector : io/sentry/IPerformanceSnapsho } public abstract interface class io/sentry/JsonDeserializer { - public abstract fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public abstract fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/JsonObjectDeserializer { @@ -851,23 +859,38 @@ public final class io/sentry/JsonObjectDeserializer { public fun deserialize (Lio/sentry/JsonObjectReader;)Ljava/lang/Object; } -public final class io/sentry/JsonObjectReader : io/sentry/vendor/gson/stream/JsonReader { +public final class io/sentry/JsonObjectReader : io/sentry/ObjectReader { public fun (Ljava/io/Reader;)V - public static fun dateOrNull (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/util/Date; + public fun beginArray ()V + public fun beginObject ()V + public fun close ()V + public fun endArray ()V + public fun endObject ()V + public fun hasNext ()Z + public fun nextBoolean ()Z public fun nextBooleanOrNull ()Ljava/lang/Boolean; public fun nextDateOrNull (Lio/sentry/ILogger;)Ljava/util/Date; + public fun nextDouble ()D public fun nextDoubleOrNull ()Ljava/lang/Double; - public fun nextFloat ()Ljava/lang/Float; + public fun nextFloat ()F public fun nextFloatOrNull ()Ljava/lang/Float; + public fun nextInt ()I public fun nextIntegerOrNull ()Ljava/lang/Integer; public fun nextListOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/List; + public fun nextLong ()J public fun nextLongOrNull ()Ljava/lang/Long; public fun nextMapOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/Map; + public fun nextName ()Ljava/lang/String; + public fun nextNull ()V public fun nextObjectOrNull ()Ljava/lang/Object; public fun nextOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/lang/Object; + public fun nextString ()Ljava/lang/String; public fun nextStringOrNull ()Ljava/lang/String; public fun nextTimeZoneOrNull (Lio/sentry/ILogger;)Ljava/util/TimeZone; public fun nextUnknown (Lio/sentry/ILogger;Ljava/util/Map;Ljava/lang/String;)V + public fun peek ()Lio/sentry/vendor/gson/stream/JsonToken; + public fun setLenient (Z)V + public fun skipValue ()V } public final class io/sentry/JsonObjectSerializer { @@ -887,11 +910,13 @@ public final class io/sentry/JsonObjectWriter : io/sentry/ObjectWriter { public synthetic fun endArray ()Lio/sentry/ObjectWriter; public fun endObject ()Lio/sentry/JsonObjectWriter; public synthetic fun endObject ()Lio/sentry/ObjectWriter; + public fun jsonValue (Ljava/lang/String;)Lio/sentry/ObjectWriter; public fun name (Ljava/lang/String;)Lio/sentry/JsonObjectWriter; public synthetic fun name (Ljava/lang/String;)Lio/sentry/ObjectWriter; public fun nullValue ()Lio/sentry/JsonObjectWriter; public synthetic fun nullValue ()Lio/sentry/ObjectWriter; public fun setIndent (Ljava/lang/String;)V + public fun setLenient (Z)V public fun value (D)Lio/sentry/JsonObjectWriter; public synthetic fun value (D)Lio/sentry/ObjectWriter; public fun value (J)Lio/sentry/JsonObjectWriter; @@ -936,6 +961,7 @@ public final class io/sentry/MainEventProcessor : io/sentry/EventProcessor, java public fun (Lio/sentry/SentryOptions;)V public fun close ()V public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; + public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; } @@ -1018,8 +1044,8 @@ public final class io/sentry/MonitorConfig : io/sentry/JsonSerializable, io/sent public final class io/sentry/MonitorConfig$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/MonitorConfig; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/MonitorConfig; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/MonitorConfig$JsonKeys { @@ -1040,8 +1066,8 @@ public final class io/sentry/MonitorContexts : java/util/concurrent/ConcurrentHa public final class io/sentry/MonitorContexts$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/MonitorContexts; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/MonitorContexts; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/MonitorSchedule : io/sentry/JsonSerializable, io/sentry/JsonUnknown { @@ -1063,8 +1089,8 @@ public final class io/sentry/MonitorSchedule : io/sentry/JsonSerializable, io/se public final class io/sentry/MonitorSchedule$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/MonitorSchedule; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/MonitorSchedule; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/MonitorSchedule$JsonKeys { @@ -1119,6 +1145,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; + public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V @@ -1331,13 +1358,48 @@ public final class io/sentry/NoOpTransportFactory : io/sentry/ITransportFactory public static fun getInstance ()Lio/sentry/NoOpTransportFactory; } +public abstract interface class io/sentry/ObjectReader : java/io/Closeable { + public abstract fun beginArray ()V + public abstract fun beginObject ()V + public static fun dateOrNull (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/util/Date; + public abstract fun endArray ()V + public abstract fun endObject ()V + public abstract fun hasNext ()Z + public abstract fun nextBoolean ()Z + public abstract fun nextBooleanOrNull ()Ljava/lang/Boolean; + public abstract fun nextDateOrNull (Lio/sentry/ILogger;)Ljava/util/Date; + public abstract fun nextDouble ()D + public abstract fun nextDoubleOrNull ()Ljava/lang/Double; + public abstract fun nextFloat ()F + public abstract fun nextFloatOrNull ()Ljava/lang/Float; + public abstract fun nextInt ()I + public abstract fun nextIntegerOrNull ()Ljava/lang/Integer; + public abstract fun nextListOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/List; + public abstract fun nextLong ()J + public abstract fun nextLongOrNull ()Ljava/lang/Long; + public abstract fun nextMapOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/Map; + public abstract fun nextName ()Ljava/lang/String; + public abstract fun nextNull ()V + public abstract fun nextObjectOrNull ()Ljava/lang/Object; + public abstract fun nextOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/lang/Object; + public abstract fun nextString ()Ljava/lang/String; + public abstract fun nextStringOrNull ()Ljava/lang/String; + public abstract fun nextTimeZoneOrNull (Lio/sentry/ILogger;)Ljava/util/TimeZone; + public abstract fun nextUnknown (Lio/sentry/ILogger;Ljava/util/Map;Ljava/lang/String;)V + public abstract fun peek ()Lio/sentry/vendor/gson/stream/JsonToken; + public abstract fun setLenient (Z)V + public abstract fun skipValue ()V +} + public abstract interface class io/sentry/ObjectWriter { public abstract fun beginArray ()Lio/sentry/ObjectWriter; public abstract fun beginObject ()Lio/sentry/ObjectWriter; public abstract fun endArray ()Lio/sentry/ObjectWriter; public abstract fun endObject ()Lio/sentry/ObjectWriter; + public abstract fun jsonValue (Ljava/lang/String;)Lio/sentry/ObjectWriter; public abstract fun name (Ljava/lang/String;)Lio/sentry/ObjectWriter; public abstract fun nullValue ()Lio/sentry/ObjectWriter; + public abstract fun setLenient (Z)V public abstract fun value (D)Lio/sentry/ObjectWriter; public abstract fun value (J)Lio/sentry/ObjectWriter; public abstract fun value (Lio/sentry/ILogger;Ljava/lang/Object;)Lio/sentry/ObjectWriter; @@ -1426,8 +1488,8 @@ public final class io/sentry/ProfilingTraceData : io/sentry/JsonSerializable, io public final class io/sentry/ProfilingTraceData$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/ProfilingTraceData; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/ProfilingTraceData; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/ProfilingTraceData$JsonKeys { @@ -1484,8 +1546,8 @@ public final class io/sentry/ProfilingTransactionData : io/sentry/JsonSerializab public final class io/sentry/ProfilingTransactionData$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/ProfilingTransactionData; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/ProfilingTransactionData; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/ProfilingTransactionData$JsonKeys { @@ -1519,6 +1581,30 @@ public final class io/sentry/PropagationContext { public fun traceContext ()Lio/sentry/TraceContext; } +public final class io/sentry/ReplayRecording : io/sentry/JsonSerializable, io/sentry/JsonUnknown { + public fun ()V + public fun equals (Ljava/lang/Object;)Z + public fun getPayload ()Ljava/util/List; + public fun getSegmentId ()Ljava/lang/Integer; + public fun getUnknown ()Ljava/util/Map; + public fun hashCode ()I + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public fun setPayload (Ljava/util/List;)V + public fun setSegmentId (Ljava/lang/Integer;)V + public fun setUnknown (Ljava/util/Map;)V +} + +public final class io/sentry/ReplayRecording$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/ReplayRecording; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + +public final class io/sentry/ReplayRecording$JsonKeys { + public static final field SEGMENT_ID Ljava/lang/String; + public fun ()V +} + public final class io/sentry/RequestDetails { public fun (Ljava/lang/String;Ljava/util/Map;)V public fun getHeaders ()Ljava/util/Map; @@ -1669,6 +1755,7 @@ public final class io/sentry/Sentry { public static fun captureMessage (Ljava/lang/String;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public static fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public static fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; + public static fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)V public static fun captureUserFeedback (Lio/sentry/UserFeedback;)V public static fun clearBreadcrumbs ()V public static fun cloneMainHub ()Lio/sentry/IHub; @@ -1742,8 +1829,8 @@ public final class io/sentry/SentryAppStartProfilingOptions : io/sentry/JsonSeri public final class io/sentry/SentryAppStartProfilingOptions$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryAppStartProfilingOptions; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryAppStartProfilingOptions; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SentryAppStartProfilingOptions$JsonKeys { @@ -1809,7 +1896,7 @@ public abstract class io/sentry/SentryBaseEvent { public final class io/sentry/SentryBaseEvent$Deserializer { public fun ()V - public fun deserializeValue (Lio/sentry/SentryBaseEvent;Ljava/lang/String;Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Z + public fun deserializeValue (Lio/sentry/SentryBaseEvent;Ljava/lang/String;Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Z } public final class io/sentry/SentryBaseEvent$JsonKeys { @@ -1839,6 +1926,7 @@ public final class io/sentry/SentryClient : io/sentry/ISentryClient { public fun captureCheckIn (Lio/sentry/CheckIn;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureEvent (Lio/sentry/SentryEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; + public fun captureReplayEvent (Lio/sentry/SentryReplayEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureSession (Lio/sentry/Session;Lio/sentry/Hint;)V public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/IScope;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; public fun captureUserFeedback (Lio/sentry/UserFeedback;)V @@ -1899,8 +1987,8 @@ public final class io/sentry/SentryEnvelopeHeader : io/sentry/JsonSerializable, public final class io/sentry/SentryEnvelopeHeader$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryEnvelopeHeader; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryEnvelopeHeader; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SentryEnvelopeHeader$JsonKeys { @@ -1917,6 +2005,7 @@ public final class io/sentry/SentryEnvelopeItem { public static fun fromClientReport (Lio/sentry/ISerializer;Lio/sentry/clientreport/ClientReport;)Lio/sentry/SentryEnvelopeItem; public static fun fromEvent (Lio/sentry/ISerializer;Lio/sentry/SentryBaseEvent;)Lio/sentry/SentryEnvelopeItem; public static fun fromProfilingTrace (Lio/sentry/ProfilingTraceData;JLio/sentry/ISerializer;)Lio/sentry/SentryEnvelopeItem; + public static fun fromReplay (Lio/sentry/ISerializer;Lio/sentry/ILogger;Lio/sentry/SentryReplayEvent;Lio/sentry/ReplayRecording;)Lio/sentry/SentryEnvelopeItem; public static fun fromSession (Lio/sentry/ISerializer;Lio/sentry/Session;)Lio/sentry/SentryEnvelopeItem; public static fun fromUserFeedback (Lio/sentry/ISerializer;Lio/sentry/UserFeedback;)Lio/sentry/SentryEnvelopeItem; public fun getClientReport (Lio/sentry/ISerializer;)Lio/sentry/clientreport/ClientReport; @@ -1940,8 +2029,8 @@ public final class io/sentry/SentryEnvelopeItemHeader : io/sentry/JsonSerializab public final class io/sentry/SentryEnvelopeItemHeader$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryEnvelopeItemHeader; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryEnvelopeItemHeader; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SentryEnvelopeItemHeader$JsonKeys { @@ -1987,8 +2076,8 @@ public final class io/sentry/SentryEvent : io/sentry/SentryBaseEvent, io/sentry/ public final class io/sentry/SentryEvent$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryEvent; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryEvent; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SentryEvent$JsonKeys { @@ -2047,6 +2136,7 @@ public final class io/sentry/SentryItemType : java/lang/Enum, io/sentry/JsonSeri public static final field Profile Lio/sentry/SentryItemType; public static final field ReplayEvent Lio/sentry/SentryItemType; public static final field ReplayRecording Lio/sentry/SentryItemType; + public static final field ReplayVideo Lio/sentry/SentryItemType; public static final field Session Lio/sentry/SentryItemType; public static final field Transaction Lio/sentry/SentryItemType; public static final field Unknown Lio/sentry/SentryItemType; @@ -2097,8 +2187,8 @@ public final class io/sentry/SentryLockReason : io/sentry/JsonSerializable, io/s public final class io/sentry/SentryLockReason$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryLockReason; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryLockReason; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SentryLockReason$JsonKeys { @@ -2374,6 +2464,70 @@ public abstract interface class io/sentry/SentryOptions$TracesSamplerCallback { public abstract fun sample (Lio/sentry/SamplingContext;)Ljava/lang/Double; } +public final class io/sentry/SentryReplayEvent : io/sentry/SentryBaseEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown { + public static final field REPLAY_EVENT_TYPE Ljava/lang/String; + public static final field REPLAY_VIDEO_MAX_SIZE J + public fun ()V + public fun equals (Ljava/lang/Object;)Z + public fun getErrorIds ()Ljava/util/List; + public fun getReplayId ()Lio/sentry/protocol/SentryId; + public fun getReplayStartTimestamp ()Ljava/util/Date; + public fun getReplayType ()Lio/sentry/SentryReplayEvent$ReplayType; + public fun getSegmentId ()I + public fun getTimestamp ()Ljava/util/Date; + public fun getTraceIds ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun getUnknown ()Ljava/util/Map; + public fun getUrls ()Ljava/util/List; + public fun getVideoFile ()Ljava/io/File; + public fun hashCode ()I + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public fun setErrorIds (Ljava/util/List;)V + public fun setReplayId (Lio/sentry/protocol/SentryId;)V + public fun setReplayStartTimestamp (Ljava/util/Date;)V + public fun setReplayType (Lio/sentry/SentryReplayEvent$ReplayType;)V + public fun setSegmentId (I)V + public fun setTimestamp (Ljava/util/Date;)V + public fun setTraceIds (Ljava/util/List;)V + public fun setType (Ljava/lang/String;)V + public fun setUnknown (Ljava/util/Map;)V + public fun setUrls (Ljava/util/List;)V + public fun setVideoFile (Ljava/io/File;)V +} + +public final class io/sentry/SentryReplayEvent$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryReplayEvent; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + +public final class io/sentry/SentryReplayEvent$JsonKeys { + public static final field ERROR_IDS Ljava/lang/String; + public static final field REPLAY_ID Ljava/lang/String; + public static final field REPLAY_START_TIMESTAMP Ljava/lang/String; + public static final field REPLAY_TYPE Ljava/lang/String; + public static final field SEGMENT_ID Ljava/lang/String; + public static final field TIMESTAMP Ljava/lang/String; + public static final field TRACE_IDS Ljava/lang/String; + public static final field TYPE Ljava/lang/String; + public static final field URLS Ljava/lang/String; + public fun ()V +} + +public final class io/sentry/SentryReplayEvent$ReplayType : java/lang/Enum, io/sentry/JsonSerializable { + public static final field BUFFER Lio/sentry/SentryReplayEvent$ReplayType; + public static final field SESSION Lio/sentry/SentryReplayEvent$ReplayType; + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public static fun valueOf (Ljava/lang/String;)Lio/sentry/SentryReplayEvent$ReplayType; + public static fun values ()[Lio/sentry/SentryReplayEvent$ReplayType; +} + +public final class io/sentry/SentryReplayEvent$ReplayType$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SentryReplayEvent$ReplayType; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + public final class io/sentry/SentrySpanStorage { public fun get (Ljava/lang/String;)Lio/sentry/ISpan; public static fun getInstance ()Lio/sentry/SentrySpanStorage; @@ -2495,8 +2649,8 @@ public final class io/sentry/Session : io/sentry/JsonSerializable, io/sentry/Jso public final class io/sentry/Session$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/Session; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/Session; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/Session$JsonKeys { @@ -2617,8 +2771,8 @@ public class io/sentry/SpanContext : io/sentry/JsonSerializable, io/sentry/JsonU public final class io/sentry/SpanContext$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SpanContext; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SpanContext; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SpanContext$JsonKeys { @@ -2662,8 +2816,8 @@ public final class io/sentry/SpanId : io/sentry/JsonSerializable { public final class io/sentry/SpanId$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SpanId; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SpanId; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public class io/sentry/SpanOptions { @@ -2704,8 +2858,8 @@ public final class io/sentry/SpanStatus : java/lang/Enum, io/sentry/JsonSerializ public final class io/sentry/SpanStatus$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/SpanStatus; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/SpanStatus; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/SystemOutLogger : io/sentry/ILogger { @@ -2733,8 +2887,8 @@ public final class io/sentry/TraceContext : io/sentry/JsonSerializable, io/sentr public final class io/sentry/TraceContext$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/TraceContext; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/TraceContext; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/TraceContext$JsonKeys { @@ -2893,8 +3047,8 @@ public final class io/sentry/UserFeedback : io/sentry/JsonSerializable, io/sentr public final class io/sentry/UserFeedback$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/UserFeedback; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/UserFeedback; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/UserFeedback$JsonKeys { @@ -3005,8 +3159,8 @@ public final class io/sentry/clientreport/ClientReport : io/sentry/JsonSerializa public final class io/sentry/clientreport/ClientReport$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/clientreport/ClientReport; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/clientreport/ClientReport; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/clientreport/ClientReport$JsonKeys { @@ -3050,8 +3204,8 @@ public final class io/sentry/clientreport/DiscardedEvent : io/sentry/JsonSeriali public final class io/sentry/clientreport/DiscardedEvent$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/clientreport/DiscardedEvent; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/clientreport/DiscardedEvent; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/clientreport/DiscardedEvent$JsonKeys { @@ -3344,8 +3498,8 @@ public final class io/sentry/profilemeasurements/ProfileMeasurement : io/sentry/ public final class io/sentry/profilemeasurements/ProfileMeasurement$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/profilemeasurements/ProfileMeasurement; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/profilemeasurements/ProfileMeasurement; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/profilemeasurements/ProfileMeasurement$JsonKeys { @@ -3368,8 +3522,8 @@ public final class io/sentry/profilemeasurements/ProfileMeasurementValue : io/se public final class io/sentry/profilemeasurements/ProfileMeasurementValue$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/profilemeasurements/ProfileMeasurementValue; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/profilemeasurements/ProfileMeasurementValue; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/profilemeasurements/ProfileMeasurementValue$JsonKeys { @@ -3410,8 +3564,8 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr public final class io/sentry/protocol/App$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/App; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/App; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/App$JsonKeys { @@ -3444,8 +3598,8 @@ public final class io/sentry/protocol/Browser : io/sentry/JsonSerializable, io/s public final class io/sentry/protocol/Browser$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Browser; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Browser; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Browser$JsonKeys { @@ -3479,8 +3633,8 @@ public final class io/sentry/protocol/Contexts : java/util/concurrent/Concurrent public final class io/sentry/protocol/Contexts$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Contexts; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Contexts; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/DebugImage : io/sentry/JsonSerializable, io/sentry/JsonUnknown { @@ -3513,8 +3667,8 @@ public final class io/sentry/protocol/DebugImage : io/sentry/JsonSerializable, i public final class io/sentry/protocol/DebugImage$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/DebugImage; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/DebugImage; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/DebugImage$JsonKeys { @@ -3543,8 +3697,8 @@ public final class io/sentry/protocol/DebugMeta : io/sentry/JsonSerializable, io public final class io/sentry/protocol/DebugMeta$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/DebugMeta; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/DebugMeta; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/DebugMeta$JsonKeys { @@ -3633,8 +3787,8 @@ public final class io/sentry/protocol/Device : io/sentry/JsonSerializable, io/se public final class io/sentry/protocol/Device$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Device; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Device; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Device$DeviceOrientation : java/lang/Enum, io/sentry/JsonSerializable { @@ -3647,8 +3801,8 @@ public final class io/sentry/protocol/Device$DeviceOrientation : java/lang/Enum, public final class io/sentry/protocol/Device$DeviceOrientation$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Device$DeviceOrientation; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Device$DeviceOrientation; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Device$JsonKeys { @@ -3706,8 +3860,8 @@ public final class io/sentry/protocol/Geo : io/sentry/JsonSerializable, io/sentr public final class io/sentry/protocol/Geo$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Geo; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Geo; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Geo$JsonKeys { @@ -3747,8 +3901,8 @@ public final class io/sentry/protocol/Gpu : io/sentry/JsonSerializable, io/sentr public final class io/sentry/protocol/Gpu$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Gpu; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Gpu; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Gpu$JsonKeys { @@ -3783,8 +3937,8 @@ public final class io/sentry/protocol/MeasurementValue : io/sentry/JsonSerializa public final class io/sentry/protocol/MeasurementValue$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/MeasurementValue; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/MeasurementValue; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/MeasurementValue$JsonKeys { @@ -3817,8 +3971,8 @@ public final class io/sentry/protocol/Mechanism : io/sentry/JsonSerializable, io public final class io/sentry/protocol/Mechanism$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Mechanism; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Mechanism; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Mechanism$JsonKeys { @@ -3847,8 +4001,8 @@ public final class io/sentry/protocol/Message : io/sentry/JsonSerializable, io/s public final class io/sentry/protocol/Message$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Message; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Message; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Message$JsonKeys { @@ -3882,8 +4036,8 @@ public final class io/sentry/protocol/OperatingSystem : io/sentry/JsonSerializab public final class io/sentry/protocol/OperatingSystem$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/OperatingSystem; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/OperatingSystem; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/OperatingSystem$JsonKeys { @@ -3930,8 +4084,8 @@ public final class io/sentry/protocol/Request : io/sentry/JsonSerializable, io/s public final class io/sentry/protocol/Request$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Request; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Request; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Request$JsonKeys { @@ -3970,8 +4124,8 @@ public final class io/sentry/protocol/Response : io/sentry/JsonSerializable, io/ public final class io/sentry/protocol/Response$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Response; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Response; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/Response$JsonKeys { @@ -4000,8 +4154,8 @@ public final class io/sentry/protocol/SdkInfo : io/sentry/JsonSerializable, io/s public final class io/sentry/protocol/SdkInfo$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SdkInfo; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SdkInfo; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SdkInfo$JsonKeys { @@ -4034,8 +4188,8 @@ public final class io/sentry/protocol/SdkVersion : io/sentry/JsonSerializable, i public final class io/sentry/protocol/SdkVersion$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SdkVersion; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SdkVersion; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SdkVersion$JsonKeys { @@ -4067,8 +4221,8 @@ public final class io/sentry/protocol/SentryException : io/sentry/JsonSerializab public final class io/sentry/protocol/SentryException$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryException; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryException; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryException$JsonKeys { @@ -4094,8 +4248,8 @@ public final class io/sentry/protocol/SentryId : io/sentry/JsonSerializable { public final class io/sentry/protocol/SentryId$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryId; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryId; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryPackage : io/sentry/JsonSerializable, io/sentry/JsonUnknown { @@ -4113,8 +4267,8 @@ public final class io/sentry/protocol/SentryPackage : io/sentry/JsonSerializable public final class io/sentry/protocol/SentryPackage$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryPackage; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryPackage; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryPackage$JsonKeys { @@ -4139,8 +4293,8 @@ public final class io/sentry/protocol/SentryRuntime : io/sentry/JsonSerializable public final class io/sentry/protocol/SentryRuntime$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryRuntime; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryRuntime; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryRuntime$JsonKeys { @@ -4173,8 +4327,8 @@ public final class io/sentry/protocol/SentrySpan : io/sentry/JsonSerializable, i public final class io/sentry/protocol/SentrySpan$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentrySpan; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentrySpan; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentrySpan$JsonKeys { @@ -4243,8 +4397,8 @@ public final class io/sentry/protocol/SentryStackFrame : io/sentry/JsonSerializa public final class io/sentry/protocol/SentryStackFrame$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryStackFrame; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryStackFrame; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryStackFrame$JsonKeys { @@ -4284,8 +4438,8 @@ public final class io/sentry/protocol/SentryStackTrace : io/sentry/JsonSerializa public final class io/sentry/protocol/SentryStackTrace$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryStackTrace; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryStackTrace; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryStackTrace$JsonKeys { @@ -4324,8 +4478,8 @@ public final class io/sentry/protocol/SentryThread : io/sentry/JsonSerializable, public final class io/sentry/protocol/SentryThread$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryThread; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryThread; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryThread$JsonKeys { @@ -4362,8 +4516,8 @@ public final class io/sentry/protocol/SentryTransaction : io/sentry/SentryBaseEv public final class io/sentry/protocol/SentryTransaction$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryTransaction; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/SentryTransaction; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/SentryTransaction$JsonKeys { @@ -4386,8 +4540,8 @@ public final class io/sentry/protocol/TransactionInfo : io/sentry/JsonSerializab public final class io/sentry/protocol/TransactionInfo$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/TransactionInfo; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/TransactionInfo; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/TransactionInfo$JsonKeys { @@ -4438,8 +4592,8 @@ public final class io/sentry/protocol/User : io/sentry/JsonSerializable, io/sent public final class io/sentry/protocol/User$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/User; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/User; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/User$JsonKeys { @@ -4466,8 +4620,8 @@ public final class io/sentry/protocol/ViewHierarchy : io/sentry/JsonSerializable public final class io/sentry/protocol/ViewHierarchy$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/ViewHierarchy$JsonKeys { @@ -4507,8 +4661,8 @@ public final class io/sentry/protocol/ViewHierarchyNode : io/sentry/JsonSerializ public final class io/sentry/protocol/ViewHierarchyNode$Deserializer : io/sentry/JsonDeserializer { public fun ()V - public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchyNode; - public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchyNode; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; } public final class io/sentry/protocol/ViewHierarchyNode$JsonKeys { @@ -4526,6 +4680,152 @@ public final class io/sentry/protocol/ViewHierarchyNode$JsonKeys { public fun ()V } +public abstract class io/sentry/rrweb/RRWebEvent { + protected fun ()V + protected fun (Lio/sentry/rrweb/RRWebEventType;)V + public fun equals (Ljava/lang/Object;)Z + public fun getTimestamp ()J + public fun getType ()Lio/sentry/rrweb/RRWebEventType; + public fun hashCode ()I + public fun setTimestamp (J)V + public fun setType (Lio/sentry/rrweb/RRWebEventType;)V +} + +public final class io/sentry/rrweb/RRWebEvent$Deserializer { + public fun ()V + public fun deserializeValue (Lio/sentry/rrweb/RRWebEvent;Ljava/lang/String;Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Z +} + +public final class io/sentry/rrweb/RRWebEvent$JsonKeys { + public static final field TAG Ljava/lang/String; + public static final field TIMESTAMP Ljava/lang/String; + public static final field TYPE Ljava/lang/String; + public fun ()V +} + +public final class io/sentry/rrweb/RRWebEvent$Serializer { + public fun ()V + public fun serialize (Lio/sentry/rrweb/RRWebEvent;Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V +} + +public final class io/sentry/rrweb/RRWebEventType : java/lang/Enum, io/sentry/JsonSerializable { + public static final field Custom Lio/sentry/rrweb/RRWebEventType; + public static final field DomContentLoaded Lio/sentry/rrweb/RRWebEventType; + public static final field FullSnapshot Lio/sentry/rrweb/RRWebEventType; + public static final field IncrementalSnapshot Lio/sentry/rrweb/RRWebEventType; + public static final field Load Lio/sentry/rrweb/RRWebEventType; + public static final field Meta Lio/sentry/rrweb/RRWebEventType; + public static final field Plugin Lio/sentry/rrweb/RRWebEventType; + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public static fun valueOf (Ljava/lang/String;)Lio/sentry/rrweb/RRWebEventType; + public static fun values ()[Lio/sentry/rrweb/RRWebEventType; +} + +public final class io/sentry/rrweb/RRWebEventType$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/rrweb/RRWebEventType; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + +public final class io/sentry/rrweb/RRWebMetaEvent : io/sentry/rrweb/RRWebEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown { + public fun ()V + public fun equals (Ljava/lang/Object;)Z + public fun getDataUnknown ()Ljava/util/Map; + public fun getHeight ()I + public fun getHref ()Ljava/lang/String; + public fun getUnknown ()Ljava/util/Map; + public fun getWidth ()I + public fun hashCode ()I + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public fun setDataUnknown (Ljava/util/Map;)V + public fun setHeight (I)V + public fun setHref (Ljava/lang/String;)V + public fun setUnknown (Ljava/util/Map;)V + public fun setWidth (I)V +} + +public final class io/sentry/rrweb/RRWebMetaEvent$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/rrweb/RRWebMetaEvent; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + +public final class io/sentry/rrweb/RRWebMetaEvent$JsonKeys { + public static final field DATA Ljava/lang/String; + public static final field HEIGHT Ljava/lang/String; + public static final field HREF Ljava/lang/String; + public static final field WIDTH Ljava/lang/String; + public fun ()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; + public static final field REPLAY_ENCODING Ljava/lang/String; + public static final field REPLAY_FRAME_RATE_TYPE_CONSTANT Ljava/lang/String; + public static final field REPLAY_FRAME_RATE_TYPE_VARIABLE Ljava/lang/String; + public fun ()V + public fun equals (Ljava/lang/Object;)Z + public fun getContainer ()Ljava/lang/String; + public fun getDataUnknown ()Ljava/util/Map; + public fun getDuration ()I + public fun getEncoding ()Ljava/lang/String; + public fun getFrameCount ()I + public fun getFrameRate ()I + public fun getFrameRateType ()Ljava/lang/String; + public fun getHeight ()I + public fun getLeft ()I + public fun getPayloadUnknown ()Ljava/util/Map; + public fun getSegmentId ()I + public fun getSize ()J + public fun getTag ()Ljava/lang/String; + public fun getTop ()I + public fun getUnknown ()Ljava/util/Map; + public fun getWidth ()I + public fun hashCode ()I + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public fun setContainer (Ljava/lang/String;)V + public fun setDataUnknown (Ljava/util/Map;)V + public fun setDuration (I)V + public fun setEncoding (Ljava/lang/String;)V + public fun setFrameCount (I)V + public fun setFrameRate (I)V + public fun setFrameRateType (Ljava/lang/String;)V + public fun setHeight (I)V + public fun setLeft (I)V + public fun setPayloadUnknown (Ljava/util/Map;)V + public fun setSegmentId (I)V + public fun setSize (J)V + public fun setTag (Ljava/lang/String;)V + public fun setTop (I)V + public fun setUnknown (Ljava/util/Map;)V + public fun setWidth (I)V +} + +public final class io/sentry/rrweb/RRWebVideoEvent$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/rrweb/RRWebVideoEvent; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + +public final class io/sentry/rrweb/RRWebVideoEvent$JsonKeys { + public static final field CONTAINER Ljava/lang/String; + public static final field DATA Ljava/lang/String; + public static final field DURATION Ljava/lang/String; + public static final field ENCODING Ljava/lang/String; + public static final field FRAME_COUNT Ljava/lang/String; + public static final field FRAME_RATE Ljava/lang/String; + public static final field FRAME_RATE_TYPE Ljava/lang/String; + public static final field HEIGHT Ljava/lang/String; + public static final field LEFT Ljava/lang/String; + public static final field PAYLOAD Ljava/lang/String; + public static final field SEGMENT_ID Ljava/lang/String; + public static final field SIZE Ljava/lang/String; + public static final field TOP Ljava/lang/String; + public static final field WIDTH Ljava/lang/String; + public fun ()V +} + public final class io/sentry/transport/AsyncHttpTransport : io/sentry/transport/ITransport { public fun (Lio/sentry/SentryOptions;Lio/sentry/transport/RateLimiter;Lio/sentry/transport/ITransportGate;Lio/sentry/RequestDetails;)V public fun (Lio/sentry/transport/QueuedThreadPoolExecutor;Lio/sentry/SentryOptions;Lio/sentry/transport/RateLimiter;Lio/sentry/transport/ITransportGate;Lio/sentry/transport/HttpConnection;)V @@ -4727,6 +5027,40 @@ public final class io/sentry/util/LogUtils { public static fun logNotInstanceOf (Ljava/lang/Class;Ljava/lang/Object;Lio/sentry/ILogger;)V } +public final class io/sentry/util/MapObjectReader : io/sentry/ObjectReader { + public fun (Ljava/util/Map;)V + public fun beginArray ()V + public fun beginObject ()V + public fun close ()V + public fun endArray ()V + public fun endObject ()V + public fun hasNext ()Z + public fun nextBoolean ()Z + public fun nextBooleanOrNull ()Ljava/lang/Boolean; + public fun nextDateOrNull (Lio/sentry/ILogger;)Ljava/util/Date; + public fun nextDouble ()D + public fun nextDoubleOrNull ()Ljava/lang/Double; + public fun nextFloat ()F + public fun nextFloatOrNull ()Ljava/lang/Float; + public fun nextInt ()I + public fun nextIntegerOrNull ()Ljava/lang/Integer; + public fun nextListOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/List; + public fun nextLong ()J + public fun nextLongOrNull ()Ljava/lang/Long; + public fun nextMapOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/Map; + public fun nextName ()Ljava/lang/String; + public fun nextNull ()V + public fun nextObjectOrNull ()Ljava/lang/Object; + public fun nextOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/lang/Object; + public fun nextString ()Ljava/lang/String; + public fun nextStringOrNull ()Ljava/lang/String; + public fun nextTimeZoneOrNull (Lio/sentry/ILogger;)Ljava/util/TimeZone; + public fun nextUnknown (Lio/sentry/ILogger;Ljava/util/Map;Ljava/lang/String;)V + public fun peek ()Lio/sentry/vendor/gson/stream/JsonToken; + public fun setLenient (Z)V + public fun skipValue ()V +} + public final class io/sentry/util/MapObjectWriter : io/sentry/ObjectWriter { public fun (Ljava/util/Map;)V public synthetic fun beginArray ()Lio/sentry/ObjectWriter; @@ -4737,10 +5071,12 @@ public final class io/sentry/util/MapObjectWriter : io/sentry/ObjectWriter { public fun endArray ()Lio/sentry/util/MapObjectWriter; public synthetic fun endObject ()Lio/sentry/ObjectWriter; public fun endObject ()Lio/sentry/util/MapObjectWriter; + public fun jsonValue (Ljava/lang/String;)Lio/sentry/ObjectWriter; public synthetic fun name (Ljava/lang/String;)Lio/sentry/ObjectWriter; public fun name (Ljava/lang/String;)Lio/sentry/util/MapObjectWriter; public synthetic fun nullValue ()Lio/sentry/ObjectWriter; public fun nullValue ()Lio/sentry/util/MapObjectWriter; + public fun setLenient (Z)V public synthetic fun value (D)Lio/sentry/ObjectWriter; public fun value (D)Lio/sentry/util/MapObjectWriter; public synthetic fun value (J)Lio/sentry/ObjectWriter; diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index fe2055c336..da1453bc68 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -86,8 +86,7 @@ public static Breadcrumb fromMap( switch (entry.getKey()) { case JsonKeys.TIMESTAMP: if (value instanceof String) { - Date deserializedDate = - JsonObjectReader.dateOrNull((String) value, options.getLogger()); + Date deserializedDate = ObjectReader.dateOrNull((String) value, options.getLogger()); if (deserializedDate != null) { timestamp = deserializedDate; } @@ -700,8 +699,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull Breadcrumb deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull Breadcrumb deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { reader.beginObject(); @NotNull Date timestamp = DateUtils.getCurrentDateTime(); String message = null; diff --git a/sentry/src/main/java/io/sentry/CheckIn.java b/sentry/src/main/java/io/sentry/CheckIn.java index 4c83771324..e7c6abef3e 100644 --- a/sentry/src/main/java/io/sentry/CheckIn.java +++ b/sentry/src/main/java/io/sentry/CheckIn.java @@ -170,7 +170,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull CheckIn deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull CheckIn deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { SentryId sentryId = null; MonitorConfig monitorConfig = null; diff --git a/sentry/src/main/java/io/sentry/JsonDeserializer.java b/sentry/src/main/java/io/sentry/JsonDeserializer.java index 7e62814fe6..390328231b 100644 --- a/sentry/src/main/java/io/sentry/JsonDeserializer.java +++ b/sentry/src/main/java/io/sentry/JsonDeserializer.java @@ -6,5 +6,5 @@ @ApiStatus.Internal public interface JsonDeserializer { @NotNull - T deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception; + T deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception; } diff --git a/sentry/src/main/java/io/sentry/JsonObjectReader.java b/sentry/src/main/java/io/sentry/JsonObjectReader.java index 79c9a35420..c069a9e26d 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectReader.java +++ b/sentry/src/main/java/io/sentry/JsonObjectReader.java @@ -15,64 +15,74 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Internal -public final class JsonObjectReader extends JsonReader { +public final class JsonObjectReader implements ObjectReader { + + private final @NotNull JsonReader jsonReader; public JsonObjectReader(Reader in) { - super(in); + this.jsonReader = new JsonReader(in); } + @Override public @Nullable String nextStringOrNull() throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - return nextString(); + return jsonReader.nextString(); } + @Override public @Nullable Double nextDoubleOrNull() throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - return nextDouble(); + return jsonReader.nextDouble(); } + @Override public @Nullable Float nextFloatOrNull() throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } return nextFloat(); } - public @NotNull Float nextFloat() throws IOException { - return (float) nextDouble(); + @Override + public float nextFloat() throws IOException { + return (float) jsonReader.nextDouble(); } + @Override public @Nullable Long nextLongOrNull() throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - return nextLong(); + return jsonReader.nextLong(); } + @Override public @Nullable Integer nextIntegerOrNull() throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - return nextInt(); + return jsonReader.nextInt(); } + @Override public @Nullable Boolean nextBooleanOrNull() throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - return nextBoolean(); + return jsonReader.nextBoolean(); } + @Override public void nextUnknown(ILogger logger, Map unknown, String name) { try { unknown.put(name, nextObjectOrNull()); @@ -81,90 +91,79 @@ public void nextUnknown(ILogger logger, Map unknown, String name } } + @Override public @Nullable List nextListOrNull( @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - beginArray(); + jsonReader.beginArray(); List list = new ArrayList<>(); - if (hasNext()) { + if (jsonReader.hasNext()) { do { try { list.add(deserializer.deserialize(this, logger)); } catch (Exception e) { logger.log(SentryLevel.WARNING, "Failed to deserialize object in list.", e); } - } while (peek() == JsonToken.BEGIN_OBJECT); + } while (jsonReader.peek() == JsonToken.BEGIN_OBJECT); } - endArray(); + jsonReader.endArray(); return list; } + @Override public @Nullable Map nextMapOrNull( @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - beginObject(); + jsonReader.beginObject(); Map map = new HashMap<>(); - if (hasNext()) { + if (jsonReader.hasNext()) { do { try { - String key = nextName(); + String key = jsonReader.nextName(); map.put(key, deserializer.deserialize(this, logger)); } catch (Exception e) { logger.log(SentryLevel.WARNING, "Failed to deserialize object in map.", e); } - } while (peek() == JsonToken.BEGIN_OBJECT || peek() == JsonToken.NAME); + } while (jsonReader.peek() == JsonToken.BEGIN_OBJECT || jsonReader.peek() == JsonToken.NAME); } - endObject(); + jsonReader.endObject(); return map; } + @Override public @Nullable T nextOrNull( @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws Exception { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } return deserializer.deserialize(this, logger); } + @Override public @Nullable Date nextDateOrNull(ILogger logger) throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); - return null; - } - return JsonObjectReader.dateOrNull(nextString(), logger); - } - - public static @Nullable Date dateOrNull(@Nullable String dateString, ILogger logger) { - if (dateString == null) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } - try { - return DateUtils.getDateTime(dateString); - } catch (Exception ignored) { - try { - return DateUtils.getDateTimeWithMillisPrecision(dateString); - } catch (Exception e) { - logger.log(SentryLevel.ERROR, "Error when deserializing millis timestamp format.", e); - } - } - return null; + return ObjectReader.dateOrNull(jsonReader.nextString(), logger); } + @Override public @Nullable TimeZone nextTimeZoneOrNull(ILogger logger) throws IOException { - if (peek() == JsonToken.NULL) { - nextNull(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); return null; } try { - return TimeZone.getTimeZone(nextString()); + return TimeZone.getTimeZone(jsonReader.nextString()); } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error when deserializing TimeZone", e); } @@ -177,7 +176,88 @@ public void nextUnknown(ILogger logger, Map unknown, String name * * @return The deserialized object from json. */ + @Override public @Nullable Object nextObjectOrNull() throws IOException { return new JsonObjectDeserializer().deserialize(this); } + + @Override + public @NotNull JsonToken peek() throws IOException { + return jsonReader.peek(); + } + + @Override + public @NotNull String nextName() throws IOException { + return jsonReader.nextName(); + } + + @Override + public void beginObject() throws IOException { + jsonReader.beginObject(); + } + + @Override + public void endObject() throws IOException { + jsonReader.endObject(); + } + + @Override + public void beginArray() throws IOException { + jsonReader.beginArray(); + } + + @Override + public void endArray() throws IOException { + jsonReader.endArray(); + } + + @Override + public boolean hasNext() throws IOException { + return jsonReader.hasNext(); + } + + @Override + public int nextInt() throws IOException { + return jsonReader.nextInt(); + } + + @Override + public long nextLong() throws IOException { + return jsonReader.nextLong(); + } + + @Override + public String nextString() throws IOException { + return jsonReader.nextString(); + } + + @Override + public boolean nextBoolean() throws IOException { + return jsonReader.nextBoolean(); + } + + @Override + public double nextDouble() throws IOException { + return jsonReader.nextDouble(); + } + + @Override + public void nextNull() throws IOException { + jsonReader.nextNull(); + } + + @Override + public void setLenient(boolean lenient) { + jsonReader.setLenient(lenient); + } + + @Override + public void skipValue() throws IOException { + jsonReader.skipValue(); + } + + @Override + public void close() throws IOException { + jsonReader.close(); + } } diff --git a/sentry/src/main/java/io/sentry/MonitorConfig.java b/sentry/src/main/java/io/sentry/MonitorConfig.java index c25884c2bd..7d15eb26d3 100644 --- a/sentry/src/main/java/io/sentry/MonitorConfig.java +++ b/sentry/src/main/java/io/sentry/MonitorConfig.java @@ -105,8 +105,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull MonitorConfig deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull MonitorConfig deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { MonitorSchedule schedule = null; Long checkinMargin = null; Long maxRuntime = null; diff --git a/sentry/src/main/java/io/sentry/MonitorContexts.java b/sentry/src/main/java/io/sentry/MonitorContexts.java index 3a15aa4113..00ccb680fc 100644 --- a/sentry/src/main/java/io/sentry/MonitorContexts.java +++ b/sentry/src/main/java/io/sentry/MonitorContexts.java @@ -66,7 +66,7 @@ public static final class Deserializer implements JsonDeserializer { @Override public @NotNull MonitorSchedule deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { String type = null; String value = null; String unit = null; diff --git a/sentry/src/main/java/io/sentry/ObjectReader.java b/sentry/src/main/java/io/sentry/ObjectReader.java new file mode 100644 index 0000000000..0449379f7d --- /dev/null +++ b/sentry/src/main/java/io/sentry/ObjectReader.java @@ -0,0 +1,101 @@ +package io.sentry; + +import io.sentry.vendor.gson.stream.JsonToken; +import java.io.Closeable; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ObjectReader extends Closeable { + static @Nullable Date dateOrNull(@Nullable String dateString, final @NotNull ILogger logger) { + if (dateString == null) { + return null; + } + try { + return DateUtils.getDateTime(dateString); + } catch (Exception ignored) { + try { + return DateUtils.getDateTimeWithMillisPrecision(dateString); + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "Error when deserializing millis timestamp format.", e); + } + } + return null; + } + + void nextUnknown(ILogger logger, Map unknown, String name); + + @Nullable List nextListOrNull( + @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws IOException; + + @Nullable Map nextMapOrNull( + @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws IOException; + + @Nullable T nextOrNull(@NotNull ILogger logger, @NotNull JsonDeserializer deserializer) + throws Exception; + + @Nullable + Date nextDateOrNull(ILogger logger) throws IOException; + + @Nullable + TimeZone nextTimeZoneOrNull(ILogger logger) throws IOException; + + @Nullable + Object nextObjectOrNull() throws IOException; + + @NotNull + JsonToken peek() throws IOException; + + @NotNull + String nextName() throws IOException; + + void beginObject() throws IOException; + + void endObject() throws IOException; + + void beginArray() throws IOException; + + void endArray() throws IOException; + + boolean hasNext() throws IOException; + + int nextInt() throws IOException; + + @Nullable + Integer nextIntegerOrNull() throws IOException; + + long nextLong() throws IOException; + + @Nullable + Long nextLongOrNull() throws IOException; + + String nextString() throws IOException; + + @Nullable + String nextStringOrNull() throws IOException; + + boolean nextBoolean() throws IOException; + + @Nullable + Boolean nextBooleanOrNull() throws IOException; + + double nextDouble() throws IOException; + + @Nullable + Double nextDoubleOrNull() throws IOException; + + float nextFloat() throws IOException; + + @Nullable + Float nextFloatOrNull() throws IOException; + + void nextNull() throws IOException; + + void setLenient(boolean lenient); + + void skipValue() throws IOException; +} diff --git a/sentry/src/main/java/io/sentry/ProfilingTraceData.java b/sentry/src/main/java/io/sentry/ProfilingTraceData.java index 35902f5f04..9d54a7ee0e 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTraceData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTraceData.java @@ -448,7 +448,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; diff --git a/sentry/src/main/java/io/sentry/ProfilingTransactionData.java b/sentry/src/main/java/io/sentry/ProfilingTransactionData.java index 46ba9bba44..045b859f05 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTransactionData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTransactionData.java @@ -179,7 +179,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; diff --git a/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java b/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java index d98ec2c32f..a9828792d7 100644 --- a/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java +++ b/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java @@ -151,7 +151,7 @@ public static final class Deserializer @Override public @NotNull SentryAppStartProfilingOptions deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); SentryAppStartProfilingOptions options = new SentryAppStartProfilingOptions(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/SentryBaseEvent.java b/sentry/src/main/java/io/sentry/SentryBaseEvent.java index c247342cc2..58435194a7 100644 --- a/sentry/src/main/java/io/sentry/SentryBaseEvent.java +++ b/sentry/src/main/java/io/sentry/SentryBaseEvent.java @@ -395,7 +395,7 @@ public static final class Deserializer { public boolean deserializeValue( @NotNull SentryBaseEvent baseEvent, @NotNull String nextName, - @NotNull JsonObjectReader reader, + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { switch (nextName) { diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java b/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java index ceb7e7bdd5..3e9525d307 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java @@ -117,7 +117,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull SentryEnvelopeHeader deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); SentryId eventId = null; diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java index 1ca9a1c8c2..6903d9b1bb 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java @@ -130,7 +130,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull SentryEnvelopeItemHeader deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); String contentType = null; diff --git a/sentry/src/main/java/io/sentry/SentryEvent.java b/sentry/src/main/java/io/sentry/SentryEvent.java index 5bd1cf3877..d370458acb 100644 --- a/sentry/src/main/java/io/sentry/SentryEvent.java +++ b/sentry/src/main/java/io/sentry/SentryEvent.java @@ -311,8 +311,8 @@ public static final class Deserializer implements JsonDeserializer @SuppressWarnings("unchecked") @Override - public @NotNull SentryEvent deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SentryEvent deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { reader.beginObject(); SentryEvent event = new SentryEvent(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index ab5fb3bc73..b0d9c62e90 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -65,7 +65,7 @@ static final class Deserializer implements JsonDeserializer { @Override public @NotNull SentryItemType deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { return SentryItemType.valueOfLabel(reader.nextString().toLowerCase(Locale.ROOT)); } } diff --git a/sentry/src/main/java/io/sentry/SentryLevel.java b/sentry/src/main/java/io/sentry/SentryLevel.java index ac179c9831..f1c6e04cb8 100644 --- a/sentry/src/main/java/io/sentry/SentryLevel.java +++ b/sentry/src/main/java/io/sentry/SentryLevel.java @@ -21,8 +21,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SentryLevel deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SentryLevel deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { return SentryLevel.valueOf(reader.nextString().toUpperCase(Locale.ROOT)); } } diff --git a/sentry/src/main/java/io/sentry/SentryLockReason.java b/sentry/src/main/java/io/sentry/SentryLockReason.java index f376317f6b..bd04f48ab0 100644 --- a/sentry/src/main/java/io/sentry/SentryLockReason.java +++ b/sentry/src/main/java/io/sentry/SentryLockReason.java @@ -147,7 +147,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; reader.beginObject(); diff --git a/sentry/src/main/java/io/sentry/Session.java b/sentry/src/main/java/io/sentry/Session.java index 500da919fe..482b055b67 100644 --- a/sentry/src/main/java/io/sentry/Session.java +++ b/sentry/src/main/java/io/sentry/Session.java @@ -426,7 +426,7 @@ public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull Session deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Session deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index be428708cb..5a43ff845e 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -292,8 +292,8 @@ public void setUnknown(@Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull SpanContext deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SpanContext deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { reader.beginObject(); SentryId traceId = null; SpanId spanId = null; diff --git a/sentry/src/main/java/io/sentry/SpanId.java b/sentry/src/main/java/io/sentry/SpanId.java index 7e221775ce..70608fb7cb 100644 --- a/sentry/src/main/java/io/sentry/SpanId.java +++ b/sentry/src/main/java/io/sentry/SpanId.java @@ -53,7 +53,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SpanId deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull SpanId deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { return new SpanId(reader.nextString()); } diff --git a/sentry/src/main/java/io/sentry/SpanStatus.java b/sentry/src/main/java/io/sentry/SpanStatus.java index b0b1bf78c8..5185d27e05 100644 --- a/sentry/src/main/java/io/sentry/SpanStatus.java +++ b/sentry/src/main/java/io/sentry/SpanStatus.java @@ -114,8 +114,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SpanStatus deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SpanStatus deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { return SpanStatus.valueOf(reader.nextString().toUpperCase(Locale.ROOT)); } } diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index ef2944a9e9..df799aaa07 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -141,7 +141,7 @@ public static final class JsonKeys { public static final class Deserializer implements JsonDeserializer { @Override public @NotNull TraceContextUser deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); String id = null; @@ -239,8 +239,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull TraceContext deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull TraceContext deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { reader.beginObject(); SentryId traceId = null; diff --git a/sentry/src/main/java/io/sentry/UserFeedback.java b/sentry/src/main/java/io/sentry/UserFeedback.java index 27086188fe..b580744ee7 100644 --- a/sentry/src/main/java/io/sentry/UserFeedback.java +++ b/sentry/src/main/java/io/sentry/UserFeedback.java @@ -174,8 +174,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull UserFeedback deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull UserFeedback deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { SentryId sentryId = null; String name = null; String email = null; diff --git a/sentry/src/main/java/io/sentry/clientreport/ClientReport.java b/sentry/src/main/java/io/sentry/clientreport/ClientReport.java index 66c3188116..e1b8abcaea 100644 --- a/sentry/src/main/java/io/sentry/clientreport/ClientReport.java +++ b/sentry/src/main/java/io/sentry/clientreport/ClientReport.java @@ -3,9 +3,9 @@ import io.sentry.DateUtils; import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.vendor.gson.stream.JsonToken; @@ -74,8 +74,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull ClientReport deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull ClientReport deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { Date timestamp = null; List discardedEvents = new ArrayList<>(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java b/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java index 8fb5da3165..10b12b0fed 100644 --- a/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java +++ b/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.vendor.gson.stream.JsonToken; @@ -93,7 +93,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull DiscardedEvent deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { String reason = null; String category = null; Long quanity = null; diff --git a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java index 94e77edbfb..1e6ff5fb41 100644 --- a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java +++ b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -118,7 +118,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; diff --git a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java index 9639ba892f..b0cebf5439 100644 --- a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java +++ b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -92,7 +92,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/App.java b/sentry/src/main/java/io/sentry/protocol/App.java index b7b41638db..90d7d7aff6 100644 --- a/sentry/src/main/java/io/sentry/protocol/App.java +++ b/sentry/src/main/java/io/sentry/protocol/App.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -255,7 +255,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull App deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull App deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); App app = new App(); diff --git a/sentry/src/main/java/io/sentry/protocol/Browser.java b/sentry/src/main/java/io/sentry/protocol/Browser.java index 99fe427c27..ed32be5ea2 100644 --- a/sentry/src/main/java/io/sentry/protocol/Browser.java +++ b/sentry/src/main/java/io/sentry/protocol/Browser.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -102,7 +102,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull Browser deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Browser deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); Browser browser = new Browser(); diff --git a/sentry/src/main/java/io/sentry/protocol/Contexts.java b/sentry/src/main/java/io/sentry/protocol/Contexts.java index 21be9fd8a5..28d2e8d2a4 100644 --- a/sentry/src/main/java/io/sentry/protocol/Contexts.java +++ b/sentry/src/main/java/io/sentry/protocol/Contexts.java @@ -2,8 +2,8 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SpanContext; import io.sentry.util.HintUtils; @@ -160,7 +160,7 @@ public static final class Deserializer implements JsonDeserializer { @Override public @NotNull Contexts deserialize( - final @NotNull JsonObjectReader reader, final @NotNull ILogger logger) throws Exception { + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { final Contexts contexts = new Contexts(); reader.beginObject(); while (reader.peek() == JsonToken.NAME) { diff --git a/sentry/src/main/java/io/sentry/protocol/DebugImage.java b/sentry/src/main/java/io/sentry/protocol/DebugImage.java index d26432033e..e769e2c2ca 100644 --- a/sentry/src/main/java/io/sentry/protocol/DebugImage.java +++ b/sentry/src/main/java/io/sentry/protocol/DebugImage.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -314,8 +314,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull DebugImage deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull DebugImage deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { DebugImage debugImage = new DebugImage(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/DebugMeta.java b/sentry/src/main/java/io/sentry/protocol/DebugMeta.java index 134947507a..458c4de631 100644 --- a/sentry/src/main/java/io/sentry/protocol/DebugMeta.java +++ b/sentry/src/main/java/io/sentry/protocol/DebugMeta.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -95,7 +95,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull DebugMeta deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull DebugMeta deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { DebugMeta debugMeta = new DebugMeta(); diff --git a/sentry/src/main/java/io/sentry/protocol/Device.java b/sentry/src/main/java/io/sentry/protocol/Device.java index 4f06f74995..25cfa41fd1 100644 --- a/sentry/src/main/java/io/sentry/protocol/Device.java +++ b/sentry/src/main/java/io/sentry/protocol/Device.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -544,7 +544,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull DeviceOrientation deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { return DeviceOrientation.valueOf(reader.nextString().toUpperCase(Locale.ROOT)); } } @@ -726,7 +726,7 @@ public void setUnknown(@Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull Device deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Device deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); Device device = new Device(); diff --git a/sentry/src/main/java/io/sentry/protocol/Geo.java b/sentry/src/main/java/io/sentry/protocol/Geo.java index fefc340e1b..6042b72d1d 100644 --- a/sentry/src/main/java/io/sentry/protocol/Geo.java +++ b/sentry/src/main/java/io/sentry/protocol/Geo.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -161,7 +161,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public Geo deserialize(JsonObjectReader reader, ILogger logger) throws Exception { + public Geo deserialize(@NotNull ObjectReader reader, ILogger logger) throws Exception { reader.beginObject(); final Geo geo = new Geo(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/Gpu.java b/sentry/src/main/java/io/sentry/protocol/Gpu.java index 0dfe85f68f..b4a8344e2d 100644 --- a/sentry/src/main/java/io/sentry/protocol/Gpu.java +++ b/sentry/src/main/java/io/sentry/protocol/Gpu.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -229,7 +229,7 @@ public void setUnknown(@Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull Gpu deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Gpu deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); Gpu gpu = new Gpu(); diff --git a/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java b/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java index 5ba28f4126..85cf6cf40f 100644 --- a/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java +++ b/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.vendor.gson.stream.JsonToken; @@ -101,7 +101,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull MeasurementValue deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); String unit = null; diff --git a/sentry/src/main/java/io/sentry/protocol/Mechanism.java b/sentry/src/main/java/io/sentry/protocol/Mechanism.java index 648aed39c2..fac8808f2d 100644 --- a/sentry/src/main/java/io/sentry/protocol/Mechanism.java +++ b/sentry/src/main/java/io/sentry/protocol/Mechanism.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; @@ -205,7 +205,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull Mechanism deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Mechanism deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { Mechanism mechanism = new Mechanism(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/Message.java b/sentry/src/main/java/io/sentry/protocol/Message.java index a1c79e2198..9aceea56a6 100644 --- a/sentry/src/main/java/io/sentry/protocol/Message.java +++ b/sentry/src/main/java/io/sentry/protocol/Message.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; @@ -131,7 +131,7 @@ public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull Message deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Message deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); Message message = new Message(); diff --git a/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java b/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java index 796a4ea1a0..ecfb59542b 100644 --- a/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java +++ b/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -180,7 +180,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/Request.java b/sentry/src/main/java/io/sentry/protocol/Request.java index 14f5403844..44e205a390 100644 --- a/sentry/src/main/java/io/sentry/protocol/Request.java +++ b/sentry/src/main/java/io/sentry/protocol/Request.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -326,7 +326,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger @SuppressWarnings("unchecked") public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull Request deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull Request deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); Request request = new Request(); diff --git a/sentry/src/main/java/io/sentry/protocol/Response.java b/sentry/src/main/java/io/sentry/protocol/Response.java index 23a16c78f8..f1a9303710 100644 --- a/sentry/src/main/java/io/sentry/protocol/Response.java +++ b/sentry/src/main/java/io/sentry/protocol/Response.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; @@ -154,7 +154,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull Response deserialize( - final @NotNull JsonObjectReader reader, final @NotNull ILogger logger) throws Exception { + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { reader.beginObject(); final Response response = new Response(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/SdkInfo.java b/sentry/src/main/java/io/sentry/protocol/SdkInfo.java index ee3ac1eb16..928a8b522d 100644 --- a/sentry/src/main/java/io/sentry/protocol/SdkInfo.java +++ b/sentry/src/main/java/io/sentry/protocol/SdkInfo.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -116,7 +116,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SdkInfo deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull SdkInfo deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { SdkInfo sdkInfo = new SdkInfo(); diff --git a/sentry/src/main/java/io/sentry/protocol/SdkVersion.java b/sentry/src/main/java/io/sentry/protocol/SdkVersion.java index f7ba230463..aa997910be 100644 --- a/sentry/src/main/java/io/sentry/protocol/SdkVersion.java +++ b/sentry/src/main/java/io/sentry/protocol/SdkVersion.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SentryLevel; @@ -224,8 +224,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger @SuppressWarnings("unchecked") public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SdkVersion deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SdkVersion deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { String name = null; String version = null; diff --git a/sentry/src/main/java/io/sentry/protocol/SentryException.java b/sentry/src/main/java/io/sentry/protocol/SentryException.java index 5ee9464a3c..4d56e12747 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryException.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryException.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -223,7 +223,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; reader.beginObject(); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryId.java b/sentry/src/main/java/io/sentry/protocol/SentryId.java index c1e5ea1819..109655fdf2 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryId.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryId.java @@ -2,8 +2,8 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.StringUtils; import java.io.IOException; @@ -82,7 +82,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SentryId deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull SentryId deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { return new SentryId(reader.nextString()); } diff --git a/sentry/src/main/java/io/sentry/protocol/SentryPackage.java b/sentry/src/main/java/io/sentry/protocol/SentryPackage.java index cea6bb8497..aa2358d8df 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryPackage.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryPackage.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.util.Objects; @@ -100,8 +100,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SentryPackage deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SentryPackage deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { String name = null; String version = null; diff --git a/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java b/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java index 751e664ae6..7d2ed8fa1e 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; @@ -110,8 +110,8 @@ public void setUnknown(@Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull SentryRuntime deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SentryRuntime deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { reader.beginObject(); SentryRuntime runtime = new SentryRuntime(); Map unknown = null; diff --git a/sentry/src/main/java/io/sentry/protocol/SentrySpan.java b/sentry/src/main/java/io/sentry/protocol/SentrySpan.java index b627aa4c9f..8e4396ac44 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentrySpan.java +++ b/sentry/src/main/java/io/sentry/protocol/SentrySpan.java @@ -3,9 +3,9 @@ import io.sentry.DateUtils; import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.Span; @@ -218,8 +218,8 @@ public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull SentrySpan deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SentrySpan deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { reader.beginObject(); Double startTimestamp = null; diff --git a/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java b/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java index fcb93eb2e8..03d64e2172 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLockReason; import io.sentry.vendor.gson.stream.JsonToken; @@ -398,7 +398,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @Override public @NotNull SentryStackFrame deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { SentryStackFrame sentryStackFrame = new SentryStackFrame(); Map unknown = null; reader.beginObject(); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java b/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java index 90b42666c8..e79e8e7ec0 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; @@ -154,7 +154,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; reader.beginObject(); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryThread.java b/sentry/src/main/java/io/sentry/protocol/SentryThread.java index 1d57e35b10..accb05968e 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryThread.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryThread.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryLockReason; import io.sentry.vendor.gson.stream.JsonToken; @@ -303,8 +303,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull SentryThread deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull SentryThread deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { SentryThread sentryThread = new SentryThread(); Map unknown = null; reader.beginObject(); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java index d0c9271a26..791fda1799 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java @@ -3,9 +3,9 @@ import io.sentry.DateUtils; import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SentryBaseEvent; import io.sentry.SentryTracer; @@ -231,7 +231,7 @@ public static final class Deserializer implements JsonDeserializer { @SuppressWarnings("unchecked") @Override - public @NotNull User deserialize(@NotNull JsonObjectReader reader, @NotNull ILogger logger) + public @NotNull User deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { reader.beginObject(); User user = new User(); diff --git a/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java b/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java index 69e5156040..791c9bbbd6 100644 --- a/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java +++ b/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -73,8 +73,8 @@ public void setUnknown(@Nullable Map unknown) { public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull ViewHierarchy deserialize( - @NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception { + public @NotNull ViewHierarchy deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { @Nullable String renderingSystem = null; @Nullable List windows = null; diff --git a/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java b/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java index 923eb95877..525d644fdc 100644 --- a/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java +++ b/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java @@ -2,9 +2,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; -import io.sentry.JsonObjectReader; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -205,7 +205,7 @@ public static final class Deserializer implements JsonDeserializer unknown = null; @NotNull final ViewHierarchyNode node = new ViewHierarchyNode(); diff --git a/sentry/src/main/java/io/sentry/util/MapObjectReader.java b/sentry/src/main/java/io/sentry/util/MapObjectReader.java new file mode 100644 index 0000000000..ba2cb83ed3 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/MapObjectReader.java @@ -0,0 +1,350 @@ +package io.sentry.util; + +import io.sentry.ILogger; +import io.sentry.JsonDeserializer; +import io.sentry.ObjectReader; +import io.sentry.SentryLevel; +import io.sentry.vendor.gson.stream.JsonToken; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayDeque; +import java.util.Date; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("unchecked") +public final class MapObjectReader implements ObjectReader { + + private final Deque> stack; + + public MapObjectReader(final Map root) { + stack = new ArrayDeque<>(); + stack.addLast(new AbstractMap.SimpleEntry<>(null, root)); + } + + @Override + public void nextUnknown(final @NotNull ILogger logger, Map unknown, String name) { + try { + unknown.put(name, nextObjectOrNull()); + } catch (Exception exception) { + logger.log(SentryLevel.ERROR, exception, "Error deserializing unknown key: %s", name); + } + } + + @Nullable + @Override + public List nextListOrNull( + final @NotNull ILogger logger, final @NotNull JsonDeserializer deserializer) + throws IOException { + return nextValueOrNull(); + } + + @Nullable + @Override + public Map nextMapOrNull( + final @NotNull ILogger logger, final @NotNull JsonDeserializer deserializer) + throws IOException { + return nextValueOrNull(); + } + + @Nullable + @Override + public T nextOrNull( + final @NotNull ILogger logger, final @NotNull JsonDeserializer deserializer) + throws Exception { + return nextValueOrNull(logger, deserializer); + } + + @Nullable + @Override + public Date nextDateOrNull(final @NotNull ILogger logger) throws IOException { + String dateString = nextStringOrNull(); + return ObjectReader.dateOrNull(dateString, logger); + } + + @Nullable + @Override + public TimeZone nextTimeZoneOrNull(final @NotNull ILogger logger) throws IOException { + String timeZoneId = nextStringOrNull(); + return timeZoneId != null ? TimeZone.getTimeZone(timeZoneId) : null; + } + + @Nullable + @Override + public Object nextObjectOrNull() throws IOException { + return nextValueOrNull(); + } + + @NotNull + @Override + public JsonToken peek() throws IOException { + if (stack.isEmpty()) { + return JsonToken.END_DOCUMENT; + } + + Map.Entry currentEntry = stack.peekLast(); + if (currentEntry == null) { + return JsonToken.END_DOCUMENT; + } + + if (currentEntry.getKey() != null) { + return JsonToken.NAME; + } + + Object value = currentEntry.getValue(); + + if (value instanceof Map) { + return JsonToken.BEGIN_OBJECT; + } else if (value instanceof List) { + return JsonToken.BEGIN_ARRAY; + } else if (value instanceof String) { + return JsonToken.STRING; + } else if (value instanceof Number) { + return JsonToken.NUMBER; + } else if (value instanceof Boolean) { + return JsonToken.BOOLEAN; + } else if (value instanceof JsonToken) { + return (JsonToken) value; + } else { + return JsonToken.END_DOCUMENT; + } + } + + @NotNull + @Override + public String nextName() throws IOException { + Map.Entry currentEntry = stack.peekLast(); + if (currentEntry != null && currentEntry.getKey() != null) { + return currentEntry.getKey(); + } + throw new IOException("Expected a name but was " + peek()); + } + + @Override + public void beginObject() throws IOException { + Map.Entry currentEntry = stack.removeLast(); + if (currentEntry == null) { + throw new IOException("No more entries"); + } + Object value = currentEntry.getValue(); + if (value instanceof Map) { + // insert a dummy entry to indicate end of an object + stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_OBJECT)); + // extract map entries onto the stack + for (Map.Entry entry : ((Map) value).entrySet()) { + stack.addLast(entry); + } + } else { + throw new IOException("Current token is not an object"); + } + } + + @Override + public void endObject() throws IOException { + if (stack.size() > 1) { + stack.removeLast(); // Pop the current map from stack + } + } + + @Override + public void beginArray() throws IOException { + Map.Entry currentEntry = stack.removeLast(); + if (currentEntry == null) { + throw new IOException("No more entries"); + } + Object value = currentEntry.getValue(); + if (value instanceof List) { + // insert a dummy entry to indicate end of an object + stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_ARRAY)); + // extract map entries onto the stack + for (Object entry : (List) value) { + stack.addLast(new AbstractMap.SimpleEntry<>(null, entry)); + } + } else { + throw new IOException("Current token is not an object"); + } + } + + @Override + public void endArray() throws IOException { + if (stack.size() > 1) { + stack.removeLast(); // Pop the current array from stack + } + } + + @Override + public boolean hasNext() throws IOException { + return !stack.isEmpty(); + } + + @Override + public int nextInt() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).intValue(); + } else { + throw new IOException("Expected int"); + } + } + + @Nullable + @Override + public Integer nextIntegerOrNull() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return null; + } + + @Override + public long nextLong() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).longValue(); + } else { + throw new IOException("Expected long"); + } + } + + @Nullable + @Override + public Long nextLongOrNull() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return null; + } + + @Override + public String nextString() throws IOException { + String value = nextValueOrNull(); + if (value != null) { + return value; + } else { + throw new IOException("Expected string"); + } + } + + @Nullable + @Override + public String nextStringOrNull() throws IOException { + return nextValueOrNull(); + } + + @Override + public boolean nextBoolean() throws IOException { + Boolean value = nextValueOrNull(); + if (value != null) { + return value; + } else { + throw new IOException("Expected boolean"); + } + } + + @Nullable + @Override + public Boolean nextBooleanOrNull() throws IOException { + return nextValueOrNull(); + } + + @Override + public double nextDouble() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else { + throw new IOException("Expected double"); + } + } + + @Nullable + @Override + public Double nextDoubleOrNull() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + return null; + } + + @Nullable + @Override + public Float nextFloatOrNull() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + return null; + } + + @Override + public float nextFloat() throws IOException { + Object value = nextValueOrNull(); + if (value instanceof Number) { + return ((Number) value).floatValue(); + } else { + throw new IOException("Expected float"); + } + } + + @Override + public void nextNull() throws IOException { + nextValueOrNull(); + } + + @Override + public void setLenient(boolean lenient) {} + + @Override + public void skipValue() throws IOException {} + + @SuppressWarnings("TypeParameterUnusedInFormals") + @Nullable + private T nextValueOrNull() throws IOException { + try { + return nextValueOrNull(null, null); + } catch (Exception e) { + throw new IOException(e); + } + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + @Nullable + private T nextValueOrNull( + final @Nullable ILogger logger, final @Nullable JsonDeserializer deserializer) + throws Exception { + Map.Entry currentEntry = stack.peekLast(); + if (currentEntry == null) { + return null; + } + T value = (T) currentEntry.getValue(); + if (deserializer != null && logger != null) { + return deserializer.deserialize(this, logger); + } else if (value instanceof List) { + List list = (List) value; + if (!list.isEmpty()) { + T next = (T) list.remove(0); + if (next instanceof Map) { + stack.addLast(new AbstractMap.SimpleEntry<>(null, next)); + } + return next; + } + } else if (value instanceof Map) { + stack.addLast(new AbstractMap.SimpleEntry<>(null, value)); + return value; + } + stack.removeLast(); + return value; + } + + @Override + public void close() throws IOException { + stack.clear(); + } +} diff --git a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java index 26f80eddc2..0bbc70a779 100644 --- a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java +++ b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java @@ -120,6 +120,11 @@ public MapObjectWriter value(final @NotNull ILogger logger, final @Nullable Obje return this; } + @Override + public void setLenient(boolean lenient) { + // no-op + } + @Override public MapObjectWriter beginArray() throws IOException { stack.add(new ArrayList<>()); @@ -151,6 +156,12 @@ public MapObjectWriter value(final @Nullable String value) throws IOException { return this; } + @Override + public ObjectWriter jsonValue(@Nullable String value) throws IOException { + // no-op + return this; + } + @Override public MapObjectWriter nullValue() throws IOException { postValue((Object) null); diff --git a/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt b/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt index 976c88a1cd..ec18b4ed5c 100644 --- a/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt +++ b/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt @@ -285,7 +285,7 @@ class JsonObjectReaderTest { var bar: String? = null ) { class Deserializer : JsonDeserializer { - override fun deserialize(reader: JsonObjectReader, logger: ILogger): Deserializable { + override fun deserialize(reader: ObjectReader, logger: ILogger): Deserializable { return Deserializable().apply { reader.beginObject() reader.nextName() diff --git a/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt index 9817897651..efc5e5cadf 100644 --- a/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt +++ b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt @@ -1,6 +1,8 @@ package io.sentry import io.sentry.exception.SentryEnvelopeException +import io.sentry.protocol.ReplayRecordingSerializationTest +import io.sentry.protocol.SentryReplayEventSerializationTest import io.sentry.protocol.User import io.sentry.protocol.ViewHierarchy import io.sentry.test.injectForField @@ -10,12 +12,15 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.msgpack.core.MessagePack import java.io.BufferedWriter import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException +import java.io.InputStreamReader import java.io.OutputStreamWriter import java.nio.charset.Charset +import java.nio.file.Files import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals @@ -66,7 +71,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with bytes`() { val attachment = Attachment(fixture.bytesAllowed, fixture.filename) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, fixture.bytesAllowed, item) } @@ -78,7 +88,12 @@ class SentryEnvelopeItemTest { val attachment = Attachment(viewHierarchy, fixture.filename, "text/plain", null, false) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, viewHierarchySerialized, item) } @@ -87,7 +102,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with attachmentType`() { val attachment = Attachment(fixture.pathname, fixture.filename, "", true, "event.minidump") - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertEquals("event.minidump", item.header.attachmentType) } @@ -98,7 +118,12 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytesAllowed) val attachment = Attachment(file.path) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, fixture.bytesAllowed, item) } @@ -110,7 +135,12 @@ class SentryEnvelopeItemTest { file.writeBytes(twoMB) val attachment = Attachment(file.absolutePath) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, twoMB, item) } @@ -119,7 +149,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with non existent file`() { val attachment = Attachment("I don't exist", "file.txt") - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Reading the attachment ${attachment.pathname} failed, because the file located at " + @@ -139,7 +174,12 @@ class SentryEnvelopeItemTest { if (changedFileReadPermission) { val attachment = Attachment(file.path, "file.txt") - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Reading the attachment ${attachment.pathname} failed, " + @@ -162,7 +202,12 @@ class SentryEnvelopeItemTest { val securityManager = DenyReadFileSecurityManager(fixture.pathname) System.setSecurityManager(securityManager) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith("Reading the attachment ${attachment.pathname} failed.") { item.data @@ -181,7 +226,12 @@ class SentryEnvelopeItemTest { // reflection instead. attachment.injectForField("pathname", null) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Couldn't attach the attachment ${attachment.filename}.\n" + @@ -196,7 +246,12 @@ class SentryEnvelopeItemTest { val image = this::class.java.classLoader.getResource("Tongariro.jpg")!! val attachment = Attachment(image.path) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, image.readBytes(), item) } @@ -204,7 +259,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with bytes too big`() { val attachment = Attachment(fixture.bytesTooBig, fixture.filename) val exception = assertFailsWith { - SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data + SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ).data } assertEquals( @@ -227,7 +287,12 @@ class SentryEnvelopeItemTest { val attachment = Attachment(serializable, fixture.filename, "text/plain", null, false) val exception = assertFailsWith { - SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data + SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ).data } assertEquals( @@ -246,7 +311,12 @@ class SentryEnvelopeItemTest { val attachment = Attachment(file.path) val exception = assertFailsWith { - SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data + SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ).data } assertEquals( @@ -261,7 +331,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with bytesFrom serializable are null`() { val attachment = Attachment(mock(), "mock-file-name", null, null, false) - val item = SentryEnvelopeItem.fromAttachment(fixture.errorSerializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.errorSerializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Couldn't attach the attachment ${attachment.filename}.\n" + @@ -279,8 +354,13 @@ class SentryEnvelopeItemTest { } file.writeBytes(fixture.bytes) - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, fixture.serializer).data - verify(profilingTraceData).sampledProfile = Base64.encodeToString(fixture.bytes, Base64.NO_WRAP or Base64.NO_PADDING) + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + fixture.serializer + ).data + verify(profilingTraceData).sampledProfile = + Base64.encodeToString(fixture.bytes, Base64.NO_WRAP or Base64.NO_PADDING) } @Test @@ -292,7 +372,11 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytes) assert(file.exists()) - val traceData = SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()) + val traceData = SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ) assert(file.exists()) traceData.data assertFalse(file.exists()) @@ -306,7 +390,11 @@ class SentryEnvelopeItemTest { } assertFailsWith("Dropping profiling trace data, because the file ${file.path} doesn't exists") { - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ).data } } @@ -319,7 +407,11 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytes) file.setReadable(false) assertFailsWith("Dropping profiling trace data, because the file ${file.path} doesn't exists") { - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ).data } } @@ -331,7 +423,11 @@ class SentryEnvelopeItemTest { whenever(it.traceFile).thenReturn(file) } - val traceData = SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()) + val traceData = SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ) assertFailsWith("Profiling trace file is empty") { traceData.data } @@ -346,7 +442,11 @@ class SentryEnvelopeItemTest { } val exception = assertFailsWith { - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ).data } assertEquals( @@ -357,6 +457,58 @@ class SentryEnvelopeItemTest { ) } + @Test + fun `fromReplay encodes payload into msgpack`() { + val file = Files.createTempFile("replay", "").toFile() + val videoBytes = + this::class.java.classLoader.getResource("Tongariro.jpg")!!.readBytes() + file.writeBytes(videoBytes) + + val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut().apply { + videoFile = file + } + val replayRecording = ReplayRecordingSerializationTest.Fixture().getSut() + val replayItem = SentryEnvelopeItem + .fromReplay(fixture.serializer, fixture.options.logger, replayEvent, replayRecording) + + assertEquals(SentryItemType.ReplayVideo, replayItem.header.type) + + assertPayload(replayItem, replayEvent, replayRecording, videoBytes) + } + + @Test + fun `fromReplay does not add video item when no bytes`() { + val file = File(fixture.pathname) + file.writeBytes(ByteArray(0)) + + val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut().apply { + videoFile = file + } + + val replayItem = SentryEnvelopeItem + .fromReplay(fixture.serializer, fixture.options.logger, replayEvent, null) + replayItem.data + assertPayload(replayItem, replayEvent, null, ByteArray(0)) { mapSize -> + assertEquals(1, mapSize) + } + } + + @Test + fun `fromReplay deletes file only after reading data`() { + val file = File(fixture.pathname) + val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut().apply { + videoFile = file + } + + file.writeBytes(fixture.bytes) + assert(file.exists()) + val replayItem = SentryEnvelopeItem + .fromReplay(fixture.serializer, fixture.options.logger, replayEvent, null) + assert(file.exists()) + replayItem.data + assertFalse(file.exists()) + } + private fun createSession(): Session { return Session("dis", User(), "env", "rel") } @@ -379,4 +531,45 @@ class SentryEnvelopeItemTest { } } } + + private fun assertPayload( + replayItem: SentryEnvelopeItem, + replayEvent: SentryReplayEvent, + replayRecording: ReplayRecording?, + videoBytes: ByteArray, + mapSizeAsserter: (mapSize: Int) -> Unit = {} + ) { + val unpacker = MessagePack.newDefaultUnpacker(replayItem.data) + val mapSize = unpacker.unpackMapHeader() + mapSizeAsserter(mapSize) + for (i in 0 until mapSize) { + val key = unpacker.unpackString() + when (key) { + SentryItemType.ReplayEvent.itemType -> { + val replayEventLength = unpacker.unpackBinaryHeader() + val replayEventBytes = unpacker.readPayload(replayEventLength) + val actualReplayEvent = fixture.serializer.deserialize( + InputStreamReader(replayEventBytes.inputStream()), + SentryReplayEvent::class.java + ) + assertEquals(replayEvent, actualReplayEvent) + } + SentryItemType.ReplayRecording.itemType -> { + val replayRecordingLength = unpacker.unpackBinaryHeader() + val replayRecordingBytes = unpacker.readPayload(replayRecordingLength) + val actualReplayRecording = fixture.serializer.deserialize( + InputStreamReader(replayRecordingBytes.inputStream()), + ReplayRecording::class.java + ) + assertEquals(replayRecording, actualReplayRecording) + } + SentryItemType.ReplayVideo.itemType -> { + val videoLength = unpacker.unpackBinaryHeader() + val actualBytes = unpacker.readPayload(videoLength) + assertArrayEquals(videoBytes, actualBytes) + } + } + } + unpacker.close() + } } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt index 4bc13559da..3da517ef56 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt @@ -2,8 +2,8 @@ package io.sentry.protocol import io.sentry.ILogger import io.sentry.JsonDeserializer -import io.sentry.JsonObjectReader import io.sentry.JsonSerializable +import io.sentry.ObjectReader import io.sentry.ObjectWriter import io.sentry.SentryBaseEvent import io.sentry.SentryIntegrationPackageStorage @@ -27,7 +27,7 @@ class SentryBaseEventSerializationTest { } class Deserializer : JsonDeserializer { - override fun deserialize(reader: JsonObjectReader, logger: ILogger): Sut { + override fun deserialize(reader: ObjectReader, logger: ILogger): Sut { val sut = Sut() reader.beginObject() From b8cb924594c5b56ee7f35d207bb7511b7ae729ba Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 21 Feb 2024 00:44:03 +0100 Subject: [PATCH 09/19] Add missing test --- .../java/io/sentry/SentryEnvelopeItemTest.kt | 235 ++++++++++++++++-- 1 file changed, 214 insertions(+), 21 deletions(-) diff --git a/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt index 9817897651..efc5e5cadf 100644 --- a/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt +++ b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt @@ -1,6 +1,8 @@ package io.sentry import io.sentry.exception.SentryEnvelopeException +import io.sentry.protocol.ReplayRecordingSerializationTest +import io.sentry.protocol.SentryReplayEventSerializationTest import io.sentry.protocol.User import io.sentry.protocol.ViewHierarchy import io.sentry.test.injectForField @@ -10,12 +12,15 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.msgpack.core.MessagePack import java.io.BufferedWriter import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException +import java.io.InputStreamReader import java.io.OutputStreamWriter import java.nio.charset.Charset +import java.nio.file.Files import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals @@ -66,7 +71,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with bytes`() { val attachment = Attachment(fixture.bytesAllowed, fixture.filename) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, fixture.bytesAllowed, item) } @@ -78,7 +88,12 @@ class SentryEnvelopeItemTest { val attachment = Attachment(viewHierarchy, fixture.filename, "text/plain", null, false) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, viewHierarchySerialized, item) } @@ -87,7 +102,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with attachmentType`() { val attachment = Attachment(fixture.pathname, fixture.filename, "", true, "event.minidump") - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertEquals("event.minidump", item.header.attachmentType) } @@ -98,7 +118,12 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytesAllowed) val attachment = Attachment(file.path) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, fixture.bytesAllowed, item) } @@ -110,7 +135,12 @@ class SentryEnvelopeItemTest { file.writeBytes(twoMB) val attachment = Attachment(file.absolutePath) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, twoMB, item) } @@ -119,7 +149,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with non existent file`() { val attachment = Attachment("I don't exist", "file.txt") - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Reading the attachment ${attachment.pathname} failed, because the file located at " + @@ -139,7 +174,12 @@ class SentryEnvelopeItemTest { if (changedFileReadPermission) { val attachment = Attachment(file.path, "file.txt") - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Reading the attachment ${attachment.pathname} failed, " + @@ -162,7 +202,12 @@ class SentryEnvelopeItemTest { val securityManager = DenyReadFileSecurityManager(fixture.pathname) System.setSecurityManager(securityManager) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith("Reading the attachment ${attachment.pathname} failed.") { item.data @@ -181,7 +226,12 @@ class SentryEnvelopeItemTest { // reflection instead. attachment.injectForField("pathname", null) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Couldn't attach the attachment ${attachment.filename}.\n" + @@ -196,7 +246,12 @@ class SentryEnvelopeItemTest { val image = this::class.java.classLoader.getResource("Tongariro.jpg")!! val attachment = Attachment(image.path) - val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertAttachment(attachment, image.readBytes(), item) } @@ -204,7 +259,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with bytes too big`() { val attachment = Attachment(fixture.bytesTooBig, fixture.filename) val exception = assertFailsWith { - SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data + SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ).data } assertEquals( @@ -227,7 +287,12 @@ class SentryEnvelopeItemTest { val attachment = Attachment(serializable, fixture.filename, "text/plain", null, false) val exception = assertFailsWith { - SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data + SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ).data } assertEquals( @@ -246,7 +311,12 @@ class SentryEnvelopeItemTest { val attachment = Attachment(file.path) val exception = assertFailsWith { - SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data + SentryEnvelopeItem.fromAttachment( + fixture.serializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ).data } assertEquals( @@ -261,7 +331,12 @@ class SentryEnvelopeItemTest { fun `fromAttachment with bytesFrom serializable are null`() { val attachment = Attachment(mock(), "mock-file-name", null, null, false) - val item = SentryEnvelopeItem.fromAttachment(fixture.errorSerializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) + val item = SentryEnvelopeItem.fromAttachment( + fixture.errorSerializer, + fixture.options.logger, + attachment, + fixture.maxAttachmentSize + ) assertFailsWith( "Couldn't attach the attachment ${attachment.filename}.\n" + @@ -279,8 +354,13 @@ class SentryEnvelopeItemTest { } file.writeBytes(fixture.bytes) - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, fixture.serializer).data - verify(profilingTraceData).sampledProfile = Base64.encodeToString(fixture.bytes, Base64.NO_WRAP or Base64.NO_PADDING) + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + fixture.serializer + ).data + verify(profilingTraceData).sampledProfile = + Base64.encodeToString(fixture.bytes, Base64.NO_WRAP or Base64.NO_PADDING) } @Test @@ -292,7 +372,11 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytes) assert(file.exists()) - val traceData = SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()) + val traceData = SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ) assert(file.exists()) traceData.data assertFalse(file.exists()) @@ -306,7 +390,11 @@ class SentryEnvelopeItemTest { } assertFailsWith("Dropping profiling trace data, because the file ${file.path} doesn't exists") { - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ).data } } @@ -319,7 +407,11 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytes) file.setReadable(false) assertFailsWith("Dropping profiling trace data, because the file ${file.path} doesn't exists") { - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ).data } } @@ -331,7 +423,11 @@ class SentryEnvelopeItemTest { whenever(it.traceFile).thenReturn(file) } - val traceData = SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()) + val traceData = SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ) assertFailsWith("Profiling trace file is empty") { traceData.data } @@ -346,7 +442,11 @@ class SentryEnvelopeItemTest { } val exception = assertFailsWith { - SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data + SentryEnvelopeItem.fromProfilingTrace( + profilingTraceData, + fixture.maxAttachmentSize, + mock() + ).data } assertEquals( @@ -357,6 +457,58 @@ class SentryEnvelopeItemTest { ) } + @Test + fun `fromReplay encodes payload into msgpack`() { + val file = Files.createTempFile("replay", "").toFile() + val videoBytes = + this::class.java.classLoader.getResource("Tongariro.jpg")!!.readBytes() + file.writeBytes(videoBytes) + + val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut().apply { + videoFile = file + } + val replayRecording = ReplayRecordingSerializationTest.Fixture().getSut() + val replayItem = SentryEnvelopeItem + .fromReplay(fixture.serializer, fixture.options.logger, replayEvent, replayRecording) + + assertEquals(SentryItemType.ReplayVideo, replayItem.header.type) + + assertPayload(replayItem, replayEvent, replayRecording, videoBytes) + } + + @Test + fun `fromReplay does not add video item when no bytes`() { + val file = File(fixture.pathname) + file.writeBytes(ByteArray(0)) + + val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut().apply { + videoFile = file + } + + val replayItem = SentryEnvelopeItem + .fromReplay(fixture.serializer, fixture.options.logger, replayEvent, null) + replayItem.data + assertPayload(replayItem, replayEvent, null, ByteArray(0)) { mapSize -> + assertEquals(1, mapSize) + } + } + + @Test + fun `fromReplay deletes file only after reading data`() { + val file = File(fixture.pathname) + val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut().apply { + videoFile = file + } + + file.writeBytes(fixture.bytes) + assert(file.exists()) + val replayItem = SentryEnvelopeItem + .fromReplay(fixture.serializer, fixture.options.logger, replayEvent, null) + assert(file.exists()) + replayItem.data + assertFalse(file.exists()) + } + private fun createSession(): Session { return Session("dis", User(), "env", "rel") } @@ -379,4 +531,45 @@ class SentryEnvelopeItemTest { } } } + + private fun assertPayload( + replayItem: SentryEnvelopeItem, + replayEvent: SentryReplayEvent, + replayRecording: ReplayRecording?, + videoBytes: ByteArray, + mapSizeAsserter: (mapSize: Int) -> Unit = {} + ) { + val unpacker = MessagePack.newDefaultUnpacker(replayItem.data) + val mapSize = unpacker.unpackMapHeader() + mapSizeAsserter(mapSize) + for (i in 0 until mapSize) { + val key = unpacker.unpackString() + when (key) { + SentryItemType.ReplayEvent.itemType -> { + val replayEventLength = unpacker.unpackBinaryHeader() + val replayEventBytes = unpacker.readPayload(replayEventLength) + val actualReplayEvent = fixture.serializer.deserialize( + InputStreamReader(replayEventBytes.inputStream()), + SentryReplayEvent::class.java + ) + assertEquals(replayEvent, actualReplayEvent) + } + SentryItemType.ReplayRecording.itemType -> { + val replayRecordingLength = unpacker.unpackBinaryHeader() + val replayRecordingBytes = unpacker.readPayload(replayRecordingLength) + val actualReplayRecording = fixture.serializer.deserialize( + InputStreamReader(replayRecordingBytes.inputStream()), + ReplayRecording::class.java + ) + assertEquals(replayRecording, actualReplayRecording) + } + SentryItemType.ReplayVideo.itemType -> { + val videoLength = unpacker.unpackBinaryHeader() + val actualBytes = unpacker.readPayload(videoLength) + assertArrayEquals(videoBytes, actualBytes) + } + } + } + unpacker.close() + } } From 1e76fc7499e8a1f4747c67c3aed4d1547c83759e Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 22 Feb 2024 10:21:35 +0100 Subject: [PATCH 10/19] Add test for MapObjectReader --- .../java/io/sentry/util/MapObjectReader.java | 11 +- .../io/sentry/util/MapObjectReaderTest.kt | 131 ++++++++++++++++++ 2 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/util/MapObjectReaderTest.kt diff --git a/sentry/src/main/java/io/sentry/util/MapObjectReader.java b/sentry/src/main/java/io/sentry/util/MapObjectReader.java index ba2cb83ed3..e4dff5f491 100644 --- a/sentry/src/main/java/io/sentry/util/MapObjectReader.java +++ b/sentry/src/main/java/io/sentry/util/MapObjectReader.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.util.AbstractMap; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Date; import java.util.Deque; import java.util.List; @@ -161,7 +162,8 @@ public void beginArray() throws IOException { // insert a dummy entry to indicate end of an object stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_ARRAY)); // extract map entries onto the stack - for (Object entry : (List) value) { + for (int i = ((List) value).size() - 1; i >= 0; i--) { + Object entry = ((List) value).get(i); stack.addLast(new AbstractMap.SimpleEntry<>(null, entry)); } } else { @@ -295,7 +297,10 @@ public float nextFloat() throws IOException { @Override public void nextNull() throws IOException { - nextValueOrNull(); + Object value = nextValueOrNull(); + if (value != null) { + throw new IOException("Expected null but was " + peek()); + } } @Override @@ -327,7 +332,7 @@ private T nextValueOrNull( if (deserializer != null && logger != null) { return deserializer.deserialize(this, logger); } else if (value instanceof List) { - List list = (List) value; + List list = new ArrayList<>((List) value); if (!list.isEmpty()) { T next = (T) list.remove(0); if (next instanceof Map) { diff --git a/sentry/src/test/java/io/sentry/util/MapObjectReaderTest.kt b/sentry/src/test/java/io/sentry/util/MapObjectReaderTest.kt new file mode 100644 index 0000000000..ab52919c43 --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/MapObjectReaderTest.kt @@ -0,0 +1,131 @@ +package io.sentry.util + +import io.sentry.ILogger +import io.sentry.JsonDeserializer +import io.sentry.JsonSerializable +import io.sentry.NoOpLogger +import io.sentry.ObjectReader +import io.sentry.ObjectWriter +import io.sentry.vendor.gson.stream.JsonToken +import java.math.BigDecimal +import java.net.URI +import java.util.Currency +import java.util.Date +import java.util.Locale +import java.util.TimeZone +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertEquals + +class MapObjectReaderTest { + + enum class BasicEnum { + A + } + + data class BasicSerializable(var test: String = "string") : JsonSerializable { + + override fun serialize(writer: ObjectWriter, logger: ILogger) { + writer.beginObject() + .name("test") + .value(test) + .endObject() + } + + class Deserializer : JsonDeserializer { + override fun deserialize(reader: ObjectReader, logger: ILogger): BasicSerializable { + val basicSerializable = BasicSerializable() + reader.beginObject() + if (reader.nextName() == "test") { + basicSerializable.test = reader.nextString() + } + reader.endObject() + return basicSerializable + } + } + } + + @Test + fun `deserializes data correctly`() { + val logger = NoOpLogger.getInstance() + val data = mutableMapOf() + val writer = MapObjectWriter(data) + + writer.name("null").nullValue() + writer.name("int").value(1) + writer.name("boolean").value(true) + writer.name("long").value(Long.MAX_VALUE) + writer.name("double").value(Double.MAX_VALUE) + writer.name("number").value(BigDecimal(123)) + writer.name("date").value(logger, Date(0)) + writer.name("string").value("string") + + writer.name("TimeZone").value(logger, TimeZone.getTimeZone("Vienna")) + writer.name("JsonSerializable").value( + logger, + BasicSerializable() + ) + writer.name("Collection").value(logger, listOf("a", "b")) + writer.name("Arrays").value(logger, arrayOf("b", "c")) + writer.name("Map").value(logger, mapOf(kotlin.Pair("key", "value"))) + writer.name("Locale").value(logger, Locale.US) + writer.name("URI").value(logger, URI.create("http://www.example.com")) + writer.name("UUID").value(logger, UUID.fromString("00000000-1111-2222-3333-444444444444")) + writer.name("Currency").value(logger, Currency.getInstance("EUR")) + writer.name("Enum").value(logger, MapObjectWriterTest.BasicEnum.A) + + val reader = MapObjectReader(data) + reader.beginObject() + assertEquals(JsonToken.NAME, reader.peek()) + assertEquals("Enum", reader.nextName()) + assertEquals(BasicEnum.A, BasicEnum.valueOf(reader.nextString())) + assertEquals("Currency", reader.nextName()) + assertEquals(Currency.getInstance("EUR"), Currency.getInstance(reader.nextString())) + assertEquals("UUID", reader.nextName()) + assertEquals( + UUID.fromString("00000000-1111-2222-3333-444444444444"), + UUID.fromString(reader.nextString()) + ) + assertEquals("URI", reader.nextName()) + assertEquals(URI.create("http://www.example.com"), URI.create(reader.nextString())) + assertEquals("Locale", reader.nextName()) + assertEquals(Locale.US.toString(), reader.nextString()) + assertEquals("Map", reader.nextName()) + // nested object + reader.beginObject() + assertEquals("key", reader.nextName()) + assertEquals("value", reader.nextStringOrNull()) + reader.endObject() + assertEquals("Arrays", reader.nextName()) + reader.beginArray() + assertEquals("b", reader.nextString()) + assertEquals("c", reader.nextString()) + reader.endArray() + assertEquals("Collection", reader.nextName()) + reader.beginArray() + assertEquals("a", reader.nextString()) + assertEquals("b", reader.nextString()) + reader.endArray() + assertEquals("JsonSerializable", reader.nextName()) + assertEquals(BasicSerializable(), reader.nextOrNull(logger, BasicSerializable.Deserializer())) + assertEquals("TimeZone", reader.nextName()) + assertEquals(TimeZone.getTimeZone("Vienna"), reader.nextTimeZoneOrNull(logger)) + assertEquals("string", reader.nextName()) + assertEquals("string", reader.nextString()) + assertEquals("date", reader.nextName()) + assertEquals(Date(0), reader.nextDateOrNull(logger)) + assertEquals("number", reader.nextName()) + assertEquals(BigDecimal(123), reader.nextObjectOrNull()) + assertEquals("double", reader.nextName()) + assertEquals(Double.MAX_VALUE, reader.nextDoubleOrNull()) + assertEquals("long", reader.nextName()) + assertEquals(Long.MAX_VALUE, reader.nextLongOrNull()) + assertEquals("boolean", reader.nextName()) + assertEquals(true, reader.nextBoolean()) + assertEquals("int", reader.nextName()) + assertEquals(1, reader.nextInt()) + assertEquals("null", reader.nextName()) + reader.nextNull() + reader.endObject() + } +} From 13c19718d27874b59115661e7f2dbf915685c2d1 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 22 Feb 2024 10:24:02 +0100 Subject: [PATCH 11/19] Add MapObjectWriter change --- .../src/main/java/io/sentry/util/MapObjectWriter.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java index 26f80eddc2..0bbc70a779 100644 --- a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java +++ b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java @@ -120,6 +120,11 @@ public MapObjectWriter value(final @NotNull ILogger logger, final @Nullable Obje return this; } + @Override + public void setLenient(boolean lenient) { + // no-op + } + @Override public MapObjectWriter beginArray() throws IOException { stack.add(new ArrayList<>()); @@ -151,6 +156,12 @@ public MapObjectWriter value(final @Nullable String value) throws IOException { return this; } + @Override + public ObjectWriter jsonValue(@Nullable String value) throws IOException { + // no-op + return this; + } + @Override public MapObjectWriter nullValue() throws IOException { postValue((Object) null); From 86baf7fc968a8ccc7dc7e8bdce22bd37e60bf8a6 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 22 Feb 2024 10:36:42 +0100 Subject: [PATCH 12/19] Add finals --- .../src/main/java/io/sentry/ObjectReader.java | 3 +- .../java/io/sentry/util/MapObjectReader.java | 53 ++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/sentry/src/main/java/io/sentry/ObjectReader.java b/sentry/src/main/java/io/sentry/ObjectReader.java index 0449379f7d..6c2210897e 100644 --- a/sentry/src/main/java/io/sentry/ObjectReader.java +++ b/sentry/src/main/java/io/sentry/ObjectReader.java @@ -11,7 +11,8 @@ import org.jetbrains.annotations.Nullable; public interface ObjectReader extends Closeable { - static @Nullable Date dateOrNull(@Nullable String dateString, final @NotNull ILogger logger) { + static @Nullable Date dateOrNull( + final @Nullable String dateString, final @NotNull ILogger logger) { if (dateString == null) { return null; } diff --git a/sentry/src/main/java/io/sentry/util/MapObjectReader.java b/sentry/src/main/java/io/sentry/util/MapObjectReader.java index e4dff5f491..6cb05989b2 100644 --- a/sentry/src/main/java/io/sentry/util/MapObjectReader.java +++ b/sentry/src/main/java/io/sentry/util/MapObjectReader.java @@ -28,7 +28,8 @@ public MapObjectReader(final Map root) { } @Override - public void nextUnknown(final @NotNull ILogger logger, Map unknown, String name) { + public void nextUnknown( + final @NotNull ILogger logger, final Map unknown, final String name) { try { unknown.put(name, nextObjectOrNull()); } catch (Exception exception) { @@ -63,14 +64,14 @@ public T nextOrNull( @Nullable @Override public Date nextDateOrNull(final @NotNull ILogger logger) throws IOException { - String dateString = nextStringOrNull(); + final String dateString = nextStringOrNull(); return ObjectReader.dateOrNull(dateString, logger); } @Nullable @Override public TimeZone nextTimeZoneOrNull(final @NotNull ILogger logger) throws IOException { - String timeZoneId = nextStringOrNull(); + final String timeZoneId = nextStringOrNull(); return timeZoneId != null ? TimeZone.getTimeZone(timeZoneId) : null; } @@ -87,7 +88,7 @@ public JsonToken peek() throws IOException { return JsonToken.END_DOCUMENT; } - Map.Entry currentEntry = stack.peekLast(); + final Map.Entry currentEntry = stack.peekLast(); if (currentEntry == null) { return JsonToken.END_DOCUMENT; } @@ -96,7 +97,7 @@ public JsonToken peek() throws IOException { return JsonToken.NAME; } - Object value = currentEntry.getValue(); + final Object value = currentEntry.getValue(); if (value instanceof Map) { return JsonToken.BEGIN_OBJECT; @@ -118,7 +119,7 @@ public JsonToken peek() throws IOException { @NotNull @Override public String nextName() throws IOException { - Map.Entry currentEntry = stack.peekLast(); + final Map.Entry currentEntry = stack.peekLast(); if (currentEntry != null && currentEntry.getKey() != null) { return currentEntry.getKey(); } @@ -127,11 +128,11 @@ public String nextName() throws IOException { @Override public void beginObject() throws IOException { - Map.Entry currentEntry = stack.removeLast(); + final Map.Entry currentEntry = stack.removeLast(); if (currentEntry == null) { throw new IOException("No more entries"); } - Object value = currentEntry.getValue(); + final Object value = currentEntry.getValue(); if (value instanceof Map) { // insert a dummy entry to indicate end of an object stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_OBJECT)); @@ -153,17 +154,17 @@ public void endObject() throws IOException { @Override public void beginArray() throws IOException { - Map.Entry currentEntry = stack.removeLast(); + final Map.Entry currentEntry = stack.removeLast(); if (currentEntry == null) { throw new IOException("No more entries"); } - Object value = currentEntry.getValue(); + final Object value = currentEntry.getValue(); if (value instanceof List) { // insert a dummy entry to indicate end of an object stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_ARRAY)); // extract map entries onto the stack for (int i = ((List) value).size() - 1; i >= 0; i--) { - Object entry = ((List) value).get(i); + final Object entry = ((List) value).get(i); stack.addLast(new AbstractMap.SimpleEntry<>(null, entry)); } } else { @@ -185,7 +186,7 @@ public boolean hasNext() throws IOException { @Override public int nextInt() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).intValue(); } else { @@ -196,7 +197,7 @@ public int nextInt() throws IOException { @Nullable @Override public Integer nextIntegerOrNull() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).intValue(); } @@ -205,7 +206,7 @@ public Integer nextIntegerOrNull() throws IOException { @Override public long nextLong() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).longValue(); } else { @@ -216,7 +217,7 @@ public long nextLong() throws IOException { @Nullable @Override public Long nextLongOrNull() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).longValue(); } @@ -225,7 +226,7 @@ public Long nextLongOrNull() throws IOException { @Override public String nextString() throws IOException { - String value = nextValueOrNull(); + final String value = nextValueOrNull(); if (value != null) { return value; } else { @@ -241,7 +242,7 @@ public String nextStringOrNull() throws IOException { @Override public boolean nextBoolean() throws IOException { - Boolean value = nextValueOrNull(); + final Boolean value = nextValueOrNull(); if (value != null) { return value; } else { @@ -257,7 +258,7 @@ public Boolean nextBooleanOrNull() throws IOException { @Override public double nextDouble() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).doubleValue(); } else { @@ -268,7 +269,7 @@ public double nextDouble() throws IOException { @Nullable @Override public Double nextDoubleOrNull() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).doubleValue(); } @@ -278,7 +279,7 @@ public Double nextDoubleOrNull() throws IOException { @Nullable @Override public Float nextFloatOrNull() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).floatValue(); } @@ -287,7 +288,7 @@ public Float nextFloatOrNull() throws IOException { @Override public float nextFloat() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value instanceof Number) { return ((Number) value).floatValue(); } else { @@ -297,14 +298,14 @@ public float nextFloat() throws IOException { @Override public void nextNull() throws IOException { - Object value = nextValueOrNull(); + final Object value = nextValueOrNull(); if (value != null) { throw new IOException("Expected null but was " + peek()); } } @Override - public void setLenient(boolean lenient) {} + public void setLenient(final boolean lenient) {} @Override public void skipValue() throws IOException {} @@ -324,17 +325,17 @@ private T nextValueOrNull() throws IOException { private T nextValueOrNull( final @Nullable ILogger logger, final @Nullable JsonDeserializer deserializer) throws Exception { - Map.Entry currentEntry = stack.peekLast(); + final Map.Entry currentEntry = stack.peekLast(); if (currentEntry == null) { return null; } - T value = (T) currentEntry.getValue(); + final T value = (T) currentEntry.getValue(); if (deserializer != null && logger != null) { return deserializer.deserialize(this, logger); } else if (value instanceof List) { List list = new ArrayList<>((List) value); if (!list.isEmpty()) { - T next = (T) list.remove(0); + final T next = (T) list.remove(0); if (next instanceof Map) { stack.addLast(new AbstractMap.SimpleEntry<>(null, next)); } From f1ca9f68bb0444b43eb705811a9176bf869fc89e Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 22 Feb 2024 10:40:33 +0100 Subject: [PATCH 13/19] Fix test --- .../io/sentry/protocol/SentryReplayEventSerializationTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt index 82b32e96c7..7be66d31e7 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt @@ -27,6 +27,10 @@ class SentryReplayEventSerializationTest { errorIds = listOf("ab3a347a4cc14fd4b4cf1dc56b670c5b") traceIds = listOf("340cfef948204549ac07c3b353c81c50") SentryBaseEventSerializationTest.Fixture().update(this) + // irrelevant for replay + serverName = null + breadcrumbs = null + extras = null } } private val fixture = Fixture() From fbbe0d97b431de445abf0f8184751ab7652f54db Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 22 Feb 2024 10:56:52 +0100 Subject: [PATCH 14/19] Fix test --- .../android/core/SessionTrackingIntegrationTest.kt | 9 +++++++++ .../protocol/SentryReplayEventSerializationTest.kt | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt index af32fa3714..341d15e608 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt @@ -15,6 +15,7 @@ import io.sentry.Sentry import io.sentry.SentryEnvelope import io.sentry.SentryEvent import io.sentry.SentryOptions +import io.sentry.SentryReplayEvent import io.sentry.Session import io.sentry.TraceContext import io.sentry.UserFeedback @@ -141,6 +142,14 @@ class SessionTrackingIntegrationTest { TODO("Not yet implemented") } + override fun captureReplayEvent( + event: SentryReplayEvent, + scope: IScope?, + hint: Hint? + ): SentryId { + TODO("Not yet implemented") + } + override fun captureUserFeedback(userFeedback: UserFeedback) { TODO("Not yet implemented") } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt index 82b32e96c7..7be66d31e7 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt @@ -27,6 +27,10 @@ class SentryReplayEventSerializationTest { errorIds = listOf("ab3a347a4cc14fd4b4cf1dc56b670c5b") traceIds = listOf("340cfef948204549ac07c3b353c81c50") SentryBaseEventSerializationTest.Fixture().update(this) + // irrelevant for replay + serverName = null + breadcrumbs = null + extras = null } } private val fixture = Fixture() From fd6396040f50ce68a397782961ee4478de485506 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 28 Feb 2024 11:54:19 +0100 Subject: [PATCH 15/19] Address review --- .../src/main/java/io/sentry/SentryEnvelopeItem.java | 2 +- .../src/main/java/io/sentry/SentryReplayEvent.java | 9 ++------- .../main/java/io/sentry/rrweb/RRWebMetaEvent.java | 2 ++ .../main/java/io/sentry/rrweb/RRWebVideoEvent.java | 12 +++++++----- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index be6838670e..2512845129 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -435,7 +435,7 @@ private static byte[] serializeToMsgpack(Map map) throws IOExcep // Iterate over the map and serialize each key-value pair for (Map.Entry entry : map.entrySet()) { // Pack the key as a string - byte[] keyBytes = entry.getKey().getBytes(Charset.forName("UTF-8")); + byte[] keyBytes = entry.getKey().getBytes(UTF_8); int keyLength = keyBytes.length; // string up to 255 chars baos.write((byte) (0xd9)); diff --git a/sentry/src/main/java/io/sentry/SentryReplayEvent.java b/sentry/src/main/java/io/sentry/SentryReplayEvent.java index a351a21d54..eaab8d0ae3 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayEvent.java +++ b/sentry/src/main/java/io/sentry/SentryReplayEvent.java @@ -5,7 +5,6 @@ import io.sentry.vendor.gson.stream.JsonToken; import java.io.File; import java.io.IOException; -import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -192,16 +191,12 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger writer.name(JsonKeys.TYPE).value(type); writer.name(JsonKeys.REPLAY_TYPE).value(logger, replayType); writer.name(JsonKeys.SEGMENT_ID).value(segmentId); - writer - .name(JsonKeys.TIMESTAMP) - .value(logger, BigDecimal.valueOf(DateUtils.dateToSeconds(timestamp))); + writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); if (replayId != null) { writer.name(JsonKeys.REPLAY_ID).value(logger, replayId); } if (replayStartTimestamp != null) { - writer - .name(JsonKeys.REPLAY_START_TIMESTAMP) - .value(logger, BigDecimal.valueOf(DateUtils.dateToSeconds(replayStartTimestamp))); + writer.name(JsonKeys.REPLAY_START_TIMESTAMP).value(logger, replayStartTimestamp); } if (urls != null) { writer.name(JsonKeys.URLS).value(logger, urls); diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java index 0a1c914cb3..dee4fb2ef3 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java @@ -20,6 +20,8 @@ public final class RRWebMetaEvent extends RRWebEvent implements JsonUnknown, Jso private @NotNull String href; private int height; private int width; + // to support unknown json attributes with nesting, we have to have unknown map for each of the + // nested object in json: { ..., "data": { ... } } private @Nullable Map unknown; private @Nullable Map dataUnknown; diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java index f9d61e591e..c55e055cca 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java @@ -36,6 +36,8 @@ public final class RRWebVideoEvent extends RRWebEvent implements JsonUnknown, Js private int frameRate; private int left; private int top; + // to support unknown json attributes with nesting, we have to have unknown map for each of the + // nested object in json: { ..., "data": { ..., "payload": { ... } } } private @Nullable Map unknown; private @Nullable Map payloadUnknown; private @Nullable Map dataUnknown; @@ -334,7 +336,7 @@ private void deserializeData( final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { - Map dataUknown = null; + Map dataUnknown = null; reader.beginObject(); while (reader.peek() == JsonToken.NAME) { @@ -348,13 +350,13 @@ private void deserializeData( deserializePayload(event, reader, logger); break; default: - if (dataUknown == null) { - dataUknown = new ConcurrentHashMap<>(); + if (dataUnknown == null) { + dataUnknown = new ConcurrentHashMap<>(); } - reader.nextUnknown(logger, dataUknown, nextName); + reader.nextUnknown(logger, dataUnknown, nextName); } } - event.setDataUnknown(dataUknown); + event.setDataUnknown(dataUnknown); reader.endObject(); } From 93785cc282c0c2a32b1dcd3e8a57a32ff2aa7658 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 28 Feb 2024 12:10:30 +0100 Subject: [PATCH 16/19] Add finals and annotations --- sentry/src/main/java/io/sentry/Hub.java | 2 +- .../java/io/sentry/MainEventProcessor.java | 3 +- .../main/java/io/sentry/ReplayRecording.java | 8 +-- .../java/io/sentry/SentryEnvelopeItem.java | 54 ++++++++++--------- .../java/io/sentry/SentryReplayEvent.java | 8 +-- .../main/java/io/sentry/rrweb/RRWebEvent.java | 12 +++-- .../java/io/sentry/rrweb/RRWebEventType.java | 5 +- .../java/io/sentry/rrweb/RRWebMetaEvent.java | 10 ++-- .../java/io/sentry/rrweb/RRWebVideoEvent.java | 23 ++++---- 9 files changed, 66 insertions(+), 59 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Hub.java b/sentry/src/main/java/io/sentry/Hub.java index b91b551705..8861c1c67f 100644 --- a/sentry/src/main/java/io/sentry/Hub.java +++ b/sentry/src/main/java/io/sentry/Hub.java @@ -932,7 +932,7 @@ private IScope buildLocalScope( "Instance is disabled and this 'captureReplay' call is a no-op."); } else { try { - StackItem item = stack.peek(); + final @NotNull StackItem item = stack.peek(); sentryId = item.getClient().captureReplayEvent(replay, item.getScope(), hint); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, "Error while capturing replay", e); diff --git a/sentry/src/main/java/io/sentry/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java index e79ebc37bd..d6445e3a56 100644 --- a/sentry/src/main/java/io/sentry/MainEventProcessor.java +++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java @@ -150,7 +150,8 @@ private void processNonCachedEvent(final @NotNull SentryBaseEvent event) { } @Override - public @NotNull SentryReplayEvent process(@NotNull SentryReplayEvent event, @NotNull Hint hint) { + public @NotNull SentryReplayEvent process( + final @NotNull SentryReplayEvent event, final @NotNull Hint hint) { setCommons(event); // TODO: maybe later it's needed to deobfuscate something (e.g. view hierarchy), for now the // TODO: protocol does not support it diff --git a/sentry/src/main/java/io/sentry/ReplayRecording.java b/sentry/src/main/java/io/sentry/ReplayRecording.java index d8892191f2..4e6aecdb2b 100644 --- a/sentry/src/main/java/io/sentry/ReplayRecording.java +++ b/sentry/src/main/java/io/sentry/ReplayRecording.java @@ -31,7 +31,7 @@ public Integer getSegmentId() { return segmentId; } - public void setSegmentId(@Nullable Integer segmentId) { + public void setSegmentId(final @Nullable Integer segmentId) { this.segmentId = segmentId; } @@ -40,7 +40,7 @@ public List getPayload() { return payload; } - public void setPayload(@Nullable List payload) { + public void setPayload(final @Nullable List payload) { this.payload = payload; } @@ -136,11 +136,11 @@ public static final class Deserializer implements JsonDeserializer eventMap = (Map) event; final ObjectReader mapReader = new MapObjectReader(eventMap); - for (Map.Entry entry : eventMap.entrySet()) { + for (final Map.Entry entry : eventMap.entrySet()) { final String key = entry.getKey(); final Object value = entry.getValue(); if (key.equals("type")) { - RRWebEventType type = RRWebEventType.values()[(int) value]; + final RRWebEventType type = RRWebEventType.values()[(int) value]; switch (type) { case Meta: final RRWebEvent metaEvent = diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index 2512845129..ff162f4464 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -425,32 +425,34 @@ public CachedItem(final @Nullable Callable dataFactory) { } } - @SuppressWarnings({"CharsetObjectCanBeUsed", "UnnecessaryParentheses"}) - private static byte[] serializeToMsgpack(Map map) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - // Write map header - baos.write((byte) (0x80 | map.size())); - - // Iterate over the map and serialize each key-value pair - for (Map.Entry entry : map.entrySet()) { - // Pack the key as a string - byte[] keyBytes = entry.getKey().getBytes(UTF_8); - int keyLength = keyBytes.length; - // string up to 255 chars - baos.write((byte) (0xd9)); - baos.write((byte) (keyLength)); - baos.write(keyBytes); - - // Pack the value as a binary string - byte[] valueBytes = entry.getValue(); - int valueLength = valueBytes.length; - // We will always use the 4 bytes data length for simplicity. - baos.write((byte) (0xc6)); - baos.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(valueLength).array()); - baos.write(valueBytes); - } + @SuppressWarnings({"UnnecessaryParentheses"}) + private static byte[] serializeToMsgpack(final @NotNull Map map) + throws IOException { + try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + + // Write map header + baos.write((byte) (0x80 | map.size())); + + // Iterate over the map and serialize each key-value pair + for (final Map.Entry entry : map.entrySet()) { + // Pack the key as a string + final byte[] keyBytes = entry.getKey().getBytes(UTF_8); + final int keyLength = keyBytes.length; + // string up to 255 chars + baos.write((byte) (0xd9)); + baos.write((byte) (keyLength)); + baos.write(keyBytes); + + // Pack the value as a binary string + final byte[] valueBytes = entry.getValue(); + final int valueLength = valueBytes.length; + // We will always use the 4 bytes data length for simplicity. + baos.write((byte) (0xc6)); + baos.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(valueLength).array()); + baos.write(valueBytes); + } - return baos.toByteArray(); + return baos.toByteArray(); + } } } diff --git a/sentry/src/main/java/io/sentry/SentryReplayEvent.java b/sentry/src/main/java/io/sentry/SentryReplayEvent.java index eaab8d0ae3..95623d2ff6 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayEvent.java +++ b/sentry/src/main/java/io/sentry/SentryReplayEvent.java @@ -22,15 +22,15 @@ public enum ReplayType implements JsonSerializable { BUFFER; @Override - public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.value(name().toLowerCase(Locale.ROOT)); } public static final class Deserializer implements JsonDeserializer { @Override - public @NotNull ReplayType deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) - throws Exception { + public @NotNull ReplayType deserialize( + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { return ReplayType.valueOf(reader.nextString().toUpperCase(Locale.ROOT)); } } @@ -236,7 +236,7 @@ public static final class Deserializer implements JsonDeserializer { @Override public @NotNull RRWebEventType deserialize( - @NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { + final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { return RRWebEventType.values()[reader.nextInt()]; } } diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java index dee4fb2ef3..b0aca2f337 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebMetaEvent.java @@ -127,11 +127,11 @@ public static final class Deserializer implements JsonDeserializer unknown = null; - RRWebMetaEvent event = new RRWebMetaEvent(); - RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); + @Nullable Map unknown = null; + final RRWebMetaEvent event = new RRWebMetaEvent(); + final RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); while (reader.peek() == JsonToken.NAME) { final String nextName = reader.nextName(); @@ -159,7 +159,7 @@ private void deserializeData( final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { - Map unknown = null; + @Nullable Map unknown = null; reader.beginObject(); while (reader.peek() == JsonToken.NAME) { diff --git a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java index c55e055cca..5bea9e3c47 100644 --- a/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java +++ b/sentry/src/main/java/io/sentry/rrweb/RRWebVideoEvent.java @@ -242,14 +242,15 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { writer.beginObject(); new RRWebEvent.Serializer().serialize(this, writer, logger); writer.name(JsonKeys.DATA); serializeData(writer, logger); if (unknown != null) { - for (String key : unknown.keySet()) { - Object value = unknown.get(key); + for (final String key : unknown.keySet()) { + final Object value = unknown.get(key); writer.name(key); writer.value(logger, value); } @@ -289,8 +290,8 @@ private void serializePayload(final @NotNull ObjectWriter writer, final @NotNull writer.name(JsonKeys.LEFT).value(left); writer.name(JsonKeys.TOP).value(top); if (payloadUnknown != null) { - for (String key : payloadUnknown.keySet()) { - Object value = payloadUnknown.get(key); + for (final String key : payloadUnknown.keySet()) { + final Object value = payloadUnknown.get(key); writer.name(key); writer.value(logger, value); } @@ -303,12 +304,12 @@ public static final class Deserializer implements JsonDeserializer unknown = null; + @Nullable Map unknown = null; - RRWebVideoEvent event = new RRWebVideoEvent(); - RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); + final RRWebVideoEvent event = new RRWebVideoEvent(); + final RRWebEvent.Deserializer baseEventDeserializer = new RRWebEvent.Deserializer(); while (reader.peek() == JsonToken.NAME) { final String nextName = reader.nextName(); @@ -336,7 +337,7 @@ private void deserializeData( final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { - Map dataUnknown = null; + @Nullable Map dataUnknown = null; reader.beginObject(); while (reader.peek() == JsonToken.NAME) { @@ -365,7 +366,7 @@ private void deserializePayload( final @NotNull ObjectReader reader, final @NotNull ILogger logger) throws Exception { - Map payloadUnknown = null; + @Nullable Map payloadUnknown = null; reader.beginObject(); while (reader.peek() == JsonToken.NAME) { From 62477b47e3d8ffed2c24e0786717af4b70f6fa63 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 1 Mar 2024 14:30:30 +0100 Subject: [PATCH 17/19] Remove public captureReplay method --- sentry/src/main/java/io/sentry/Sentry.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 6058199d62..0aff89c0d0 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1025,9 +1025,4 @@ public interface OptionsConfiguration { public static @NotNull SentryId captureCheckIn(final @NotNull CheckIn checkIn) { return getCurrentHub().captureCheckIn(checkIn); } - - public static void captureReplay( - final @NotNull SentryReplayEvent replay, final @Nullable Hint hint) { - getCurrentHub().captureReplay(replay, hint); - } } From af42fb3578733c1af687b48133fd9272c54c2603 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 1 Mar 2024 16:21:01 +0100 Subject: [PATCH 18/19] Fix test --- .../io/sentry/protocol/SentryReplayEventSerializationTest.kt | 4 ++-- sentry/src/test/resources/json/sentry_replay_event.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt index 7be66d31e7..6ecd680076 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryReplayEventSerializationTest.kt @@ -21,8 +21,8 @@ class SentryReplayEventSerializationTest { fun getSut() = SentryReplayEvent().apply { replayId = SentryId("f715e1d64ef64ea3ad7744b5230813c3") segmentId = 0 - timestamp = DateUtils.getDateTimeWithMillisPrecision("987654321.123") - replayStartTimestamp = DateUtils.getDateTimeWithMillisPrecision("987654321.123") + timestamp = DateUtils.getDateTime("1942-07-09T12:55:34.000Z") + replayStartTimestamp = DateUtils.getDateTime("1942-07-09T12:55:34.000Z") urls = listOf("ScreenOne") errorIds = listOf("ab3a347a4cc14fd4b4cf1dc56b670c5b") traceIds = listOf("340cfef948204549ac07c3b353c81c50") diff --git a/sentry/src/test/resources/json/sentry_replay_event.json b/sentry/src/test/resources/json/sentry_replay_event.json index 04c96968b9..dea5a99e59 100644 --- a/sentry/src/test/resources/json/sentry_replay_event.json +++ b/sentry/src/test/resources/json/sentry_replay_event.json @@ -2,9 +2,9 @@ "type": "replay_event", "replay_type": "session", "segment_id": 0, - "timestamp": 987654321.123, + "timestamp": "1942-07-09T12:55:34.000Z", "replay_id": "f715e1d64ef64ea3ad7744b5230813c3", - "replay_start_timestamp": 987654321.123, + "replay_start_timestamp": "1942-07-09T12:55:34.000Z", "urls": [ "ScreenOne" From 4e54c77b149347b3f44c5479f7976fc8417575a0 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Sat, 2 Mar 2024 00:04:28 +0100 Subject: [PATCH 19/19] api dump --- sentry/api/sentry.api | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 20e2d8b53b..b6acae0cde 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1755,7 +1755,6 @@ public final class io/sentry/Sentry { public static fun captureMessage (Ljava/lang/String;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public static fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public static fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; - public static fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)V public static fun captureUserFeedback (Lio/sentry/UserFeedback;)V public static fun clearBreadcrumbs ()V public static fun cloneMainHub ()Lio/sentry/IHub;