Skip to content

Commit 97d1157

Browse files
authored
Merge branch 'main' into feat/logcat-integration
2 parents d502bb7 + 2ec7569 commit 97d1157

21 files changed

Lines changed: 479 additions & 49 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
### Features
66

7+
- Add `name` and `geo` to `User` ([#2556](https://github.com/getsentry/sentry-java/pull/2556))
78
- Add time-to-initial-display and time-to-full-display measurements to Activity transactions ([#2611](https://github.com/getsentry/sentry-java/pull/2611))
89
- Read integration list written by sentry gradle plugin from manifest ([#2598](https://github.com/getsentry/sentry-java/pull/2598))
910
- Add Logcat adapter ([#2620](https://github.com/getsentry/sentry-java/pull/2620))
1011

1112
### Fixes
1213

14+
- Fix Automatic UI transactions having wrong durations ([#2623](https://github.com/getsentry/sentry-java/pull/2623))
1315
- Fix wrong default environment in Session ([#2610](https://github.com/getsentry/sentry-java/pull/2610))
16+
- Pass through unknown sentry baggage keys into SentryEnvelopeHeader ([#2618](https://github.com/getsentry/sentry-java/pull/2618))
1417

1518
## 6.16.0
1619

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ This directory is also included in `.gitignore` not to be shown as pending chang
9696

9797
# Sentry Self Hosted Compatibility
9898

99-
Since version 3.0.0 of this SDK, Sentry version >= v20.6.0 is required. This only applies to on-premise Sentry, if you are using [sentry.io](http://sentry.io/) no action is needed.
99+
Since version 3.0.0 of this SDK, Sentry version >= v20.6.0 is required. This only applies to self-hosted Sentry, if you are using [sentry.io](http://sentry.io/) no action is needed.
100+
101+
Since version 6.0.0 of this SDK, Sentry version >= v21.9.0 is required or you have to manually disable sending client reports via the `sendClientReports` option. This only applies to self-hosted Sentry, if you are using [sentry.io](http://sentry.io/) no action is needed.
100102

101103
# Resources
102104

sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,18 +208,24 @@ private void startTracing(final @NotNull Activity activity) {
208208
}
209209
});
210210

211+
// This will be the start timestamp of the transaction, as well as the ttid/ttfd spans
212+
final @NotNull SentryDate ttidStartTime;
213+
211214
if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {
212-
transactionOptions.setStartTimestamp(appStartTime);
215+
// The first activity ttid/ttfd spans should start at the app start time
216+
ttidStartTime = appStartTime;
217+
} else {
218+
// The ttid/ttfd spans should start when the previous activity called its onPause method
219+
ttidStartTime = lastPausedTime;
213220
}
221+
transactionOptions.setStartTimestamp(ttidStartTime);
214222

215223
// we can only bind to the scope if there's no running transaction
216224
ITransaction transaction =
217225
hub.startTransaction(
218226
new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP),
219227
transactionOptions);
220228

221-
final @NotNull SentryDate ttidStartTime;
222-
223229
// in case appStartTime isn't available, we don't create a span for it.
224230
if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {
225231
// start specific span for app start
@@ -233,12 +239,6 @@ private void startTracing(final @NotNull Activity activity) {
233239
// in case there's already an end time (e.g. due to deferred SDK init)
234240
// we can finish the app-start span
235241
finishAppStartSpan();
236-
237-
// The first activity ttid/ttfd spans should start at the app start time
238-
ttidStartTime = appStartTime;
239-
} else {
240-
// The ttid/ttfd spans should start when the previous activity called its onPause method
241-
ttidStartTime = lastPausedTime;
242242
}
243243
ttidSpanMap.put(
244244
activity,

sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ import io.sentry.protocol.MeasurementValue
3232
import io.sentry.protocol.TransactionNameSource
3333
import io.sentry.test.getProperty
3434
import org.junit.runner.RunWith
35-
import org.mockito.ArgumentCaptor
3635
import org.mockito.kotlin.any
3736
import org.mockito.kotlin.anyOrNull
37+
import org.mockito.kotlin.argumentCaptor
3838
import org.mockito.kotlin.check
3939
import org.mockito.kotlin.eq
4040
import org.mockito.kotlin.mock
@@ -73,7 +73,9 @@ class ActivityLifecycleIntegrationTest {
7373
val activityFramesTracker = mock<ActivityFramesTracker>()
7474
val fullyDisplayedReporter = FullyDisplayedReporter.getInstance()
7575
val transactionFinishedCallback = mock<TransactionFinishedCallback>()
76-
lateinit var transaction: SentryTracer
76+
77+
// we init the transaction with a mock to avoid errors when finishing it after tests that don't start it
78+
var transaction: SentryTracer = mock()
7779
val buildInfo = mock<BuildInfoProvider>()
7880

7981
fun getSut(
@@ -85,15 +87,13 @@ class ActivityLifecycleIntegrationTest {
8587

8688
whenever(hub.options).thenReturn(options)
8789

88-
// TODO: we should let the ActivityLifecycleIntegration create the proper transaction here
89-
val transactionOptions = TransactionOptions().apply {
90-
isWaitForChildren = true
91-
if (options.isEnableActivityLifecycleTracingAutoFinish) {
92-
idleTimeout = options.idleTimeout
93-
}
90+
// We let the ActivityLifecycleIntegration create the proper transaction here
91+
val argumentCaptor = argumentCaptor<TransactionOptions>()
92+
whenever(hub.startTransaction(any(), argumentCaptor.capture())).thenAnswer {
93+
val t = SentryTracer(context, hub, argumentCaptor.lastValue, transactionFinishedCallback)
94+
transaction = t
95+
return@thenAnswer t
9496
}
95-
transaction = SentryTracer(context, hub, transactionOptions, transactionFinishedCallback)
96-
whenever(hub.startTransaction(any(), any<TransactionOptions>())).thenReturn(transaction)
9797
whenever(buildInfo.sdkInfoVersion).thenReturn(apiVersion)
9898

9999
whenever(application.getSystemService(any())).thenReturn(am)
@@ -842,7 +842,7 @@ class ActivityLifecycleIntegrationTest {
842842
sut.onActivityCreated(activity, fixture.bundle)
843843

844844
// call only once
845-
verify(fixture.hub).startTransaction(any(), check<TransactionOptions> { assertNull(it.startTimestamp) })
845+
verify(fixture.hub).startTransaction(any(), check<TransactionOptions> { assertNotEquals(date, it.startTimestamp) })
846846
}
847847

848848
@Test
@@ -1024,26 +1024,19 @@ class ActivityLifecycleIntegrationTest {
10241024
fixture.options.tracesSampleRate = 1.0
10251025
sut.register(fixture.hub, fixture.options)
10261026

1027-
// Using ArgumentCaptor because multiple verify() throws an assertionError, even if the test passes
1028-
val parameters: ArgumentCaptor<TransactionOptions> = ArgumentCaptor.forClass(TransactionOptions::class.java)
10291027
val date = SentryNanotimeDate(Date(0), 0)
10301028
setAppStartTime()
10311029

10321030
val activity = mock<Activity>()
10331031
// First invocation: we expect to start a transaction with the appStartTime
10341032
sut.onActivityCreated(activity, fixture.bundle)
10351033
sut.onActivityPostResumed(activity)
1034+
assertEquals(date.nanoTimestamp(), fixture.transaction.startDate.nanoTimestamp())
10361035

10371036
val newActivity = mock<Activity>()
1038-
// Second invocation: we expect the transaction start timestamp not to be set
1037+
// Second invocation: we expect to start a transaction with a different start timestamp
10391038
sut.onActivityCreated(newActivity, fixture.bundle)
1040-
1041-
verify(fixture.hub, times(2)).startTransaction(any(), parameters.capture())
1042-
val capturedValues = parameters.allValues
1043-
// The first invocation contains the appStartTime
1044-
assertEquals(date.nanoTimestamp(), capturedValues[0].startTimestamp!!.nanoTimestamp())
1045-
// The second invocation contains no start timestamp
1046-
assertNull(capturedValues[1].startTimestamp)
1039+
assertNotEquals(date.nanoTimestamp(), fixture.transaction.startDate.nanoTimestamp())
10471040
}
10481041

10491042
@Test
@@ -1294,6 +1287,24 @@ class ActivityLifecycleIntegrationTest {
12941287
)
12951288
}
12961289

1290+
@Test
1291+
fun `transaction has same start timestamp of ttid and ttfd`() {
1292+
val sut = fixture.getSut()
1293+
val activity = mock<Activity>()
1294+
fixture.options.tracesSampleRate = 1.0
1295+
fixture.options.isEnableTimeToFullDisplayTracing = true
1296+
1297+
sut.register(fixture.hub, fixture.options)
1298+
sut.onActivityCreated(activity, fixture.bundle)
1299+
1300+
// The ttid span should be running
1301+
val ttidSpan = sut.ttidSpanMap[activity]
1302+
assertNotNull(ttidSpan)
1303+
1304+
assertEquals(ttidSpan.startDate, fixture.transaction.startDate)
1305+
assertEquals(sut.ttfdSpan?.startDate, fixture.transaction.startDate)
1306+
}
1307+
12971308
private fun runFirstDraw(view: View) {
12981309
// Removes OnDrawListener in the next OnGlobalLayout after onDraw
12991310
view.viewTreeObserver.dispatchOnDraw()

sentry/api/sentry.api

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class io/sentry/Baggage {
4444
public fun getThirdPartyHeader ()Ljava/lang/String;
4545
public fun getTraceId ()Ljava/lang/String;
4646
public fun getTransaction ()Ljava/lang/String;
47+
public fun getUnknown ()Ljava/util/Map;
4748
public fun getUserId ()Ljava/lang/String;
4849
public fun getUserSegment ()Ljava/lang/String;
4950
public fun isMutable ()Z
@@ -62,6 +63,7 @@ public final class io/sentry/Baggage {
6263
}
6364

6465
public final class io/sentry/Baggage$DSCKeys {
66+
public static final field ALL Ljava/util/List;
6567
public static final field ENVIRONMENT Ljava/lang/String;
6668
public static final field PUBLIC_KEY Ljava/lang/String;
6769
public static final field RELEASE Ljava/lang/String;
@@ -2866,6 +2868,33 @@ public final class io/sentry/protocol/Device$JsonKeys {
28662868
public fun <init> ()V
28672869
}
28682870

2871+
public final class io/sentry/protocol/Geo : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
2872+
public fun <init> ()V
2873+
public fun <init> (Lio/sentry/protocol/Geo;)V
2874+
public fun getCity ()Ljava/lang/String;
2875+
public fun getCountryCode ()Ljava/lang/String;
2876+
public fun getRegion ()Ljava/lang/String;
2877+
public fun getUnknown ()Ljava/util/Map;
2878+
public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V
2879+
public fun setCity (Ljava/lang/String;)V
2880+
public fun setCountryCode (Ljava/lang/String;)V
2881+
public fun setRegion (Ljava/lang/String;)V
2882+
public fun setUnknown (Ljava/util/Map;)V
2883+
}
2884+
2885+
public final class io/sentry/protocol/Geo$Deserializer : io/sentry/JsonDeserializer {
2886+
public fun <init> ()V
2887+
public fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Geo;
2888+
public synthetic fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
2889+
}
2890+
2891+
public final class io/sentry/protocol/Geo$JsonKeys {
2892+
public static final field CITY Ljava/lang/String;
2893+
public static final field COUNTRY_CODE Ljava/lang/String;
2894+
public static final field REGION Ljava/lang/String;
2895+
public fun <init> ()V
2896+
}
2897+
28692898
public final class io/sentry/protocol/Gpu : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
28702899
public static final field TYPE Ljava/lang/String;
28712900
public fun <init> ()V
@@ -3536,17 +3565,21 @@ public final class io/sentry/protocol/User : io/sentry/JsonSerializable, io/sent
35363565
public fun <init> (Lio/sentry/protocol/User;)V
35373566
public fun getData ()Ljava/util/Map;
35383567
public fun getEmail ()Ljava/lang/String;
3568+
public fun getGeo ()Lio/sentry/protocol/Geo;
35393569
public fun getId ()Ljava/lang/String;
35403570
public fun getIpAddress ()Ljava/lang/String;
3571+
public fun getName ()Ljava/lang/String;
35413572
public fun getOthers ()Ljava/util/Map;
35423573
public fun getSegment ()Ljava/lang/String;
35433574
public fun getUnknown ()Ljava/util/Map;
35443575
public fun getUsername ()Ljava/lang/String;
35453576
public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V
35463577
public fun setData (Ljava/util/Map;)V
35473578
public fun setEmail (Ljava/lang/String;)V
3579+
public fun setGeo (Lio/sentry/protocol/Geo;)V
35483580
public fun setId (Ljava/lang/String;)V
35493581
public fun setIpAddress (Ljava/lang/String;)V
3582+
public fun setName (Ljava/lang/String;)V
35503583
public fun setOthers (Ljava/util/Map;)V
35513584
public fun setSegment (Ljava/lang/String;)V
35523585
public fun setUnknown (Ljava/util/Map;)V
@@ -3562,8 +3595,10 @@ public final class io/sentry/protocol/User$Deserializer : io/sentry/JsonDeserial
35623595
public final class io/sentry/protocol/User$JsonKeys {
35633596
public static final field DATA Ljava/lang/String;
35643597
public static final field EMAIL Ljava/lang/String;
3598+
public static final field GEO Ljava/lang/String;
35653599
public static final field ID Ljava/lang/String;
35663600
public static final field IP_ADDRESS Ljava/lang/String;
3601+
public static final field NAME Ljava/lang/String;
35673602
public static final field OTHER Ljava/lang/String;
35683603
public static final field SEGMENT Ljava/lang/String;
35693604
public static final field USERNAME Ljava/lang/String;

sentry/src/main/java/io/sentry/Baggage.java

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
import java.text.DecimalFormat;
1212
import java.text.DecimalFormatSymbols;
1313
import java.util.ArrayList;
14+
import java.util.Arrays;
1415
import java.util.HashMap;
1516
import java.util.List;
1617
import java.util.Locale;
1718
import java.util.Map;
1819
import java.util.Set;
1920
import java.util.TreeSet;
21+
import java.util.concurrent.ConcurrentHashMap;
2022
import org.jetbrains.annotations.ApiStatus;
2123
import org.jetbrains.annotations.NotNull;
2224
import org.jetbrains.annotations.Nullable;
@@ -312,6 +314,23 @@ public void set(final @NotNull String key, final @Nullable String value) {
312314
}
313315
}
314316

317+
@ApiStatus.Internal
318+
public @NotNull Map<String, Object> getUnknown() {
319+
final @NotNull Map<String, Object> unknown = new ConcurrentHashMap<>();
320+
for (Map.Entry<String, String> keyValue : this.keyValues.entrySet()) {
321+
final @NotNull String key = keyValue.getKey();
322+
final @Nullable String value = keyValue.getValue();
323+
if (!DSCKeys.ALL.contains(key)) {
324+
if (value != null) {
325+
final @NotNull String unknownKey = key.replaceFirst(SENTRY_BAGGAGE_PREFIX, "");
326+
unknown.put(unknownKey, value);
327+
}
328+
}
329+
}
330+
331+
return unknown;
332+
}
333+
315334
@ApiStatus.Internal
316335
public void setValuesFromTransaction(
317336
final @NotNull ITransaction transaction,
@@ -390,15 +409,18 @@ public TraceContext toTraceContext() {
390409
final String publicKey = getPublicKey();
391410

392411
if (traceIdString != null && publicKey != null) {
393-
return new TraceContext(
394-
new SentryId(traceIdString),
395-
publicKey,
396-
getRelease(),
397-
getEnvironment(),
398-
getUserId(),
399-
getUserSegment(),
400-
getTransaction(),
401-
getSampleRate());
412+
final @NotNull TraceContext traceContext =
413+
new TraceContext(
414+
new SentryId(traceIdString),
415+
publicKey,
416+
getRelease(),
417+
getEnvironment(),
418+
getUserId(),
419+
getUserSegment(),
420+
getTransaction(),
421+
getSampleRate());
422+
traceContext.setUnknown(getUnknown());
423+
return traceContext;
402424
} else {
403425
return null;
404426
}
@@ -414,5 +436,16 @@ public static final class DSCKeys {
414436
public static final String USER_SEGMENT = "sentry-user_segment";
415437
public static final String TRANSACTION = "sentry-transaction";
416438
public static final String SAMPLE_RATE = "sentry-sample_rate";
439+
440+
public static final List<String> ALL =
441+
Arrays.asList(
442+
TRACE_ID,
443+
PUBLIC_KEY,
444+
RELEASE,
445+
USER_ID,
446+
ENVIRONMENT,
447+
USER_SEGMENT,
448+
TRANSACTION,
449+
SAMPLE_RATE);
417450
}
418451
}

sentry/src/main/java/io/sentry/JsonSerializer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.sentry.protocol.DebugImage;
1010
import io.sentry.protocol.DebugMeta;
1111
import io.sentry.protocol.Device;
12+
import io.sentry.protocol.Geo;
1213
import io.sentry.protocol.Gpu;
1314
import io.sentry.protocol.MeasurementValue;
1415
import io.sentry.protocol.Mechanism;
@@ -108,6 +109,7 @@ public JsonSerializer(@NotNull SentryOptions options) {
108109
deserializersByClass.put(SpanId.class, new SpanId.Deserializer());
109110
deserializersByClass.put(SpanStatus.class, new SpanStatus.Deserializer());
110111
deserializersByClass.put(User.class, new User.Deserializer());
112+
deserializersByClass.put(Geo.class, new Geo.Deserializer());
111113
deserializersByClass.put(UserFeedback.class, new UserFeedback.Deserializer());
112114
deserializersByClass.put(ClientReport.class, new ClientReport.Deserializer());
113115
deserializersByClass.put(ViewHierarchyNode.class, new ViewHierarchyNode.Deserializer());

sentry/src/main/java/io/sentry/Span.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,12 @@ public void finish(final @Nullable SpanStatus status, final @Nullable SentryDate
193193
@Nullable SentryDate minChildStart = null;
194194
@Nullable SentryDate maxChildEnd = null;
195195

196-
final @NotNull List<Span> children = getChildren();
196+
// The root span should be trimmed based on all children, but the other spans, like the
197+
// jetpack composition should be trimmed based on its direct children only
198+
final @NotNull List<Span> children =
199+
transaction.getRoot().getSpanId().equals(getSpanId())
200+
? transaction.getChildren()
201+
: getDirectChildren();
197202
for (final Span child : children) {
198203
if (minChildStart == null || child.getStartDate().isBefore(minChildStart)) {
199204
minChildStart = child.getStartDate();
@@ -389,7 +394,7 @@ SpanOptions getOptions() {
389394
}
390395

391396
@NotNull
392-
private List<Span> getChildren() {
397+
private List<Span> getDirectChildren() {
393398
final List<Span> children = new ArrayList<>();
394399
final Iterator<Span> iterator = transaction.getSpans().iterator();
395400

0 commit comments

Comments
 (0)