Skip to content

Commit ac1445b

Browse files
denrasegetsentry-botromtsn
authored
Add sent_at to envelope header item (#2638)
Co-authored-by: Sentry Github Bot <[email protected]> Co-authored-by: Roman Zavarnitsyn <[email protected]>
1 parent e9e1695 commit ac1445b

File tree

10 files changed

+141
-3
lines changed

10 files changed

+141
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Make log4j2 integration compatible with log4j 3.0 ([#2634](https://github.com/getsentry/sentry-java/pull/2634))
99
- Instead of relying on package scanning, we now use an annotation processor to generate `Log4j2Plugins.dat`
1010
- Create `User` and `Breadcrumb` from map ([#2614](https://github.com/getsentry/sentry-java/pull/2614))
11+
- Add `sent_at` to envelope header item ([#2638](https://github.com/getsentry/sentry-java/pull/2638))
1112

1213
### Fixes
1314

sentry-apache-http-client-5/src/main/java/io/sentry/transport/apache/ApacheHttpClientTransport.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import static io.sentry.SentryLevel.*;
44

5+
import io.sentry.DateUtils;
56
import io.sentry.Hint;
67
import io.sentry.RequestDetails;
8+
import io.sentry.SentryDate;
79
import io.sentry.SentryEnvelope;
810
import io.sentry.SentryLevel;
911
import io.sentry.SentryOptions;
@@ -76,6 +78,12 @@ public void send(final @NotNull SentryEnvelope envelope, final @NotNull Hint hin
7678
options.getClientReportRecorder().attachReportToEnvelope(filteredEnvelope);
7779

7880
if (envelopeWithClientReport != null) {
81+
82+
@NotNull SentryDate now = options.getDateProvider().now();
83+
envelopeWithClientReport
84+
.getHeader()
85+
.setSentAt(DateUtils.nanosToDate(now.nanoTimestamp()));
86+
7987
currentlyRunning.increment();
8088

8189
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

sentry-apache-http-client-5/src/test/kotlin/io/sentry/transport/apache/ApacheHttpClientTransportTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package io.sentry.transport.apache
22

3+
import io.sentry.Hint
34
import io.sentry.ILogger
45
import io.sentry.RequestDetails
56
import io.sentry.SentryEnvelope
67
import io.sentry.SentryEvent
78
import io.sentry.SentryLevel
9+
import io.sentry.SentryNanotimeDate
810
import io.sentry.SentryOptions
911
import io.sentry.SentryOptionsManipulator
1012
import io.sentry.clientreport.NoOpClientReportRecorder
@@ -23,6 +25,7 @@ import org.mockito.kotlin.spy
2325
import org.mockito.kotlin.times
2426
import org.mockito.kotlin.verify
2527
import org.mockito.kotlin.whenever
28+
import java.util.Date
2629
import java.util.concurrent.CompletableFuture
2730
import java.util.concurrent.Executors
2831
import kotlin.test.AfterTest
@@ -182,4 +185,30 @@ class ApacheHttpClientTransportTest {
182185
verify(fixture.logger).log(SentryLevel.WARNING, "Failed to flush all events within %s ms", 200L)
183186
verify(fixture.currentlyRunning, times(1)).decrement()
184187
}
188+
189+
@Test
190+
fun `sets current date to sent_at in envelope header`() {
191+
val now = Date(9001)
192+
val sut = fixture.getSut()
193+
fixture.options.dateProvider = mock()
194+
whenever(fixture.options.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0))
195+
196+
val envelope = SentryEnvelope.from(fixture.options.serializer, SentryEvent(), null)
197+
sut.send(envelope)
198+
199+
assertEquals(envelope.header.sentAt, now)
200+
}
201+
202+
@Test
203+
fun `sets current date to sent_at in envelope header when sent with hint`() {
204+
val now = Date(9001)
205+
val sut = fixture.getSut()
206+
fixture.options.dateProvider = mock()
207+
whenever(fixture.options.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0))
208+
209+
val envelope = SentryEnvelope.from(fixture.options.serializer, SentryEvent(), null)
210+
sut.send(envelope, Hint())
211+
212+
assertEquals(envelope.header.sentAt, now)
213+
}
185214
}

sentry/api/sentry.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,9 +1360,11 @@ public final class io/sentry/SentryEnvelopeHeader : io/sentry/JsonSerializable,
13601360
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SdkVersion;Lio/sentry/TraceContext;)V
13611361
public fun getEventId ()Lio/sentry/protocol/SentryId;
13621362
public fun getSdkVersion ()Lio/sentry/protocol/SdkVersion;
1363+
public fun getSentAt ()Ljava/util/Date;
13631364
public fun getTraceContext ()Lio/sentry/TraceContext;
13641365
public fun getUnknown ()Ljava/util/Map;
13651366
public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V
1367+
public fun setSentAt (Ljava/util/Date;)V
13661368
public fun setUnknown (Ljava/util/Map;)V
13671369
}
13681370

@@ -1375,6 +1377,7 @@ public final class io/sentry/SentryEnvelopeHeader$Deserializer : io/sentry/JsonD
13751377
public final class io/sentry/SentryEnvelopeHeader$JsonKeys {
13761378
public static final field EVENT_ID Ljava/lang/String;
13771379
public static final field SDK Ljava/lang/String;
1380+
public static final field SENT_AT Ljava/lang/String;
13781381
public static final field TRACE Ljava/lang/String;
13791382
public fun <init> ()V
13801383
}

sentry/src/main/java/io/sentry/SentryClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ private boolean shouldSendSessionUpdateForDroppedEvent(
303303
if (!envelopeItems.isEmpty()) {
304304
final SentryEnvelopeHeader envelopeHeader =
305305
new SentryEnvelopeHeader(sentryId, options.getSdkVersion(), traceContext);
306+
306307
return new SentryEnvelope(envelopeHeader, envelopeItems);
307308
}
308309

sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.sentry.protocol.SentryId;
55
import io.sentry.vendor.gson.stream.JsonToken;
66
import java.io.IOException;
7+
import java.util.Date;
78
import java.util.HashMap;
89
import java.util.Map;
910
import org.jetbrains.annotations.ApiStatus;
@@ -20,6 +21,8 @@ public final class SentryEnvelopeHeader implements JsonSerializable, JsonUnknown
2021

2122
private final @Nullable TraceContext traceContext;
2223

24+
private @Nullable Date sentAt;
25+
2326
private @Nullable Map<String, Object> unknown;
2427

2528
public SentryEnvelopeHeader(
@@ -56,12 +59,33 @@ public SentryEnvelopeHeader() {
5659
return traceContext;
5760
}
5861

62+
/**
63+
* Get the timestamp when the event was sent from the SDK as string in RFC 3339 format. Used for
64+
* clock drift correction of the event timestamp. The time zone must be UTC.
65+
*/
66+
public @Nullable Date getSentAt() {
67+
return sentAt;
68+
}
69+
70+
/**
71+
* Set he timestamp when the event was sent from the SDK as string in RFC 3339 format. Used * for
72+
* clock drift correction of the event timestamp. The time zone must be UTC.
73+
*
74+
* @param sentAt The timestamp should be generated as close as possible to the transmission of the
75+
* event, so that the delay between sending the envelope and receiving it on the server-side
76+
* is minimized.
77+
*/
78+
public void setSentAt(@Nullable Date sentAt) {
79+
this.sentAt = sentAt;
80+
}
81+
5982
// JsonSerializable
6083

6184
public static final class JsonKeys {
6285
public static final String EVENT_ID = "event_id";
6386
public static final String SDK = "sdk";
6487
public static final String TRACE = "trace";
88+
public static final String SENT_AT = "sent_at";
6589
}
6690

6791
@Override
@@ -77,6 +101,9 @@ public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger)
77101
if (traceContext != null) {
78102
writer.name(JsonKeys.TRACE).value(logger, traceContext);
79103
}
104+
if (sentAt != null) {
105+
writer.name(JsonKeys.SENT_AT).value(logger, DateUtils.getTimestamp(sentAt));
106+
}
80107
if (unknown != null) {
81108
for (String key : unknown.keySet()) {
82109
Object value = unknown.get(key);
@@ -96,6 +123,7 @@ public static final class Deserializer implements JsonDeserializer<SentryEnvelop
96123
SentryId eventId = null;
97124
SdkVersion sdkVersion = null;
98125
TraceContext traceContext = null;
126+
Date sentAt = null;
99127
Map<String, Object> unknown = null;
100128

101129
while (reader.peek() == JsonToken.NAME) {
@@ -110,6 +138,9 @@ public static final class Deserializer implements JsonDeserializer<SentryEnvelop
110138
case JsonKeys.TRACE:
111139
traceContext = reader.nextOrNull(logger, new TraceContext.Deserializer());
112140
break;
141+
case JsonKeys.SENT_AT:
142+
sentAt = reader.nextDateOrNull(logger);
143+
break;
113144
default:
114145
if (unknown == null) {
115146
unknown = new HashMap<>();
@@ -120,6 +151,7 @@ public static final class Deserializer implements JsonDeserializer<SentryEnvelop
120151
}
121152
SentryEnvelopeHeader sentryEnvelopeHeader =
122153
new SentryEnvelopeHeader(eventId, sdkVersion, traceContext);
154+
sentryEnvelopeHeader.setSentAt(sentAt);
123155
sentryEnvelopeHeader.setUnknown(unknown);
124156
reader.endObject();
125157
return sentryEnvelopeHeader;

sentry/src/main/java/io/sentry/transport/AsyncHttpTransport.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package io.sentry.transport;
22

3+
import io.sentry.DateUtils;
34
import io.sentry.Hint;
45
import io.sentry.ILogger;
56
import io.sentry.RequestDetails;
7+
import io.sentry.SentryDate;
68
import io.sentry.SentryEnvelope;
79
import io.sentry.SentryLevel;
810
import io.sentry.SentryOptions;
@@ -219,6 +221,7 @@ public void run() {
219221
private @NotNull TransportResult flush() {
220222
TransportResult result = this.failedResult;
221223

224+
envelope.getHeader().setSentAt(null);
222225
envelopeCache.store(envelope, hint);
223226

224227
HintUtils.runIfHasType(
@@ -233,6 +236,12 @@ public void run() {
233236
final SentryEnvelope envelopeWithClientReport =
234237
options.getClientReportRecorder().attachReportToEnvelope(envelope);
235238
try {
239+
240+
@NotNull SentryDate now = options.getDateProvider().now();
241+
envelopeWithClientReport
242+
.getHeader()
243+
.setSentAt(DateUtils.nanosToDate(now.nanoTimestamp()));
244+
236245
result = connection.send(envelopeWithClientReport);
237246
if (result.isSuccess()) {
238247
envelopeCache.discard(envelope);

sentry/src/test/java/io/sentry/protocol/SentryEnvelopeHeaderSerializationTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.protocol
22

3+
import io.sentry.DateUtils
34
import io.sentry.FileFromResources
45
import io.sentry.ILogger
56
import io.sentry.JsonObjectReader
@@ -22,7 +23,9 @@ class SentryEnvelopeHeaderSerializationTest {
2223
SentryIdSerializationTest.Fixture().getSut(),
2324
SdkVersionSerializationTest.Fixture().getSut(),
2425
TraceContextSerializationTest.Fixture().getSut()
25-
)
26+
).apply {
27+
sentAt = DateUtils.getDateTime("2020-02-07T14:16:00.000Z")
28+
}
2629
}
2730
private val fixture = Fixture()
2831

sentry/src/test/java/io/sentry/transport/AsyncHttpTransportTest.kt

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package io.sentry.transport
22

33
import io.sentry.CachedEvent
4+
import io.sentry.Hint
45
import io.sentry.SentryEnvelope
56
import io.sentry.SentryEnvelopeHeader
67
import io.sentry.SentryEnvelopeItem
78
import io.sentry.SentryEvent
9+
import io.sentry.SentryNanotimeDate
810
import io.sentry.SentryOptions
911
import io.sentry.SentryOptionsManipulator
1012
import io.sentry.Session
@@ -22,6 +24,7 @@ import org.mockito.kotlin.never
2224
import org.mockito.kotlin.verify
2325
import org.mockito.kotlin.whenever
2426
import java.io.IOException
27+
import java.util.Date
2528
import kotlin.test.Test
2629
import kotlin.test.assertEquals
2730

@@ -228,7 +231,7 @@ class AsyncHttpTransportTest {
228231
whenever(fixture.connection.send(any<SentryEnvelope>())).thenReturn(TransportResult.success())
229232
fixture.getSUT().send(envelope)
230233
verify(fixture.connection).send(
231-
check<SentryEnvelope> {
234+
check {
232235
assertEquals(1, it.items.count())
233236
}
234237
)
@@ -270,6 +273,54 @@ class AsyncHttpTransportTest {
270273
verify(fixture.executor).waitTillIdle(500)
271274
}
272275

276+
@Test
277+
fun `sets current date to sent_at in envelope header before send`() {
278+
// given
279+
val now = Date(9001)
280+
fixture.sentryOptions.dateProvider = mock()
281+
whenever(fixture.sentryOptions.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0))
282+
283+
val envelope = SentryEnvelope.from(fixture.sentryOptions.serializer, createSession(), null)
284+
whenever(fixture.transportGate.isConnected).thenReturn(true)
285+
whenever(fixture.rateLimiter.filter(any(), anyOrNull())).thenAnswer { it.arguments[0] }
286+
whenever(fixture.connection.send(any())).thenReturn(TransportResult.success())
287+
288+
// when
289+
val sut = fixture.getSUT()
290+
sut.send(envelope)
291+
292+
// then
293+
verify(fixture.connection).send(
294+
check {
295+
assertEquals(it.header.sentAt, now)
296+
}
297+
)
298+
}
299+
300+
@Test
301+
fun `sets current date to sent_at in envelope header before send when sent with hint`() {
302+
// given
303+
val now = Date(9001)
304+
fixture.sentryOptions.dateProvider = mock()
305+
whenever(fixture.sentryOptions.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0))
306+
307+
val envelope = SentryEnvelope.from(fixture.sentryOptions.serializer, createSession(), null)
308+
whenever(fixture.transportGate.isConnected).thenReturn(true)
309+
whenever(fixture.rateLimiter.filter(any(), anyOrNull())).thenAnswer { it.arguments[0] }
310+
whenever(fixture.connection.send(any())).thenReturn(TransportResult.success())
311+
312+
// when
313+
val sut = fixture.getSUT()
314+
sut.send(envelope, Hint())
315+
316+
// then
317+
verify(fixture.connection).send(
318+
check {
319+
assertEquals(it.header.sentAt, now)
320+
}
321+
)
322+
}
323+
273324
private fun createSession(): Session {
274325
return Session("123", User(), "env", "release")
275326
}

sentry/src/test/resources/json/sentry_envelope_header.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@
2727
"user_segment": "f7d8662b-5551-4ef8-b6a8-090f0561a530",
2828
"transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e",
2929
"sample_rate": "0.00000021"
30-
}
30+
},
31+
"sent_at": "2020-02-07T14:16:00.000Z"
3132
}

0 commit comments

Comments
 (0)