Skip to content

Commit e520d4c

Browse files
committed
Expose crash hook callback
1 parent a280fec commit e520d4c

File tree

24 files changed

+594
-153
lines changed

24 files changed

+594
-153
lines changed

Cargo.Bazel.lock

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

Cargo.lock

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

Cargo.toml

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,35 @@ android_logger = { version = "0.15.1", default-features = false }
1919
anyhow = "1.0.102"
2020
assert_matches = "1.5.0"
2121
async-trait = "0.1.89"
22-
bd-api = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
23-
bd-bonjson = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
24-
bd-bonjson-ffi = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
25-
bd-buffer = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
26-
bd-client-common = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
27-
bd-client-stats-store = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
28-
bd-crash-handler = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
29-
bd-device = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
30-
bd-error-reporter = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
31-
bd-grpc = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
32-
bd-hyper-network = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67", default-features = false, features = [
22+
bd-api = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
23+
bd-bonjson = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
24+
bd-bonjson-ffi = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
25+
bd-buffer = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
26+
bd-client-common = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
27+
bd-client-stats-store = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
28+
bd-crash-handler = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
29+
bd-device = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
30+
bd-error-reporter = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
31+
bd-grpc = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
32+
bd-hyper-network = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6", default-features = false, features = [
3333
"ring",
3434
] }
35-
bd-key-value = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
36-
bd-log = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
37-
bd-log-metadata = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
38-
bd-log-primitives = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
39-
bd-logger = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
40-
bd-noop-network = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
41-
bd-proto = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
42-
bd-report-parsers = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
43-
bd-report-writer = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
44-
bd-runtime = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
45-
bd-session = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
46-
bd-shutdown = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
47-
bd-test-helpers = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67", default-features = false, features = [
35+
bd-key-value = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
36+
bd-log = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
37+
bd-log-metadata = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
38+
bd-log-primitives = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
39+
bd-logger = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
40+
bd-noop-network = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
41+
bd-proto = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
42+
bd-report-parsers = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
43+
bd-report-writer = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
44+
bd-runtime = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
45+
bd-session = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
46+
bd-shutdown = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
47+
bd-test-helpers = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6", default-features = false, features = [
4848
"ring",
4949
] }
50-
bd-time = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "eb85d1790ff9374dbe7ea778f70337ef5ab15f67" }
50+
bd-time = { git = "https://github.com/bitdriftlabs/shared-core.git", rev = "5f38dc4d9e3c993ee5315209c2519a9a8030a0e6" }
5151
clap = { version = "4.5.60", features = ["derive", "env"] }
5252
ctor = "0.6.3"
5353
env_logger = { version = "0.11.9", default-features = false }

platform/jvm/capture/consumer-rules.pro

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@
6464
public <methods>;
6565
}
6666

67+
# Accessed reflectively from Rust/JNI (class/method names must remain stable).
68+
-keep class io.bitdrift.capture.reports.IssueCallbackConfiguration {
69+
public void dispatch(io.bitdrift.capture.reports.Report);
70+
}
71+
72+
-keep class io.bitdrift.capture.reports.Report {
73+
public <init>(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Map);
74+
}
75+
6776
-keep, includedescriptorclasses class io.bitdrift.capture.IEventsListenerTarget {
6877
public <methods>;
6978
}
@@ -126,4 +135,4 @@
126135
-keep class io.bitdrift.capture.webview.WebViewCapture {
127136
public static void instrument(android.webkit.WebView);
128137
public static void instrument(android.webkit.WebView, io.bitdrift.capture.ILogger);
129-
}
138+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.bitdrift.capture.error.IErrorReporter
1212
import io.bitdrift.capture.network.ICaptureNetwork
1313
import io.bitdrift.capture.providers.Field
1414
import io.bitdrift.capture.providers.session.SessionStrategyConfiguration
15+
import io.bitdrift.capture.reports.IssueCallbackConfiguration
1516
import io.bitdrift.capture.reports.processor.IStreamingReportProcessor
1617
import io.bitdrift.capture.reports.processor.ReportProcessingSession
1718
import okio.IOException
@@ -71,6 +72,7 @@ internal object CaptureJniLibrary : IBridge, IStreamingReportProcessor {
7172
preferences: IPreferences,
7273
errorReporter: IErrorReporter,
7374
startInSleepMode: Boolean,
75+
issueCallbackConfiguration: IssueCallbackConfiguration?,
7476
): Long
7577

7678
/**

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ package io.bitdrift.capture
99

1010
import io.bitdrift.capture.experimental.ExperimentalBitdriftApi
1111
import io.bitdrift.capture.replay.SessionReplayConfiguration
12+
import io.bitdrift.capture.reports.IssueCallbackConfiguration
1213
import io.bitdrift.capture.webview.WebViewConfiguration
1314

1415
/**
1516
* A configuration object representing the feature set enabled for Capture.
1617
* @param sessionReplayConfiguration The resource reporting configuration to use. Passing `null` disables the feature.
17-
* @param enableFatalIssueReporting When set to true wil capture Fatal Issues automatically [JVM crash, ANR, etc] and without requiring
18-
* any external 3rd party library integration
18+
* @param enableFatalIssueReporting When set to true captures fatal issues automatically (JVM crash, ANR, etc.)
19+
* without requiring third-party integrations.
1920
* @param sleepMode SleepMode.ENABLED if Capture should initialize in minimal activity mode
2021
* @param webViewConfiguration The WebView instrumentation configuration. Requires the `io.bitdrift.capture-plugin`
2122
* Gradle plugin with `automaticWebViewInstrumentation = true` enabled.
2223
* Passing `null` disables WebView monitoring.
23-
*/
24+
* @param issueCallbackConfiguration Optional callback configuration used for issue report callbacks.
25+
* This is only effective when [enableFatalIssueReporting] is true.
26+
*/
2427
data class Configuration
2528
@JvmOverloads
2629
constructor(
@@ -29,4 +32,5 @@ data class Configuration
2932
val sleepMode: SleepMode = SleepMode.DISABLED,
3033
@property:ExperimentalBitdriftApi
3134
val webViewConfiguration: WebViewConfiguration? = null,
35+
val issueCallbackConfiguration: IssueCallbackConfiguration? = null,
3236
)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package io.bitdrift.capture
1010
import io.bitdrift.capture.error.IErrorReporter
1111
import io.bitdrift.capture.network.ICaptureNetwork
1212
import io.bitdrift.capture.providers.session.SessionStrategyConfiguration
13+
import io.bitdrift.capture.reports.IssueCallbackConfiguration
1314

1415
internal interface IBridge {
1516
fun createLogger(
@@ -27,5 +28,6 @@ internal interface IBridge {
2728
preferences: IPreferences,
2829
errorReporter: IErrorReporter,
2930
startInSleepMode: Boolean,
31+
issueCallbackConfiguration: IssueCallbackConfiguration?,
3032
): Long
3133
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ internal class LoggerImpl(
210210
preferences,
211211
localErrorReporter,
212212
configuration.sleepMode == SleepMode.ENABLED,
213+
if (configuration.enableFatalIssueReporting) configuration.issueCallbackConfiguration else null,
213214
)
214215

215216
check(loggerId != -1L) { "initialization of the rust logger failed" }
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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
9+
10+
import android.util.Log
11+
import io.bitdrift.capture.Capture
12+
import io.bitdrift.capture.Capture.LOG_TAG
13+
import io.bitdrift.capture.IInternalLogger
14+
import java.util.concurrent.Executor
15+
import kotlin.jvm.JvmName
16+
17+
/**
18+
* Configuration for issue report callbacks.
19+
*
20+
* Use this to provide:
21+
* - the executor where callbacks run
22+
* - the callback that receives issue report metadata before send
23+
*
24+
* @param executor Executor used to run callback invocations.
25+
* @param issueReportCallback Callback invoked before an issue report is sent.
26+
*/
27+
class IssueCallbackConfiguration(
28+
private val executor: Executor,
29+
private val issueReportCallback: IssueReportCallback,
30+
) {
31+
/**
32+
* Dispatches the callback on the configured executor from the JNI layer
33+
*/
34+
@Suppress("unused")
35+
@JvmName("dispatch")
36+
internal fun dispatch(report: Report) {
37+
runSafely("Issue report callback dispatch failed") {
38+
executor.execute { invokeCallback(report) }
39+
}
40+
}
41+
42+
private fun invokeCallback(report: Report) {
43+
runSafely("Issue report callback failed") {
44+
issueReportCallback.onBeforeReportSend(report)
45+
}
46+
}
47+
48+
private inline fun runSafely(
49+
message: String,
50+
action: () -> Unit,
51+
) {
52+
runCatching(action).onFailure { handleFailure(message, it) }
53+
}
54+
55+
private fun handleFailure(
56+
message: String,
57+
throwable: Throwable,
58+
) {
59+
Log.e(LOG_TAG, message, throwable)
60+
getInternalLogger()?.logInternalError(throwable) { message }
61+
}
62+
63+
private fun getInternalLogger(): IInternalLogger? = Capture.logger() as? IInternalLogger
64+
}
65+
66+
/**
67+
* Issue report metadata delivered to [IssueReportCallback] before send.
68+
*
69+
* @param reportType High-level issue type (for example "ANR", "Native Crash", "Crash").
70+
* @param reason Primary issue identifier (for example exception class or signal name).
71+
* @param details Additional issue details (for example exception message).
72+
* @param sessionId bitdrift session ID associated with the report.
73+
* @param fields Additional report fields available at callback time.
74+
*/
75+
data class Report(
76+
val reportType: String,
77+
val reason: String,
78+
val details: String,
79+
val sessionId: String,
80+
val fields: Map<String, String>,
81+
)
82+
83+
/**
84+
* Callback invoked before an issue report (crash, ANR, etc.) is sent.
85+
*/
86+
fun interface IssueReportCallback {
87+
/**
88+
* Called before an issue report is sent.
89+
*/
90+
fun onBeforeReportSend(report: Report)
91+
}

platform/jvm/capture/src/test/kotlin/io/bitdrift/capture/CaptureLoggerNetworkTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class CaptureLoggerNetworkTest {
9292
mock(),
9393
mock(),
9494
false,
95+
null,
9596
)
9697
CaptureJniLibrary.startLogger(logger)
9798
return logger
@@ -175,6 +176,7 @@ class CaptureLoggerNetworkTest {
175176
mock(),
176177
mock(),
177178
false,
179+
null,
178180
)
179181
CaptureJniLibrary.startLogger(loggerId)
180182
logger = loggerId
@@ -211,6 +213,7 @@ class CaptureLoggerNetworkTest {
211213
MockPreferences(),
212214
mock(),
213215
false,
216+
null,
214217
)
215218
CaptureJniLibrary.startLogger(logger)
216219

0 commit comments

Comments
 (0)