From 78a7eb71465371cce209c79d4957478c023ac855 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 1 Oct 2024 00:19:04 +0200 Subject: [PATCH 1/7] Replace Calendar getInstance with system currentTimeMillis for breadcrumbs --- .../sentry/android/ndk/NdkScopeObserver.java | 2 +- .../sentry/android/ndk/NdkScopeObserverTest.kt | 2 +- .../replay/DefaultReplayBreadcrumbConverter.kt | 6 +++--- .../android/replay/capture/CaptureStrategy.kt | 4 ++-- .../sentry-samples-android/build.gradle.kts | 2 +- sentry/src/main/java/io/sentry/Breadcrumb.java | 18 +++++++++++------- .../main/java/io/sentry/JsonSerializer.java | 2 +- .../src/main/java/io/sentry/SentryClient.java | 2 +- .../src/test/java/io/sentry/BreadcrumbTest.kt | 3 ++- 9 files changed, 23 insertions(+), 18 deletions(-) diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java index 009bba9b81..f54dccc06b 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java @@ -49,7 +49,7 @@ public void addBreadcrumb(final @NotNull Breadcrumb crumb) { if (crumb.getLevel() != null) { level = crumb.getLevel().name().toLowerCase(Locale.ROOT); } - final String timestamp = DateUtils.getTimestamp(crumb.getTimestamp()); + final String timestamp = DateUtils.getTimestamp(DateUtils.getDateTime(crumb.getTimestamp())); String data = null; try { diff --git a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt index 335a7679e1..b44ee85161 100644 --- a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt +++ b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt @@ -97,7 +97,7 @@ class NdkScopeObserverTest { setData("a", "b") type = "type" } - val timestamp = DateUtils.getTimestamp(breadcrumb.timestamp) + val timestamp = DateUtils.getTimestamp(DateUtils.getDateTime(breadcrumb.timestamp)) val data = "{\"a\":\"b\"}" sut.addBreadcrumb(breadcrumb) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt index c95b72088a..ef54870287 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt @@ -106,8 +106,8 @@ public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { } return if (!breadcrumbCategory.isNullOrEmpty()) { RRWebBreadcrumbEvent().apply { - timestamp = breadcrumb.timestamp.time - breadcrumbTimestamp = breadcrumb.timestamp.time / 1000.0 + timestamp = breadcrumb.timestamp + breadcrumbTimestamp = breadcrumb.timestamp / 1000.0 breadcrumbType = "default" category = breadcrumbCategory message = breadcrumbMessage @@ -134,7 +134,7 @@ public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { val httpStartTimestamp = breadcrumb.data[SpanDataConvention.HTTP_START_TIMESTAMP] val httpEndTimestamp = breadcrumb.data[SpanDataConvention.HTTP_END_TIMESTAMP] return RRWebSpanEvent().apply { - timestamp = breadcrumb.timestamp.time + timestamp = breadcrumb.timestamp op = "resource.http" description = breadcrumb.data["url"] as String // can be double if it was serialized to disk diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt index 7e9168df22..9bdc3e87cb 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt @@ -161,8 +161,8 @@ internal interface CaptureStrategy { val urls = LinkedList() breadcrumbs.forEach { breadcrumb -> - if (breadcrumb.timestamp.time >= segmentTimestamp.time && - breadcrumb.timestamp.time < endTimestamp.time + if (breadcrumb.timestamp >= segmentTimestamp.time && + breadcrumb.timestamp < endTimestamp.time ) { val rrwebEvent = options .replayController diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index a8d8897519..2c94dbba9d 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -133,5 +133,5 @@ dependencies { implementation(Config.Libs.composeNavigation) implementation(Config.Libs.composeMaterial) - debugImplementation(Config.Libs.leakCanary) +// debugImplementation(Config.Libs.leakCanary) } diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index da1453bc68..895642c927 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -20,7 +20,7 @@ public final class Breadcrumb implements JsonUnknown, JsonSerializable { /** A timestamp representing when the breadcrumb occurred. */ - private final @NotNull Date timestamp; + private final long timestamp; /** If a message is provided, its rendered as text and the whitespace is preserved. */ private @Nullable String message; @@ -45,7 +45,12 @@ public final class Breadcrumb implements JsonUnknown, JsonSerializable { * * @param timestamp the timestamp */ + @SuppressWarnings("JavaUtilDate") public Breadcrumb(final @NotNull Date timestamp) { + this.timestamp = timestamp.getTime(); + } + + public Breadcrumb(final long timestamp) { this.timestamp = timestamp; } @@ -492,7 +497,7 @@ public static Breadcrumb fromMap( /** Breadcrumb ctor */ public Breadcrumb() { - this(DateUtils.getCurrentDateTime()); + this(System.currentTimeMillis()); } /** @@ -510,9 +515,8 @@ public Breadcrumb(@Nullable String message) { * * @return the timestamp */ - @SuppressWarnings({"JdkObsolete", "JavaUtilDate"}) - public @NotNull Date getTimestamp() { - return (Date) timestamp.clone(); + public long getTimestamp() { + return timestamp; } /** @@ -634,7 +638,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Breadcrumb that = (Breadcrumb) o; - return timestamp.getTime() == that.timestamp.getTime() + return timestamp == that.timestamp && Objects.equals(message, that.message) && Objects.equals(type, that.type) && Objects.equals(category, that.category) @@ -672,7 +676,7 @@ public static final class JsonKeys { public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); - writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); + writer.name(JsonKeys.TIMESTAMP).value(logger, DateUtils.getDateTime(timestamp)); if (message != null) { writer.name(JsonKeys.MESSAGE).value(message); } diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index 6c46306cc7..4dd115544f 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -258,7 +258,7 @@ public void serialize(@NotNull SentryEnvelope envelope, @NotNull OutputStream ou } @Override - public @NotNull String serialize(@NotNull Map data) throws Exception { + public @NotNull String aaaserialize(@NotNull Map data) throws Exception { return serializeToString(data, false); } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 6868894340..f5800cf17e 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -1214,7 +1214,7 @@ private static final class SortBreadcrumbsByDate implements Comparator 0) } @Test From 48614674dad254b27255fcaecb0c9627432bcd01 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 1 Oct 2024 00:33:28 +0200 Subject: [PATCH 2/7] Revert --- sentry/src/main/java/io/sentry/JsonSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index 4dd115544f..6c46306cc7 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -258,7 +258,7 @@ public void serialize(@NotNull SentryEnvelope envelope, @NotNull OutputStream ou } @Override - public @NotNull String aaaserialize(@NotNull Map data) throws Exception { + public @NotNull String serialize(@NotNull Map data) throws Exception { return serializeToString(data, false); } From c2d497f736265eca120197316bf982891295713a Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 2 Oct 2024 11:10:18 +0200 Subject: [PATCH 3/7] Keep Date getTImestamp for compatibility --- .../java/io/sentry/android/ndk/NdkScopeObserver.java | 2 +- .../io/sentry/android/ndk/NdkScopeObserverTest.kt | 2 +- .../replay/DefaultReplayBreadcrumbConverter.kt | 6 +++--- .../sentry/android/replay/capture/CaptureStrategy.kt | 4 ++-- sentry/api/sentry.api | 2 ++ sentry/src/main/java/io/sentry/Breadcrumb.java | 11 ++++++++++- sentry/src/main/java/io/sentry/SentryClient.java | 2 +- sentry/src/test/java/io/sentry/BreadcrumbTest.kt | 2 +- .../io/sentry/protocol/BreadcrumbSerializationTest.kt | 2 +- 9 files changed, 22 insertions(+), 11 deletions(-) diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java index f54dccc06b..009bba9b81 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java @@ -49,7 +49,7 @@ public void addBreadcrumb(final @NotNull Breadcrumb crumb) { if (crumb.getLevel() != null) { level = crumb.getLevel().name().toLowerCase(Locale.ROOT); } - final String timestamp = DateUtils.getTimestamp(DateUtils.getDateTime(crumb.getTimestamp())); + final String timestamp = DateUtils.getTimestamp(crumb.getTimestamp()); String data = null; try { diff --git a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt index b44ee85161..335a7679e1 100644 --- a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt +++ b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt @@ -97,7 +97,7 @@ class NdkScopeObserverTest { setData("a", "b") type = "type" } - val timestamp = DateUtils.getTimestamp(DateUtils.getDateTime(breadcrumb.timestamp)) + val timestamp = DateUtils.getTimestamp(breadcrumb.timestamp) val data = "{\"a\":\"b\"}" sut.addBreadcrumb(breadcrumb) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt index ef54870287..3375e37f96 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt @@ -106,8 +106,8 @@ public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { } return if (!breadcrumbCategory.isNullOrEmpty()) { RRWebBreadcrumbEvent().apply { - timestamp = breadcrumb.timestamp - breadcrumbTimestamp = breadcrumb.timestamp / 1000.0 + timestamp = breadcrumb.timestampMs + breadcrumbTimestamp = breadcrumb.timestampMs / 1000.0 breadcrumbType = "default" category = breadcrumbCategory message = breadcrumbMessage @@ -134,7 +134,7 @@ public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { val httpStartTimestamp = breadcrumb.data[SpanDataConvention.HTTP_START_TIMESTAMP] val httpEndTimestamp = breadcrumb.data[SpanDataConvention.HTTP_END_TIMESTAMP] return RRWebSpanEvent().apply { - timestamp = breadcrumb.timestamp + timestamp = breadcrumb.timestampMs op = "resource.http" description = breadcrumb.data["url"] as String // can be double if it was serialized to disk diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt index 9bdc3e87cb..03e14cbc3b 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt @@ -161,8 +161,8 @@ internal interface CaptureStrategy { val urls = LinkedList() breadcrumbs.forEach { breadcrumb -> - if (breadcrumb.timestamp >= segmentTimestamp.time && - breadcrumb.timestamp < endTimestamp.time + if (breadcrumb.timestampMs >= segmentTimestamp.time && + breadcrumb.timestampMs < endTimestamp.time ) { val rrwebEvent = options .replayController diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 5f8b061acf..d0bd4db746 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -98,6 +98,7 @@ public final class io/sentry/BaggageHeader { public final class io/sentry/Breadcrumb : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun ()V + public fun (J)V public fun (Ljava/lang/String;)V public fun (Ljava/util/Date;)V public static fun debug (Ljava/lang/String;)Lio/sentry/Breadcrumb; @@ -111,6 +112,7 @@ public final class io/sentry/Breadcrumb : io/sentry/JsonSerializable, io/sentry/ public fun getMessage ()Ljava/lang/String; public fun getOrigin ()Ljava/lang/String; public fun getTimestamp ()Ljava/util/Date; + public fun getTimestampMs ()J public fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public static fun graphqlDataFetcher (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb; diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index 02349aabd8..37e0414b18 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -527,10 +527,19 @@ public Breadcrumb(@Nullable String message) { * * @return the timestamp */ - public long getTimestamp() { + public long getTimestampMs() { return timestamp; } + /** + * Returns the Breadcrumb's timestamp as java.util.Date + * + * @return the timestamp + */ + public @NotNull Date getTimestamp() { + return DateUtils.getDateTime(timestamp); + } + /** * Returns the message * diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index f5800cf17e..eeb4f05af1 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -1214,7 +1214,7 @@ private static final class SortBreadcrumbsByDate implements Comparator 0) + assertTrue(breadcrumb.timestampMs > 0) } @Test diff --git a/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt index 0e43168897..852d94f4da 100644 --- a/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt @@ -66,7 +66,7 @@ class BreadcrumbSerializationTest { val actual = Breadcrumb.fromMap(map, SentryOptions()) val expected = fixture.getSut() - assertEquals(expected.timestamp, actual?.timestamp) + assertEquals(expected.timestampMs, actual?.timestampMs) assertEquals(expected.message, actual?.message) assertEquals(expected.type, actual?.type) assertEquals(expected.data, actual?.data) From 3bdcf9278545db5e98fad1312bc7b8b86f214c85 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 2 Oct 2024 11:11:56 +0200 Subject: [PATCH 4/7] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f43332c4..4e6bc7c535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - For example, (this is already a default behavior) to redact all `TextView`s and their subclasses (`RadioButton`, `EditText`, etc.): `options.experimental.sessionReplay.addRedactViewClass("android.widget.TextView")` - If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified - Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669)) +- Fix potential ANRs due to `Calendar.getInstance` usage in Breadcrumbs constructor ([#3736](https://github.com/getsentry/sentry-java/pull/3736)) *Breaking changes*: From 663d6aa0cbed38e71e2f357498dbc10b51d82822 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 2 Oct 2024 11:15:06 +0200 Subject: [PATCH 5/7] Revert --- sentry-samples/sentry-samples-android/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index 2c94dbba9d..a8d8897519 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -133,5 +133,5 @@ dependencies { implementation(Config.Libs.composeNavigation) implementation(Config.Libs.composeMaterial) -// debugImplementation(Config.Libs.leakCanary) + debugImplementation(Config.Libs.leakCanary) } From e08f61fd50d6a9ddf01daffaae3e8bcdcf2015bf Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 2 Oct 2024 11:27:24 +0200 Subject: [PATCH 6/7] Use getTimestamp() --- sentry/src/main/java/io/sentry/Breadcrumb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index 37e0414b18..1f08a6e60b 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -717,7 +717,7 @@ public static final class JsonKeys { public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); - writer.name(JsonKeys.TIMESTAMP).value(logger, DateUtils.getDateTime(timestamp)); + writer.name(JsonKeys.TIMESTAMP).value(logger, getTimestamp()); if (message != null) { writer.name(JsonKeys.MESSAGE).value(message); } From e8103492008e0815f1ce234b0b94ac14e405bfc9 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 3 Oct 2024 11:54:50 +0200 Subject: [PATCH 7/7] Preserve Date passed in ctor --- .../DefaultReplayBreadcrumbConverter.kt | 6 ++-- .../android/replay/capture/CaptureStrategy.kt | 4 +-- sentry/api/sentry.api | 1 - .../src/main/java/io/sentry/Breadcrumb.java | 35 +++++++++++-------- .../src/main/java/io/sentry/SentryClient.java | 2 +- .../src/test/java/io/sentry/BreadcrumbTest.kt | 10 ++++-- .../protocol/BreadcrumbSerializationTest.kt | 2 +- 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt index 3375e37f96..c95b72088a 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt @@ -106,8 +106,8 @@ public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { } return if (!breadcrumbCategory.isNullOrEmpty()) { RRWebBreadcrumbEvent().apply { - timestamp = breadcrumb.timestampMs - breadcrumbTimestamp = breadcrumb.timestampMs / 1000.0 + timestamp = breadcrumb.timestamp.time + breadcrumbTimestamp = breadcrumb.timestamp.time / 1000.0 breadcrumbType = "default" category = breadcrumbCategory message = breadcrumbMessage @@ -134,7 +134,7 @@ public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { val httpStartTimestamp = breadcrumb.data[SpanDataConvention.HTTP_START_TIMESTAMP] val httpEndTimestamp = breadcrumb.data[SpanDataConvention.HTTP_END_TIMESTAMP] return RRWebSpanEvent().apply { - timestamp = breadcrumb.timestampMs + timestamp = breadcrumb.timestamp.time op = "resource.http" description = breadcrumb.data["url"] as String // can be double if it was serialized to disk diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt index 03e14cbc3b..7e9168df22 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt @@ -161,8 +161,8 @@ internal interface CaptureStrategy { val urls = LinkedList() breadcrumbs.forEach { breadcrumb -> - if (breadcrumb.timestampMs >= segmentTimestamp.time && - breadcrumb.timestampMs < endTimestamp.time + if (breadcrumb.timestamp.time >= segmentTimestamp.time && + breadcrumb.timestamp.time < endTimestamp.time ) { val rrwebEvent = options .replayController diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index d0bd4db746..530d8241f5 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -112,7 +112,6 @@ public final class io/sentry/Breadcrumb : io/sentry/JsonSerializable, io/sentry/ public fun getMessage ()Ljava/lang/String; public fun getOrigin ()Ljava/lang/String; public fun getTimestamp ()Ljava/util/Date; - public fun getTimestampMs ()J public fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public static fun graphqlDataFetcher (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb; diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index 1f08a6e60b..10b3c951d3 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -19,8 +19,11 @@ /** Series of application events */ public final class Breadcrumb implements JsonUnknown, JsonSerializable { - /** A timestamp representing when the breadcrumb occurred. */ - private final long timestamp; + /** A timestamp representing when the breadcrumb occurred in milliseconds. */ + private @Nullable final Long timestampMs; + + /** A timestamp representing when the breadcrumb occurred as java.util.Date. */ + private @Nullable Date timestamp; /** If a message is provided, its rendered as text and the whitespace is preserved. */ private @Nullable String message; @@ -53,15 +56,18 @@ public final class Breadcrumb implements JsonUnknown, JsonSerializable { */ @SuppressWarnings("JavaUtilDate") public Breadcrumb(final @NotNull Date timestamp) { - this.timestamp = timestamp.getTime(); + this.timestamp = timestamp; + this.timestampMs = null; } public Breadcrumb(final long timestamp) { - this.timestamp = timestamp; + this.timestampMs = timestamp; + this.timestamp = null; } Breadcrumb(final @NotNull Breadcrumb breadcrumb) { this.timestamp = breadcrumb.timestamp; + this.timestampMs = breadcrumb.timestampMs; this.message = breadcrumb.message; this.type = breadcrumb.type; this.category = breadcrumb.category; @@ -522,22 +528,21 @@ public Breadcrumb(@Nullable String message) { this.message = message; } - /** - * Returns the Breadcrumb's timestamp - * - * @return the timestamp - */ - public long getTimestampMs() { - return timestamp; - } - /** * Returns the Breadcrumb's timestamp as java.util.Date * * @return the timestamp */ + @SuppressWarnings("JavaUtilDate") public @NotNull Date getTimestamp() { - return DateUtils.getDateTime(timestamp); + if (timestamp != null) { + return (Date) timestamp.clone(); + } else if (timestampMs != null) { + // we memoize it here into timestamp to avoid instantiating Calendar again and again + timestamp = DateUtils.getDateTime(timestampMs); + return timestamp; + } + throw new IllegalStateException("No timestamp set for breadcrumb"); } /** @@ -677,7 +682,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Breadcrumb that = (Breadcrumb) o; - return timestamp == that.timestamp + return getTimestamp().getTime() == that.getTimestamp().getTime() && Objects.equals(message, that.message) && Objects.equals(type, that.type) && Objects.equals(category, that.category) diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index eeb4f05af1..6868894340 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -1214,7 +1214,7 @@ private static final class SortBreadcrumbsByDate implements Comparator 0) + assertNotNull(breadcrumb.timestamp) + } + + @Test + fun `breadcrumb can be created with Date timestamp`() { + val breadcrumb = Breadcrumb(Date(123L)) + assertEquals(123L, breadcrumb.timestamp.time) } @Test diff --git a/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt index 852d94f4da..0e43168897 100644 --- a/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/BreadcrumbSerializationTest.kt @@ -66,7 +66,7 @@ class BreadcrumbSerializationTest { val actual = Breadcrumb.fromMap(map, SentryOptions()) val expected = fixture.getSut() - assertEquals(expected.timestampMs, actual?.timestampMs) + assertEquals(expected.timestamp, actual?.timestamp) assertEquals(expected.message, actual?.message) assertEquals(expected.type, actual?.type) assertEquals(expected.data, actual?.data)