Skip to content

Commit 052fcd9

Browse files
committed
Merge branch 'main' into claude/dreamy-solomon
2 parents 9d3cb80 + 455eb6e commit 052fcd9

39 files changed

Lines changed: 595 additions & 95 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
# Skip on PRs from forks, which don't have access to the upload secret
5353
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
5454
run: |
55-
sentry-cli build snapshots ./sentry-android-core/build/test-snapshots \
55+
sentry-cli snapshots upload ./sentry-android-core/build/test-snapshots \
5656
--app-id sentry-android-core
5757
env:
5858
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

.github/workflows/integration-tests-ui.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ jobs:
8686
if [ ${#pngs[@]} -gt 0 ]; then
8787
mkdir -p replay-snapshots
8888
cp "${pngs[@]}" replay-snapshots/
89-
sentry-cli build snapshots ./replay-snapshots \
89+
sentry-cli snapshots upload ./replay-snapshots \
9090
--app-id sentry-android-replay
9191
else
9292
echo "No replay snapshot files found, skipping upload"

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Changelog
22

3-
## Unreleased
3+
## 8.49.0
44

55
### Features
66

7+
- Session Replay: Record segment names (transaction names) ([#5763](https://github.com/getsentry/sentry-java/pull/5763))
8+
79
- Add `io.sentry:sentry-opentelemetry-bom` to align Sentry OpenTelemetry modules with tested OpenTelemetry dependencies ([#5629](https://github.com/getsentry/sentry-java/pull/5629))
810
- Spring Boot Gradle plugin: add the Sentry BOM to `dependencyManagement`; explicit imports are applied after Spring Boot's implicit BOM
911
```kotlin
@@ -34,6 +36,14 @@
3436
- Session Replay: Fix error-to-replay linkage in `buffer` mode ([#5754](https://github.com/getsentry/sentry-java/pull/5754))
3537
- Prevent logs and metrics from remaining queued after a flush scheduling race ([#5756](https://github.com/getsentry/sentry-java/pull/5756))
3638
- Fix main thread identification for tombstone (native crash) events ([#5742](https://github.com/getsentry/sentry-java/pull/5742))
39+
- Prevent malformed JDBC URLs, which may contain credentials, from being printed to stdout ([#5656](https://github.com/getsentry/sentry-java/pull/5656))
40+
- Restrict JVM-global proxy authentication credentials to challenges from the configured proxy host ([#5656](https://github.com/getsentry/sentry-java/pull/5656))
41+
- Sanitize Spring 7 and Spring Jakarta WebClient span descriptions to prevent embedded URL credentials from being sent to Sentry ([#5656](https://github.com/getsentry/sentry-java/pull/5656))
42+
- Respect `tracePropagationTargets` when injecting Sentry tracing headers through the OpenTelemetry OTLP propagator ([#5656](https://github.com/getsentry/sentry-java/pull/5656))
43+
44+
### Performance
45+
46+
- Schedule transaction idle/deadline timeouts on a shared, dedicated executor instead of spawning a `Timer` thread per transaction ([#5670](https://github.com/getsentry/sentry-java/pull/5670))
3747

3848
### Dependencies
3949

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ android.useAndroidX=true
1313
android.experimental.lint.version=8.13.1
1414

1515
# Release information
16-
versionName=8.48.0
16+
versionName=8.49.0
1717

1818
# Override the SDK name on native crashes on Android
1919
sentryAndroidSdkName=sentry.native.android

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.sentry.Scopes
2323
import io.sentry.Sentry
2424
import io.sentry.SentryDate
2525
import io.sentry.SentryDateProvider
26+
import io.sentry.SentryExecutorService
2627
import io.sentry.SentryNanotimeDate
2728
import io.sentry.SentryTraceHeader
2829
import io.sentry.SentryTracer
@@ -919,6 +920,8 @@ class ActivityLifecycleIntegrationTest {
919920
it.idleTimeout = 100
920921
}
921922
)
923+
// the transaction idle timeout is scheduled on the dedicated timer executor
924+
fixture.options.timerExecutorService = SentryExecutorService()
922925
sut.register(fixture.scopes, fixture.options)
923926
sut.onActivityCreated(activity, fixture.bundle)
924927

sentry-android-replay/api/sentry-android-replay.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public final class io/sentry/android/replay/ReplayIntegration : io/sentry/IConne
7676
public fun onWindowSizeChanged (II)V
7777
public fun pause ()V
7878
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
79+
public fun registerSegmentName (Ljava/lang/String;)V
7980
public fun registerTraceId (Lio/sentry/protocol/SentryId;)V
8081
public fun resume ()V
8182
public fun setBreadcrumbConverter (Lio/sentry/ReplayBreadcrumbConverter;)V

sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,13 @@ public class ReplayIntegration(
297297
captureStrategy?.registerTraceId(traceId)
298298
}
299299

300+
override fun registerSegmentName(segmentName: String) {
301+
if (!isEnabled.get() || !isRecording()) {
302+
return
303+
}
304+
captureStrategy?.registerSegmentName(segmentName)
305+
}
306+
300307
private fun pauseInternal() {
301308
lifecycleLock.acquire().use {
302309
if (!isEnabled.get() || !lifecycle.isAllowed(PAUSED)) {

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal abstract class BaseCaptureStrategy(
5454
internal companion object {
5555
private const val TAG = "CaptureStrategy"
5656
// https://github.com/getsentry/sentry-javascript/blob/30eb68fff5077211c30c61ba74625e66ab514870/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts#L41
57-
private const val MAX_TRACE_IDS = 100
57+
private const val MAX_CONTEXT_VALUES = 100
5858
}
5959

6060
private val gestureConverter = ReplayGestureConverter(dateProvider)
@@ -97,8 +97,9 @@ internal abstract class BaseCaptureStrategy(
9797
persistableAtomic(initialValue = false, propertyName = SEGMENT_KEY_FLUSHED)
9898

9999
protected val currentEvents: Deque<RRWebEvent> = ConcurrentLinkedDeque()
100-
private val traceIdsLock = Any()
101-
private val currentTraceIds: MutableList<String> = mutableListOf()
100+
private val replayContextLock = Any()
101+
private val currentTraceIds: MutableSet<String> = linkedSetOf()
102+
private val currentSegmentNames: MutableSet<String> = linkedSetOf()
102103

103104
override fun start(segmentId: Int, replayId: SentryId, replayType: ReplayType?) {
104105
cache = replayCacheProvider?.invoke(replayId) ?: ReplayCache(options, replayId)
@@ -139,11 +140,12 @@ internal abstract class BaseCaptureStrategy(
139140
breadcrumbs: List<Breadcrumb>? = null,
140141
events: Deque<RRWebEvent> = this.currentEvents,
141142
): ReplaySegment {
142-
val traceIds =
143-
synchronized(traceIdsLock) {
144-
val ids = currentTraceIds.toList()
143+
val (traceIds, segmentNames) =
144+
synchronized(replayContextLock) {
145+
val context = currentTraceIds.toList() to currentSegmentNames.toList()
145146
currentTraceIds.clear()
146-
ids
147+
currentSegmentNames.clear()
148+
context
147149
}
148150
return createSegment(
149151
scopes,
@@ -162,6 +164,7 @@ internal abstract class BaseCaptureStrategy(
162164
breadcrumbs,
163165
events,
164166
traceIds,
167+
segmentNames,
165168
)
166169
}
167170

@@ -180,12 +183,19 @@ internal abstract class BaseCaptureStrategy(
180183

181184
override fun registerTraceId(traceId: SentryId) {
182185
if (traceId != SentryId.EMPTY_ID) {
183-
synchronized(traceIdsLock) {
184-
if (currentTraceIds.size < MAX_TRACE_IDS) {
185-
val id = traceId.toString()
186-
if (!currentTraceIds.contains(id)) {
187-
currentTraceIds.add(id)
188-
}
186+
synchronized(replayContextLock) {
187+
if (currentTraceIds.size < MAX_CONTEXT_VALUES) {
188+
currentTraceIds.add(traceId.toString())
189+
}
190+
}
191+
}
192+
}
193+
194+
override fun registerSegmentName(segmentName: String) {
195+
if (segmentName.isNotEmpty()) {
196+
synchronized(replayContextLock) {
197+
if (currentSegmentNames.size < MAX_CONTEXT_VALUES) {
198+
currentSegmentNames.add(segmentName)
189199
}
190200
}
191201
}

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ internal interface CaptureStrategy {
5656

5757
fun registerTraceId(traceId: SentryId)
5858

59+
fun registerSegmentName(segmentName: String)
60+
5961
companion object {
6062
private fun Breadcrumb?.isNetworkAvailable(): Boolean =
6163
this != null &&
@@ -88,6 +90,7 @@ internal interface CaptureStrategy {
8890
breadcrumbs: List<Breadcrumb>?,
8991
events: Deque<RRWebEvent>,
9092
traceIds: List<String> = emptyList(),
93+
segmentNames: List<String> = emptyList(),
9194
): ReplaySegment {
9295
val generatedVideo =
9396
cache?.createVideoOf(
@@ -127,6 +130,7 @@ internal interface CaptureStrategy {
127130
replayBreadcrumbs,
128131
events,
129132
traceIds,
133+
segmentNames,
130134
)
131135
}
132136

@@ -147,6 +151,7 @@ internal interface CaptureStrategy {
147151
breadcrumbs: List<Breadcrumb>,
148152
events: Deque<RRWebEvent>,
149153
traceIds: List<String>,
154+
segmentNames: List<String>,
150155
): ReplaySegment {
151156
val endTimestamp = DateUtils.getDateTime(segmentTimestamp.time + videoDuration)
152157
val replay =
@@ -159,6 +164,7 @@ internal interface CaptureStrategy {
159164
this.replayType = replayType
160165
this.videoFile = video
161166
this.traceIds = traceIds
167+
this.segmentNames = segmentNames
162168
}
163169

164170
val recordingPayload = mutableListOf<RRWebEvent>()

sentry-android-replay/src/test/java/io/sentry/android/replay/capture/SessionCaptureStrategyTest.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,4 +562,58 @@ class SessionCaptureStrategyTest {
562562
any(),
563563
)
564564
}
565+
566+
@Test
567+
fun `registerSegmentName includes unique segment names in next segment and clears them`() {
568+
val now =
569+
System.currentTimeMillis() + (fixture.options.sessionReplay.sessionSegmentDuration * 5)
570+
val strategy = fixture.getSut(dateProvider = { now })
571+
strategy.start()
572+
strategy.onConfigurationChanged(fixture.recorderConfig)
573+
574+
strategy.registerSegmentName("CheckoutActivity")
575+
strategy.registerSegmentName("CheckoutActivity")
576+
strategy.registerSegmentName("ProductDetailsActivity")
577+
578+
strategy.onScreenshotRecorded(mock<Bitmap>()) {}
579+
580+
verify(fixture.scopes)
581+
.captureReplay(
582+
argThat { event ->
583+
event is SentryReplayEvent &&
584+
event.segmentNames == listOf("CheckoutActivity", "ProductDetailsActivity")
585+
},
586+
any(),
587+
)
588+
589+
strategy.onScreenshotRecorded(mock<Bitmap>()) {}
590+
591+
verify(fixture.scopes)
592+
.captureReplay(
593+
argThat { event ->
594+
event is SentryReplayEvent && event.segmentId == 1 && event.segmentNames.isNullOrEmpty()
595+
},
596+
any(),
597+
)
598+
}
599+
600+
@Test
601+
fun `registerSegmentName ignores empty names and limits names to 100`() {
602+
val now =
603+
System.currentTimeMillis() + (fixture.options.sessionReplay.sessionSegmentDuration * 5)
604+
val strategy = fixture.getSut(dateProvider = { now })
605+
strategy.start()
606+
strategy.onConfigurationChanged(fixture.recorderConfig)
607+
608+
strategy.registerSegmentName("")
609+
repeat(101) { strategy.registerSegmentName("ProductActivity$it") }
610+
611+
strategy.onScreenshotRecorded(mock<Bitmap>()) {}
612+
613+
verify(fixture.scopes)
614+
.captureReplay(
615+
argThat { event -> event is SentryReplayEvent && event.segmentNames?.size == 100 },
616+
any(),
617+
)
618+
}
565619
}

0 commit comments

Comments
 (0)