Skip to content

Commit 9023083

Browse files
authored
Add session replay id to Sentry Logs (#4740)
* added session replay id to logs * added sentry._internal.replay_is_buffering to logs when replay is in buffer mode
1 parent 0e2c478 commit 9023083

File tree

5 files changed

+134
-0
lines changed

5 files changed

+134
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Unreleased
44

55
## Features
6+
7+
- Add session replay id to Sentry Logs ([#4740](https://github.com/getsentry/sentry-java/pull/4740))
68
- Add support for continuous profiling of JVM applications on macOS and Linux ([#4556](https://github.com/getsentry/sentry-java/pull/4556))
79
- Sentry continuous profiling on the JVM is using async-profiler under the hood.
810

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.sentry.ISpan;
1313
import io.sentry.MeasurementUnit;
1414
import io.sentry.Sentry;
15+
import io.sentry.SentryLogLevel;
1516
import io.sentry.UpdateStatus;
1617
import io.sentry.instrumentation.file.SentryFileOutputStream;
1718
import io.sentry.protocol.Feedback;
@@ -340,7 +341,10 @@ public void run() {
340341
});
341342
});
342343

344+
Sentry.logger().log(SentryLogLevel.INFO, "Creating content view");
343345
setContentView(binding.getRoot());
346+
347+
Sentry.logger().log(SentryLogLevel.INFO, "MainActivity created");
344348
}
345349

346350
private void stackOverflow() {

sentry/src/main/java/io/sentry/logger/LoggerApi.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,26 @@ private void captureLog(
211211
"sentry.environment",
212212
new SentryLogEventAttributeValue(SentryAttributeType.STRING, environment));
213213
}
214+
215+
final @NotNull SentryId scopeReplayId = scopes.getCombinedScopeView().getReplayId();
216+
if (!SentryId.EMPTY_ID.equals(scopeReplayId)) {
217+
attributes.put(
218+
"sentry.replay_id",
219+
new SentryLogEventAttributeValue(SentryAttributeType.STRING, scopeReplayId.toString()));
220+
} else {
221+
final @NotNull SentryId controllerReplayId =
222+
scopes.getOptions().getReplayController().getReplayId();
223+
if (!SentryId.EMPTY_ID.equals(controllerReplayId)) {
224+
attributes.put(
225+
"sentry.replay_id",
226+
new SentryLogEventAttributeValue(
227+
SentryAttributeType.STRING, controllerReplayId.toString()));
228+
attributes.put(
229+
"sentry._internal.replay_is_buffering",
230+
new SentryLogEventAttributeValue(SentryAttributeType.BOOLEAN, true));
231+
}
232+
}
233+
214234
final @Nullable String release = scopes.getOptions().getRelease();
215235
if (release != null) {
216236
attributes.put(

sentry/src/test/java/io/sentry/NoOpScopeTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry
22

33
import io.sentry.Scope.IWithSession
4+
import io.sentry.protocol.SentryId
45
import kotlin.test.assertEquals
56
import kotlin.test.assertNull
67
import kotlin.test.assertSame
@@ -120,4 +121,9 @@ class NoOpScopeTest {
120121
}
121122

122123
@Test fun `clone returns the same instance`() = assertSame(NoOpScope.getInstance(), sut.clone())
124+
125+
@Test
126+
fun `getReplayId returns empty id`() {
127+
assertEquals(SentryId.EMPTY_ID, sut.replayId)
128+
}
123129
}

sentry/src/test/java/io/sentry/ScopesTest.kt

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2955,6 +2955,108 @@ class ScopesTest {
29552955
)
29562956
}
29572957

2958+
@Test
2959+
fun `adds session replay id to log attributes`() {
2960+
val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true }
2961+
val replayId = SentryId()
2962+
sut.scope.replayId = replayId
2963+
sut.logger().log(SentryLogLevel.WARN, "log message")
2964+
2965+
verify(mockClient)
2966+
.captureLog(
2967+
check {
2968+
assertEquals("log message", it.body)
2969+
val logReplayId = it.attributes?.get("sentry.replay_id")!!
2970+
assertEquals(replayId.toString(), logReplayId.value)
2971+
},
2972+
anyOrNull(),
2973+
)
2974+
}
2975+
2976+
@Test
2977+
fun `missing session replay id do not break attributes`() {
2978+
val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true }
2979+
sut.logger().log(SentryLogLevel.WARN, "log message")
2980+
2981+
verify(mockClient)
2982+
.captureLog(
2983+
check {
2984+
assertEquals("log message", it.body)
2985+
val logReplayId = it.attributes?.get("sentry.replay_id")
2986+
assertNull(logReplayId)
2987+
},
2988+
anyOrNull(),
2989+
)
2990+
}
2991+
2992+
@Test
2993+
fun `does not add session replay buffering to log attributes if no replay id in scope and in controller`() {
2994+
val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true }
2995+
2996+
sut.logger().log(SentryLogLevel.WARN, "log message")
2997+
assertEquals(SentryId.EMPTY_ID, sut.options.replayController.replayId)
2998+
2999+
verify(mockClient)
3000+
.captureLog(
3001+
check {
3002+
assertEquals("log message", it.body)
3003+
val logReplayId = it.attributes?.get("sentry.replay_id")
3004+
val logReplayType = it.attributes?.get("sentry._internal.replay_is_buffering")
3005+
assertNull(logReplayId)
3006+
assertNull(logReplayType)
3007+
},
3008+
anyOrNull(),
3009+
)
3010+
}
3011+
3012+
@Test
3013+
fun `does not add session replay buffering to log attributes if replay id in scope`() {
3014+
val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true }
3015+
val replayId = SentryId()
3016+
sut.scope.replayId = replayId
3017+
3018+
sut.logger().log(SentryLogLevel.WARN, "log message")
3019+
3020+
verify(mockClient)
3021+
.captureLog(
3022+
check {
3023+
assertEquals("log message", it.body)
3024+
val logReplayId = it.attributes?.get("sentry.replay_id")
3025+
val logReplayType = it.attributes?.get("sentry._internal.replay_is_buffering")
3026+
assertEquals(replayId.toString(), logReplayId!!.value)
3027+
assertNull(logReplayType)
3028+
},
3029+
anyOrNull(),
3030+
)
3031+
}
3032+
3033+
@Test
3034+
fun `adds session replay buffering to log attributes if replay id in controller and not in scope`() {
3035+
val mockReplayController = mock<ReplayController>()
3036+
val (sut, mockClient) =
3037+
getEnabledScopes {
3038+
it.logs.isEnabled = true
3039+
it.setReplayController(mockReplayController)
3040+
}
3041+
val replayId = SentryId()
3042+
sut.scope.replayId = SentryId.EMPTY_ID
3043+
whenever(mockReplayController.replayId).thenReturn(replayId)
3044+
3045+
sut.logger().log(SentryLogLevel.WARN, "log message")
3046+
3047+
verify(mockClient)
3048+
.captureLog(
3049+
check {
3050+
assertEquals("log message", it.body)
3051+
val logReplayId = it.attributes?.get("sentry.replay_id")
3052+
val logReplayType = it.attributes?.get("sentry._internal.replay_is_buffering")!!
3053+
assertEquals(replayId.toString(), logReplayId!!.value)
3054+
assertTrue(logReplayType.value as Boolean)
3055+
},
3056+
anyOrNull(),
3057+
)
3058+
}
3059+
29583060
// endregion
29593061

29603062
@Test

0 commit comments

Comments
 (0)