Skip to content

Commit ba64d7b

Browse files
Alter AEI session ID attributes (#3276)
* fix: rate limit manual end via user session * persist inactivity timeout for user sessions * return correct ID in SdkStateApiDelegate * refactor: alter timer syntax * refactor inactivity timeout * update semantic conventions * enforce manual session end requirements correctly * handle backwards time shift * support correct session ordinals * wire through session ids * refactor: prevent setting part id without user session id * wire in correct session ids for native crashes * refactor: alter AEI session id attributes
1 parent e2ac120 commit ba64d7b

12 files changed

Lines changed: 91 additions & 29 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ From `CONTRIBUTING.md`:
301301
- Lint suppression must be done in code with explanation
302302
- Commits should be reasonably small (<500 lines diff) with proper messages
303303
- PR template has `Goal` and `Testing` sections
304+
- Verify changes with `./gradlew build -x embrace-gradle-plugin-integration-tests:test`
304305

305306
---
306307

embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/id/SessionPartTracker.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,10 @@ interface SessionPartTracker {
3636
* Adds a listener that will be called when the active session ends. This will be called prior to the session ending.
3737
*/
3838
fun addSessionPartEndListener(listener: SessionPartEndListener)
39+
40+
/**
41+
* Persists both the session part ID and user session ID into the process state summary so
42+
* that AEI logs can recover them after a process death.
43+
*/
44+
fun setProcessStateSummary(sessionPartId: String, userSessionId: String)
3945
}

embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/id/SessionPartTrackerImpl.kt

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,15 @@ internal class SessionPartTrackerImpl(
4848
sessionChangeListeners.forEach(SessionPartChangeListener::onPostSessionChange)
4949
}
5050

51-
if (postTransitionAppState == AppState.FOREGROUND) {
52-
setSessionIdToProcessStateSummary(activeSession?.sessionPartId)
53-
}
54-
5551
return activeSession
5652
}
5753

58-
/**
59-
* On android 11+, we use ActivityManager#setProcessStateSummary to store sessionId
60-
* Then, this information will be included in the record of ApplicationExitInfo on the death of the current calling process
61-
*
62-
* @param sessionId current session id
63-
*/
64-
private fun setSessionIdToProcessStateSummary(sessionId: String?) {
54+
override fun setProcessStateSummary(sessionPartId: String, userSessionId: String) {
6555
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
66-
if (sessionId != null) {
67-
try {
68-
activityManager?.setProcessStateSummary(sessionId.toByteArray())
69-
} catch (e: Throwable) {
70-
logger.trackInternalError(InternalErrorType.PROCESS_STATE_SUMMARY_FAIL, e)
71-
}
56+
try {
57+
activityManager?.setProcessStateSummary("${sessionPartId}_$userSessionId".toByteArray())
58+
} catch (e: Throwable) {
59+
logger.trackInternalError(InternalErrorType.PROCESS_STATE_SUMMARY_FAIL, e)
7260
}
7361
}
7462
}

embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/orchestrator/SessionOrchestratorImpl.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ internal class SessionOrchestratorImpl(
332332
// update newly created session
333333
val userSession = currentUserSession()
334334
if (newSession != null && userSession != null) {
335+
if (endAppState == AppState.FOREGROUND) {
336+
sessionTracker.setProcessStateSummary(newSession.sessionPartId, userSession.userSessionId)
337+
}
335338
boundaryDelegate.prepareForNewSession()
336339
sessionSpanAttrPopulator.populateSessionSpanStartAttrs(newSession, userSession)
337340
if (transitionType != TransitionType.CRASH) {

embrace-android-instrumentation-app-exit-info/src/main/kotlin/io/embrace/android/embracesdk/internal/instrumentation/aei/AeiDataSourceImpl.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal class AeiDataSourceImpl(
6767
val capture = configService.autoDataCaptureBehavior.isNativeCrashCaptureEnabled() || !obj.hasNativeTombstone()
6868
if (capture) {
6969
val schemaType = AeiLog(
70-
sessionId = obj.sessionId,
70+
sessionPartId = obj.sessionPartId,
7171
sessionIdError = obj.sessionIdError,
7272
importance = obj.importance,
7373
pss = obj.pss,
@@ -78,7 +78,8 @@ internal class AeiDataSourceImpl(
7878
description = obj.description,
7979
traceStatus = obj.traceStatus,
8080
crashNumber = crashNumber,
81-
aeiNumber = aeiNumber
81+
aeiNumber = aeiNumber,
82+
userSessionId = obj.userSessionId,
8283
)
8384
addLog(schemaType, LogSeverity.INFO, obj.trace ?: "")
8485
}

embrace-android-instrumentation-app-exit-info/src/main/kotlin/io/embrace/android/embracesdk/internal/instrumentation/aei/AppExitInfoData.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.embrace.android.embracesdk.internal.instrumentation.aei
22

33
internal data class AppExitInfoData(
4-
val sessionId: String?,
4+
val sessionPartId: String?,
5+
6+
val userSessionId: String?,
57

68
val sessionIdError: String?,
79

embrace-android-instrumentation-app-exit-info/src/main/kotlin/io/embrace/android/embracesdk/internal/instrumentation/aei/ApplicationExitInfoExt.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import java.io.IOException
1111
import java.util.regex.Pattern
1212

1313
private val SESSION_ID_PATTERN by lazy { Pattern.compile("^[0-9a-fA-F]{32}\$").toRegex() }
14+
private const val SESSION_ID_LENGTH = 32
1415

1516
/**
1617
* Constructs an [io.embrace.android.embracesdk.internal.payload.AppExitInfoData] object from an [ApplicationExitInfo] object. The trace
@@ -23,11 +24,13 @@ internal fun ApplicationExitInfo.constructAeiObject(
2324
charLimit: Int,
2425
): AppExitInfoData? {
2526
val result = readAeiTrace(versionChecker, charLimit) ?: return null
26-
val sessionId = String(processStateSummary ?: ByteArray(0))
27+
val summary = String(processStateSummary ?: ByteArray(0))
28+
val parsed = parseProcessStateSummary(summary)
2729

2830
return AppExitInfoData(
29-
sessionId = sessionId,
30-
sessionIdError = getSessionIdValidationError(sessionId),
31+
sessionPartId = parsed.sessionPartId,
32+
userSessionId = parsed.userSessionId,
33+
sessionIdError = getSessionIdValidationError(parsed.sessionPartId),
3134
importance = importance,
3235
pss = pss,
3336
reason = reason,
@@ -87,3 +90,20 @@ private fun getSessionIdValidationError(sid: String): String = when {
8790
sid.isEmpty() || sid.matches(SESSION_ID_PATTERN) -> ""
8891
else -> "invalid session ID: $sid"
8992
}
93+
94+
/**
95+
* Parses the processStateSummary string. If it has the combined format
96+
* `{sessionPartId}_{userSessionId}` (both 32-char hex IDs delimited by `_`), both are
97+
* returned. Otherwise the entire string is treated as the session part ID with no user session ID.
98+
*/
99+
internal fun parseProcessStateSummary(summary: String): ProcessStateSummary {
100+
val expectedLength = SESSION_ID_LENGTH * 2 + 1
101+
return if (summary.length == expectedLength && summary[SESSION_ID_LENGTH] == '_') {
102+
ProcessStateSummary(
103+
sessionPartId = summary.substring(0, SESSION_ID_LENGTH),
104+
userSessionId = summary.substring(SESSION_ID_LENGTH + 1),
105+
)
106+
} else {
107+
ProcessStateSummary(sessionPartId = summary, userSessionId = null)
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.embrace.android.embracesdk.internal.instrumentation.aei
2+
3+
internal data class ProcessStateSummary(
4+
val sessionPartId: String,
5+
val userSessionId: String?,
6+
)

embrace-android-instrumentation-app-exit-info/src/test/kotlin/io/embrace/android/embracesdk/internal/instrumentation/aei/AeiDataSourceImplTest.kt

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ private const val STATUS = 1
3232
private const val DESCRIPTION = "testDescription"
3333
private const val TRACE = "testInputStream"
3434
private const val SESSION_ID = "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
35+
private const val USER_SESSION_ID = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
36+
private const val PROCESS_SUMMARY_STATE = "${SESSION_ID}_$USER_SESSION_ID"
3537

3638
internal class AeiDataSourceImplTest {
3739

@@ -50,7 +52,7 @@ internal class AeiDataSourceImplTest {
5052
private val mockAppExitInfo = mockk<ApplicationExitInfo>(relaxed = true) {
5153
every { timestamp } returns TIMESTAMP
5254
every { pid } returns PID
53-
every { processStateSummary } returns SESSION_ID.toByteArray()
55+
every { processStateSummary } returns PROCESS_SUMMARY_STATE.toByteArray()
5456
every { importance } returns IMPORTANCE
5557
every { pss } returns PSS
5658
every { reason } returns REASON
@@ -101,7 +103,7 @@ internal class AeiDataSourceImplTest {
101103
// when getCapturedData is called
102104
val attrs = getAeiLogAttrs()
103105
assertEquals(TIMESTAMP.toString(), attrs[EmbAeiAttributes.TIMESTAMP])
104-
assertEquals(SESSION_ID, attrs[EmbAeiAttributes.AEI_SESSION_ID])
106+
assertEquals(SESSION_ID, attrs[EmbAeiAttributes.AEI_SESSION_PART_ID])
105107
assertEquals(IMPORTANCE.toString(), attrs[EmbAeiAttributes.PROCESS_IMPORTANCE])
106108
assertEquals(PSS.toString(), attrs[EmbAeiAttributes.PSS])
107109
assertEquals(RSS.toString(), attrs[EmbAeiAttributes.RSS])
@@ -205,7 +207,35 @@ internal class AeiDataSourceImplTest {
205207

206208
// then the invalid session ID message should be added to the sessionIdError
207209
assertEquals("invalid session ID: $invalidSessionId", attrs[EmbAeiAttributes.SESSION_ID_ERROR])
208-
assertEquals(invalidSessionId, attrs[EmbAeiAttributes.AEI_SESSION_ID])
210+
assertEquals(invalidSessionId, attrs[EmbAeiAttributes.AEI_SESSION_PART_ID])
211+
}
212+
213+
@Test
214+
fun `aei_user_session_id is set when processStateSummary contains combined ids`() {
215+
every { mockAppExitInfo.processStateSummary } returns "${SESSION_ID}_${USER_SESSION_ID}".toByteArray()
216+
every {
217+
mockActivityManager.getHistoricalProcessExitReasons(any(), any(), any())
218+
} returns listOf(mockAppExitInfo)
219+
220+
startApplicationExitInfoService()
221+
222+
val attrs = getAeiLogAttrs()
223+
assertEquals(SESSION_ID, attrs[EmbAeiAttributes.AEI_SESSION_PART_ID])
224+
assertEquals(USER_SESSION_ID, attrs[EmbAeiAttributes.AEI_USER_SESSION_ID])
225+
}
226+
227+
@Test
228+
fun `aei_user_session_id is absent when processStateSummary contains only part id`() {
229+
every { mockAppExitInfo.processStateSummary } returns SESSION_ID.toByteArray()
230+
every {
231+
mockActivityManager.getHistoricalProcessExitReasons(any(), any(), any())
232+
} returns listOf(mockAppExitInfo)
233+
234+
startApplicationExitInfoService()
235+
236+
val attrs = getAeiLogAttrs()
237+
assertEquals(SESSION_ID, attrs[EmbAeiAttributes.AEI_SESSION_PART_ID])
238+
assertNull(attrs[EmbAeiAttributes.AEI_USER_SESSION_ID])
209239
}
210240

211241
@Test

embrace-android-instrumentation-schema/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/schema/SchemaType.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ sealed class SchemaType(
115115
}
116116

117117
class AeiLog(
118-
sessionId: String?,
118+
sessionPartId: String?,
119119
sessionIdError: String?,
120120
importance: Int?,
121121
pss: Long?,
@@ -127,9 +127,11 @@ sealed class SchemaType(
127127
traceStatus: String?,
128128
crashNumber: Int?,
129129
aeiNumber: Int?,
130+
userSessionId: String?,
130131
) : SchemaType(EmbType.System.Exit) {
131132
override val schemaAttributes: Map<String, String> = mapOf(
132-
EmbAeiAttributes.AEI_SESSION_ID to sessionId,
133+
EmbAeiAttributes.AEI_SESSION_PART_ID to sessionPartId,
134+
EmbAeiAttributes.AEI_USER_SESSION_ID to userSessionId,
133135
EmbAeiAttributes.SESSION_ID_ERROR to sessionIdError,
134136
EmbAeiAttributes.PROCESS_IMPORTANCE to importance.toString(),
135137
EmbAeiAttributes.PSS to pss.toString(),

0 commit comments

Comments
 (0)