Skip to content

Commit d3e1be9

Browse files
committed
[Android] Add manufacturer as OOTB field (#920)
1 parent c098172 commit d3e1be9

File tree

23 files changed

+532
-83
lines changed

23 files changed

+532
-83
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
**Added**
99

10-
- Nothing yet!
10+
- Added experimental `Capture.Logger.getPreviousRunInfo()` API to get detailed previous run information.
1111

1212
**Changed**
1313

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/swift/hello_world/LoggerCustomer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ final class LoggerCustomer: NSObject, URLSessionDelegate {
115115
Logger.registerOpaqueUserID(kOpaqueUserID)
116116
Logger.logInfo("App launched. Logger configured.")
117117

118+
if let previousRunInfo = Capture.Logger.previousRunInfo {
119+
Capture.Logger.logInfo(
120+
"Bitdrift PreviousRunInfo",
121+
fields: [
122+
"hasFatallyTerminated": String(previousRunInfo.hasFatallyTerminated),
123+
]
124+
)
125+
}
126+
118127
MXMetricManager.shared.add(self)
119128
}
120129

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/Capture.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.bitdrift.capture.providers.DateProvider
2626
import io.bitdrift.capture.providers.FieldProvider
2727
import io.bitdrift.capture.providers.SystemDateProvider
2828
import io.bitdrift.capture.providers.session.SessionStrategy
29+
import io.bitdrift.capture.reports.exitinfo.PreviousRunInfo
2930
import io.bitdrift.capture.utils.BuildTypeChecker
3031
import okhttp3.HttpUrl
3132
import java.util.UUID
@@ -253,6 +254,14 @@ object Capture {
253254
}
254255
}
255256

257+
/**
258+
* Returns a snapshot of the previous app run status.
259+
*
260+
*/
261+
@JvmStatic
262+
@ExperimentalBitdriftApi
263+
fun getPreviousRunInfo(): PreviousRunInfo? = (logger() as? LoggerImpl)?.getPreviousRunInfo()
264+
256265
/**
257266
* Adds a field that should be attached to all logs emitted by the logger going forward.
258267
* If a field with a given key has already been registered with the logger, its value is

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/LoggerImpl.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ import io.bitdrift.capture.providers.toFields
5050
import io.bitdrift.capture.providers.toLegacyJniFields
5151
import io.bitdrift.capture.reports.IssueCallbackConfiguration
5252
import io.bitdrift.capture.reports.IssueReporter
53+
import io.bitdrift.capture.reports.exitinfo.LatestAppExitInfoProvider
54+
import io.bitdrift.capture.reports.exitinfo.PreviousRunInfo
55+
import io.bitdrift.capture.reports.exitinfo.PreviousRunInfoResolver
5356
import io.bitdrift.capture.reports.processor.ICompletedReportsProcessor
5457
import io.bitdrift.capture.reports.processor.IIssueReporterProcessor
5558
import io.bitdrift.capture.reports.processor.ReportProcessingSession
@@ -85,7 +88,7 @@ internal class LoggerImpl(
8588
sharedOkHttpClient: OkHttpClient = OkHttpClient(),
8689
private val apiClient: OkHttpApiClient = OkHttpApiClient(apiUrl, apiKey, client = sharedOkHttpClient),
8790
private var deviceCodeService: DeviceCodeService = DeviceCodeService(apiClient),
88-
activityManager: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager,
91+
private val activityManager: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager,
8992
bridge: IBridge = CaptureJniLibrary,
9093
private val eventListenerDispatcher: CaptureDispatchers.CommonBackground = CaptureDispatchers.CommonBackground,
9194
windowManager: IWindowManager = WindowManager(errorHandler),
@@ -112,9 +115,15 @@ internal class LoggerImpl(
112115

113116
private val sessionReplayTarget: ISessionReplayTarget
114117

118+
private val previousRunInfoResolver = PreviousRunInfoResolver(this, LatestAppExitInfoProvider)
119+
115120
private val issueReporter: IssueReporter? =
116121
if (configuration.enableFatalIssueReporting) {
117-
IssueReporter(internalLogger = this, dateProvider = dateProvider)
122+
IssueReporter(
123+
internalLogger = this,
124+
dateProvider = dateProvider,
125+
previousRunInfoResolver = previousRunInfoResolver,
126+
)
118127
} else {
119128
null
120129
}
@@ -265,6 +274,7 @@ internal class LoggerImpl(
265274
runtime,
266275
memoryMetricsProvider = memoryMetricsProvider,
267276
issueReporter = issueReporter,
277+
previousRunInfoResolver = previousRunInfoResolver,
268278
)
269279

270280
// Install the app exit logger before the Capture logger is started to ensure
@@ -274,6 +284,8 @@ internal class LoggerImpl(
274284

275285
CaptureJniLibrary.startLogger(this.loggerId)
276286

287+
previousRunInfoResolver.initLegacyWatcher(sdkDirectory)
288+
277289
// issue reporter needs to be initialized after appExitLogger and the jniLogger
278290
issueReporter?.init(
279291
activityManager = activityManager,
@@ -670,6 +682,8 @@ internal class LoggerImpl(
670682

671683
internal fun getIssueProcessor(): IIssueReporterProcessor? = issueReporter?.getIssueReporterProcessor()
672684

685+
internal fun getPreviousRunInfo(): PreviousRunInfo? = previousRunInfoResolver.get(activityManager)
686+
673687
private fun startDebugOperationsAsNeeded(context: Context) {
674688
if (!BuildTypeChecker.isDebuggable(context)) {
675689
return

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/events/lifecycle/AppExitLogger.kt

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ import io.bitdrift.capture.providers.fieldsOf
2727
import io.bitdrift.capture.reports.IIssueReporter
2828
import io.bitdrift.capture.reports.IssueReporterState
2929
import io.bitdrift.capture.reports.exitinfo.ILatestAppExitInfoProvider
30+
import io.bitdrift.capture.reports.exitinfo.IPreviousRunInfoResolver
3031
import io.bitdrift.capture.reports.exitinfo.LatestAppExitInfoProvider
3132
import io.bitdrift.capture.reports.exitinfo.LatestAppExitReasonResult
33+
import io.bitdrift.capture.reports.exitinfo.toExitReason
3234
import io.bitdrift.capture.reports.jvmcrash.CaptureUncaughtExceptionHandler
3335
import io.bitdrift.capture.reports.jvmcrash.ICaptureUncaughtExceptionHandler
3436
import io.bitdrift.capture.reports.jvmcrash.IJvmCrashListener
@@ -43,6 +45,7 @@ internal class AppExitLogger(
4345
private val latestAppExitInfoProvider: ILatestAppExitInfoProvider = LatestAppExitInfoProvider,
4446
private val captureUncaughtExceptionHandler: ICaptureUncaughtExceptionHandler = CaptureUncaughtExceptionHandler,
4547
private val issueReporter: IIssueReporter?,
48+
private val previousRunInfoResolver: IPreviousRunInfoResolver,
4649
) : IJvmCrashListener {
4750
companion object {
4851
private const val APP_EXIT_EVENT_NAME = "AppExit"
@@ -110,6 +113,8 @@ internal class AppExitLogger(
110113
return
111114
}
112115

116+
previousRunInfoResolver.persistJvmCrashState()
117+
113118
// explicitly letting it run in the caller thread
114119
logger.logInternal(
115120
LogType.LIFECYCLE,
@@ -170,7 +175,7 @@ internal class AppExitLogger(
170175
return fieldsOf(
171176
APP_EXIT_SOURCE_KEY to "ApplicationExitInfo",
172177
APP_EXIT_PROCESS_NAME_KEY to this.processName,
173-
APP_EXIT_REASON_KEY to this.reason.toReasonText(),
178+
APP_EXIT_REASON_KEY to this.reason.toExitReason().value,
174179
APP_EXIT_IMPORTANCE_KEY to this.importance.toImportanceText(),
175180
APP_EXIT_STATUS_KEY to this.status.toString(),
176181
APP_EXIT_PSS_KEY to this.pss.toString(),
@@ -179,25 +184,6 @@ internal class AppExitLogger(
179184
)
180185
}
181186

182-
private fun Int.toReasonText(): String =
183-
when (this) {
184-
ApplicationExitInfo.REASON_EXIT_SELF -> "EXIT_SELF"
185-
ApplicationExitInfo.REASON_SIGNALED -> "SIGNALED"
186-
ApplicationExitInfo.REASON_LOW_MEMORY -> "LOW_MEMORY"
187-
ApplicationExitInfo.REASON_CRASH -> "CRASH"
188-
ApplicationExitInfo.REASON_CRASH_NATIVE -> "CRASH_NATIVE"
189-
ApplicationExitInfo.REASON_ANR -> "ANR"
190-
ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> "INITIALIZATION_FAILURE"
191-
ApplicationExitInfo.REASON_PERMISSION_CHANGE -> "PERMISSION_CHANGE"
192-
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> "EXCESSIVE_RESOURCE_USAGE"
193-
ApplicationExitInfo.REASON_USER_REQUESTED -> "USER_REQUESTED"
194-
ApplicationExitInfo.REASON_USER_STOPPED -> "USER_STOPPED"
195-
ApplicationExitInfo.REASON_DEPENDENCY_DIED -> "DEPENDENCY_DIED"
196-
ApplicationExitInfo.REASON_OTHER -> "OTHER"
197-
ApplicationExitInfo.REASON_FREEZER -> "FREEZER"
198-
else -> "UNKNOWN"
199-
}
200-
201187
private fun Int.toImportanceText(): String =
202188
when (this) {
203189
RunningAppProcessInfo.IMPORTANCE_CACHED -> "CACHED"

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/reports/IssueReporter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.bitdrift.capture.providers.DateProvider
2020
import io.bitdrift.capture.reports.IssueReporterState.NotInitialized
2121
import io.bitdrift.capture.reports.IssueReporterState.RuntimeState
2222
import io.bitdrift.capture.reports.exitinfo.ILatestAppExitInfoProvider
23+
import io.bitdrift.capture.reports.exitinfo.IPreviousRunInfoResolver
2324
import io.bitdrift.capture.reports.exitinfo.LatestAppExitInfoProvider
2425
import io.bitdrift.capture.reports.exitinfo.LatestAppExitInfoProvider.mapToFatalIssueType
2526
import io.bitdrift.capture.reports.exitinfo.LatestAppExitReasonResult
@@ -50,6 +51,7 @@ internal class IssueReporter(
5051
private val internalLogger: IInternalLogger,
5152
private val backgroundThreadHandler: IBackgroundThreadHandler = CaptureDispatchers.CommonBackground,
5253
private val latestAppExitInfoProvider: ILatestAppExitInfoProvider = LatestAppExitInfoProvider,
54+
private val previousRunInfoResolver: IPreviousRunInfoResolver,
5355
private val captureUncaughtExceptionHandler: ICaptureUncaughtExceptionHandler = CaptureUncaughtExceptionHandler,
5456
private val dateProvider: DateProvider,
5557
) : IIssueReporter,
@@ -120,6 +122,7 @@ internal class IssueReporter(
120122
throwable = throwable,
121123
allThreads = Thread.getAllStackTraces(),
122124
)
125+
previousRunInfoResolver.persistJvmCrashState()
123126
}
124127

125128
override fun getLogStatusFieldsMap(): Map<String, String> =
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// capture-sdk - bitdrift's client SDK
2+
// Copyright Bitdrift, Inc. All rights reserved.
3+
//
4+
// Use of this source code is governed by a source available license that can be found in the
5+
// LICENSE file or at:
6+
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt
7+
8+
package io.bitdrift.capture.reports.exitinfo
9+
10+
import android.app.ApplicationExitInfo
11+
12+
/**
13+
* Application termination reason values.
14+
*/
15+
enum class ExitReason(
16+
/** Stable text representation safe for serialization/logging. */
17+
val value: String,
18+
) {
19+
/** App exited itself. */
20+
ExitSelf("EXIT_SELF"),
21+
22+
/** App was terminated by a signal. */
23+
Signaled("SIGNALED"),
24+
25+
/** App exited due to low memory. */
26+
LowMemory("LOW_MEMORY"),
27+
28+
/** App exited due to a JVM crash. */
29+
JvmCrash("CRASH"),
30+
31+
/** App exited due to a native crash. */
32+
NativeCrash("CRASH_NATIVE"),
33+
34+
/** App exited due to ANR. */
35+
AppNotResponding("ANR"),
36+
37+
/** App exited due to initialization failure. */
38+
InitializationFailure("INITIALIZATION_FAILURE"),
39+
40+
/** App exited due to permission changes. */
41+
PermissionChange("PERMISSION_CHANGE"),
42+
43+
/** App exited due to excessive resource usage. */
44+
ExcessiveResourceUsage("EXCESSIVE_RESOURCE_USAGE"),
45+
46+
/** App exited due to a user request. */
47+
UserRequested("USER_REQUESTED"),
48+
49+
/** App was stopped by the user. */
50+
UserStopped("USER_STOPPED"),
51+
52+
/** App exited because a dependency died. */
53+
DependencyDied("DEPENDENCY_DIED"),
54+
55+
/** Other OS exit reason. */
56+
Other("OTHER"),
57+
58+
/** App was frozen by the OS. */
59+
Freezer("FREEZER"),
60+
61+
/** Unknown or unsupported reason. */
62+
Unknown("UNKNOWN"),
63+
64+
;
65+
66+
/**
67+
* Lookup helpers for [ExitReason].
68+
*/
69+
companion object {
70+
/**
71+
* Returns the enum value matching a stable string representation.
72+
*/
73+
fun fromValue(value: String): ExitReason? = entries.firstOrNull { it.value == value }
74+
}
75+
}
76+
77+
internal fun Int.toExitReason(): ExitReason =
78+
when (this) {
79+
ApplicationExitInfo.REASON_EXIT_SELF -> ExitReason.ExitSelf
80+
ApplicationExitInfo.REASON_SIGNALED -> ExitReason.Signaled
81+
ApplicationExitInfo.REASON_LOW_MEMORY -> ExitReason.LowMemory
82+
ApplicationExitInfo.REASON_CRASH -> ExitReason.JvmCrash
83+
ApplicationExitInfo.REASON_CRASH_NATIVE -> ExitReason.NativeCrash
84+
ApplicationExitInfo.REASON_ANR -> ExitReason.AppNotResponding
85+
ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> ExitReason.InitializationFailure
86+
ApplicationExitInfo.REASON_PERMISSION_CHANGE -> ExitReason.PermissionChange
87+
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> ExitReason.ExcessiveResourceUsage
88+
ApplicationExitInfo.REASON_USER_REQUESTED -> ExitReason.UserRequested
89+
ApplicationExitInfo.REASON_USER_STOPPED -> ExitReason.UserStopped
90+
ApplicationExitInfo.REASON_DEPENDENCY_DIED -> ExitReason.DependencyDied
91+
ApplicationExitInfo.REASON_OTHER -> ExitReason.Other
92+
ApplicationExitInfo.REASON_FREEZER -> ExitReason.Freezer
93+
else -> ExitReason.Unknown
94+
}

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/reports/exitinfo/ILatestAppExitInfoProvider.kt

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)