Skip to content

Commit 450ed8e

Browse files
Merge 7128222 into 2e90ac7
2 parents 2e90ac7 + 7128222 commit 450ed8e

File tree

7 files changed

+124
-25
lines changed

7 files changed

+124
-25
lines changed

sentry-android-core/api/sentry-android-core.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public abstract interface class io/sentry/android/core/IDebugImagesLoader {
204204

205205
public final class io/sentry/android/core/InternalSentrySdk {
206206
public fun <init> ()V
207-
public static fun captureEnvelope ([B)Lio/sentry/protocol/SentryId;
207+
public static fun captureEnvelope ([BZ)Lio/sentry/protocol/SentryId;
208208
public static fun getAppStartMeasurement ()Ljava/util/Map;
209209
public static fun getCurrentScope ()Lio/sentry/IScope;
210210
public static fun serializeScope (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/IScope;)Ljava/util/Map;

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package io.sentry.android.core;
22

3+
import static io.sentry.SentryLevel.DEBUG;
4+
import static io.sentry.SentryLevel.INFO;
5+
import static io.sentry.SentryLevel.WARNING;
6+
37
import android.content.Context;
48
import android.content.pm.PackageInfo;
59
import android.content.pm.PackageManager;
@@ -10,6 +14,7 @@
1014
import io.sentry.IScope;
1115
import io.sentry.ISerializer;
1216
import io.sentry.ObjectWriter;
17+
import io.sentry.Sentry;
1318
import io.sentry.SentryEnvelope;
1419
import io.sentry.SentryEnvelopeItem;
1520
import io.sentry.SentryEvent;
@@ -19,12 +24,14 @@
1924
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
2025
import io.sentry.android.core.performance.AppStartMetrics;
2126
import io.sentry.android.core.performance.TimeSpan;
27+
import io.sentry.cache.EnvelopeCache;
2228
import io.sentry.protocol.App;
2329
import io.sentry.protocol.Device;
2430
import io.sentry.protocol.SentryId;
2531
import io.sentry.protocol.User;
2632
import io.sentry.util.MapObjectWriter;
2733
import java.io.ByteArrayInputStream;
34+
import java.io.File;
2835
import java.io.InputStream;
2936
import java.util.ArrayList;
3037
import java.util.HashMap;
@@ -148,7 +155,8 @@ public static Map<String, Object> serializeScope(
148155
* captured
149156
*/
150157
@Nullable
151-
public static SentryId captureEnvelope(final @NotNull byte[] envelopeData) {
158+
public static SentryId captureEnvelope(
159+
final @NotNull byte[] envelopeData, final boolean maybeStartNewSession) {
152160
final @NotNull IHub hub = HubAdapter.getInstance();
153161
final @NotNull SentryOptions options = hub.getOptions();
154162

@@ -184,6 +192,10 @@ public static SentryId captureEnvelope(final @NotNull byte[] envelopeData) {
184192
if (session != null) {
185193
final SentryEnvelopeItem sessionItem = SentryEnvelopeItem.fromSession(serializer, session);
186194
envelopeItems.add(sessionItem);
195+
deleteCurrentSessionFile(options);
196+
if (maybeStartNewSession) {
197+
Sentry.startSession();
198+
}
187199
}
188200

189201
final SentryEnvelope repackagedEnvelope =
@@ -233,15 +245,15 @@ private static void addTimeSpanToSerializedSpans(TimeSpan span, List<Map<String,
233245
HubAdapter.getInstance()
234246
.getOptions()
235247
.getLogger()
236-
.log(SentryLevel.WARNING, "Can not convert not-started TimeSpan to Map for Hybrid SDKs.");
248+
.log(WARNING, "Can not convert not-started TimeSpan to Map for Hybrid SDKs.");
237249
return;
238250
}
239251

240252
if (span.hasNotStopped()) {
241253
HubAdapter.getInstance()
242254
.getOptions()
243255
.getLogger()
244-
.log(SentryLevel.WARNING, "Can not convert not-stopped TimeSpan to Map for Hybrid SDKs.");
256+
.log(WARNING, "Can not convert not-stopped TimeSpan to Map for Hybrid SDKs.");
245257
return;
246258
}
247259

@@ -252,6 +264,26 @@ private static void addTimeSpanToSerializedSpans(TimeSpan span, List<Map<String,
252264
spans.add(spanMap);
253265
}
254266

267+
private static void deleteCurrentSessionFile(final @NotNull SentryOptions options) {
268+
final String cacheDirPath = options.getCacheDirPath();
269+
if (cacheDirPath == null) {
270+
options.getLogger().log(INFO, "Cache dir is not set, not deleting the current session.");
271+
return;
272+
}
273+
274+
if (!options.isEnableAutoSessionTracking()) {
275+
options
276+
.getLogger()
277+
.log(DEBUG, "Session tracking is disabled, bailing from deleting current session file.");
278+
return;
279+
}
280+
281+
final File sessionFile = EnvelopeCache.getCurrentSessionFile(cacheDirPath);
282+
if (!sessionFile.delete()) {
283+
options.getLogger().log(WARNING, "Failed to delete the current session file.");
284+
}
285+
}
286+
255287
@Nullable
256288
private static Session updateSession(
257289
final @NotNull IHub hub,
@@ -268,11 +300,14 @@ private static Session updateSession(
268300
if (updated) {
269301
if (session.getStatus() == Session.State.Crashed) {
270302
session.end();
303+
// Session needs to be removed from the scope, otherwise it will be send twice
304+
// standalone and with the crash event
305+
scope.clearSession();
271306
}
272307
sessionRef.set(session);
273308
}
274309
} else {
275-
options.getLogger().log(SentryLevel.INFO, "Session is null on updateSession");
310+
options.getLogger().log(INFO, "Session is null on updateSession");
276311
}
277312
});
278313
return sessionRef.get();

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

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicReference
3939
import kotlin.test.BeforeTest
4040
import kotlin.test.Test
4141
import kotlin.test.assertEquals
42+
import kotlin.test.assertNotEquals
4243
import kotlin.test.assertNotNull
4344
import kotlin.test.assertNull
4445
import kotlin.test.assertTrue
@@ -84,7 +85,7 @@ class InternalSentrySdkTest {
8485
capturedEnvelopes.clear()
8586
}
8687

87-
fun captureEnvelopeWithEvent(event: SentryEvent = SentryEvent()) {
88+
fun captureEnvelopeWithEvent(event: SentryEvent = SentryEvent(), maybeStartNewSession: Boolean = false) {
8889
// create an envelope with session data
8990
val options = Sentry.getCurrentHub().options
9091
val eventId = SentryId()
@@ -103,7 +104,24 @@ class InternalSentrySdkTest {
103104
options.serializer.serialize(envelope, outputStream)
104105
val data = outputStream.toByteArray()
105106

106-
InternalSentrySdk.captureEnvelope(data)
107+
InternalSentrySdk.captureEnvelope(data, maybeStartNewSession)
108+
}
109+
110+
fun createSentryEventWithUnhandledException(): SentryEvent {
111+
return SentryEvent(RuntimeException()).apply {
112+
val mechanism = Mechanism()
113+
mechanism.isHandled = false
114+
115+
val factory = SentryExceptionFactory(mock())
116+
val sentryExceptions = factory.getSentryExceptions(
117+
ExceptionMechanismException(
118+
mechanism,
119+
Throwable(),
120+
Thread()
121+
)
122+
)
123+
exceptions = sentryExceptions
124+
}
107125
}
108126

109127
fun mockFinishedAppStart() {
@@ -313,7 +331,7 @@ class InternalSentrySdkTest {
313331

314332
@Test
315333
fun `captureEnvelope fails if payload is invalid`() {
316-
assertNull(InternalSentrySdk.captureEnvelope(ByteArray(8)))
334+
assertNull(InternalSentrySdk.captureEnvelope(ByteArray(8), false))
317335
}
318336

319337
@Test
@@ -337,27 +355,12 @@ class InternalSentrySdkTest {
337355
}
338356

339357
@Test
340-
fun `captureEnvelope correctly enriches the envelope with session data`() {
358+
fun `captureEnvelope correctly enriches the envelope with session data and does not start new session`() {
341359
val fixture = Fixture()
342360
fixture.init(context)
343361

344362
// when capture envelope is called with an crashed event
345-
fixture.captureEnvelopeWithEvent(
346-
SentryEvent(RuntimeException()).apply {
347-
val mechanism = Mechanism()
348-
mechanism.isHandled = false
349-
350-
val factory = SentryExceptionFactory(mock())
351-
val sentryExceptions = factory.getSentryExceptions(
352-
ExceptionMechanismException(
353-
mechanism,
354-
Throwable(),
355-
Thread()
356-
)
357-
)
358-
exceptions = sentryExceptions
359-
}
360-
)
363+
fixture.captureEnvelopeWithEvent(fixture.createSentryEventWithUnhandledException())
361364

362365
val capturedEnvelope = fixture.capturedEnvelopes.first()
363366
val capturedEnvelopeItems = capturedEnvelope.items.toList()
@@ -380,6 +383,51 @@ class InternalSentrySdkTest {
380383
scopeRef.set(scope)
381384
}
382385
assertEquals(Session.State.Crashed, scopeRef.get().session!!.status)
386+
assertEquals(capturedSession.sessionId, scopeRef.get().session!!.sessionId)
387+
}
388+
389+
@Test
390+
fun `captureEnvelope starts new session when enabled`() {
391+
val fixture = Fixture()
392+
fixture.init(context)
393+
394+
// when capture envelope is called with an crashed event
395+
fixture.captureEnvelopeWithEvent(fixture.createSentryEventWithUnhandledException(), true)
396+
397+
val scopeRef = AtomicReference<IScope>()
398+
Sentry.configureScope { scope ->
399+
scopeRef.set(scope)
400+
}
401+
402+
// first envelope is the new session start
403+
val capturedStartSessionEnvelope = fixture.capturedEnvelopes.first()
404+
val capturedNewSessionStart = fixture.options.serializer.deserialize(
405+
InputStreamReader(ByteArrayInputStream(capturedStartSessionEnvelope.items.toList()[0].data)),
406+
Session::class.java
407+
)!!
408+
assertEquals(capturedNewSessionStart.sessionId, scopeRef.get().session!!.sessionId)
409+
assertEquals(Session.State.Ok, capturedNewSessionStart.status)
410+
411+
val capturedEnvelope = fixture.capturedEnvelopes.last()
412+
val capturedEnvelopeItems = capturedEnvelope.items.toList()
413+
414+
// there should be two envelopes session start and captured crash
415+
assertEquals(2, fixture.capturedEnvelopes.size)
416+
417+
// then it should contain the original event + session
418+
assertEquals(2, capturedEnvelopeItems.size)
419+
assertEquals(SentryItemType.Event, capturedEnvelopeItems[0].header.type)
420+
assertEquals(SentryItemType.Session, capturedEnvelopeItems[1].header.type)
421+
422+
// and then the sent session should be marked as crashed
423+
val capturedSession = fixture.options.serializer.deserialize(
424+
InputStreamReader(ByteArrayInputStream(capturedEnvelopeItems[1].data)),
425+
Session::class.java
426+
)!!
427+
assertEquals(Session.State.Crashed, capturedSession.status)
428+
429+
// and the local session should be a new session
430+
assertNotEquals(capturedSession.sessionId, scopeRef.get().session!!.sessionId)
383431
}
384432

385433
@Test

sentry/api/sentry.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ public abstract interface class io/sentry/IScope {
667667
public abstract fun clear ()V
668668
public abstract fun clearAttachments ()V
669669
public abstract fun clearBreadcrumbs ()V
670+
public abstract fun clearSession ()V
670671
public abstract fun clearTransaction ()V
671672
public abstract fun clone ()Lio/sentry/IScope;
672673
public abstract fun endSession ()Lio/sentry/Session;
@@ -1221,6 +1222,7 @@ public final class io/sentry/NoOpScope : io/sentry/IScope {
12211222
public fun clear ()V
12221223
public fun clearAttachments ()V
12231224
public fun clearBreadcrumbs ()V
1225+
public fun clearSession ()V
12241226
public fun clearTransaction ()V
12251227
public fun clone ()Lio/sentry/IScope;
12261228
public synthetic fun clone ()Ljava/lang/Object;
@@ -1592,6 +1594,7 @@ public final class io/sentry/Scope : io/sentry/IScope {
15921594
public fun clear ()V
15931595
public fun clearAttachments ()V
15941596
public fun clearBreadcrumbs ()V
1597+
public fun clearSession ()V
15951598
public fun clearTransaction ()V
15961599
public fun clone ()Lio/sentry/IScope;
15971600
public synthetic fun clone ()Ljava/lang/Object;

sentry/src/main/java/io/sentry/IScope.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ public interface IScope {
352352
@Nullable
353353
Session getSession();
354354

355+
@ApiStatus.Internal
356+
void clearSession();
357+
355358
@ApiStatus.Internal
356359
void setPropagationContext(final @NotNull PropagationContext propagationContext);
357360

sentry/src/main/java/io/sentry/NoOpScope.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ public void withTransaction(Scope.@NotNull IWithTransaction callback) {}
219219
return null;
220220
}
221221

222+
@ApiStatus.Internal
223+
@Override
224+
public void clearSession() {}
225+
222226
@ApiStatus.Internal
223227
@Override
224228
public void setPropagationContext(@NotNull PropagationContext propagationContext) {}

sentry/src/main/java/io/sentry/Scope.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,12 @@ public SentryOptions getOptions() {
913913
return session;
914914
}
915915

916+
@ApiStatus.Internal
917+
@Override
918+
public void clearSession() {
919+
session = null;
920+
}
921+
916922
@ApiStatus.Internal
917923
@Override
918924
public void setPropagationContext(final @NotNull PropagationContext propagationContext) {

0 commit comments

Comments
 (0)