Skip to content

Commit 36a5339

Browse files
committed
feat: Support setting error type on individual stack frames
1 parent d7c7040 commit 36a5339

31 files changed

+1117
-464
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
### Enhancements
66

7-
* Support native stack traces in the ANR plugin
7+
* Support setting error type on individual stack frames
8+
[#1001](https://github.com/bugsnag/bugsnag-android/pull/1001)
9+
10+
* Support native stack traces in the ANR plugin
811
[#972](https://github.com/bugsnag/bugsnag-android/pull/972)
912

1013
## 5.2.3 (2020-11-04)

bugsnag-android-core/detekt-baseline.xml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<ID>LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array&lt;String&gt;? )</ID>
1212
<ID>LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, private val resources: Resources?, private val installId: String, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, private val logger: Logger )</ID>
1313
<ID>LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap&lt;String, Any&gt;, /** * The number of free bytes of storage available on the device */ var freeDisk: Long?, /** * The number of free bytes of memory available on the device */ var freeMemory: Long?, /** * The orientation of the device when the event occurred: either portrait or landscape */ var orientation: String?, /** * The timestamp on the device when the event occurred */ var time: Date? )</ID>
14+
<ID>LongParameterList:NativeStackframe.kt$NativeStackframe$( /** * The name of the method that was being executed */ var method: String?, /** * The location of the source file */ var file: String?, /** * The line number within the source file this stackframe refers to */ var lineNumber: Number?, /** * The address of the instruction where the event occurred. */ var frameAddress: Long?, /** * The address of the function where the event occurred. */ var symbolAddress: Long?, /** * The address of the library where the event occurred. */ var loadAddress: Long?, /** * The type of the error */ var type: ErrorType = ErrorType.C )</ID>
1415
<ID>LongParameterList:ThreadState.kt$ThreadState$( exc: Throwable?, isUnhandled: Boolean, sendThreads: ThreadSendPolicy, projectPackages: Collection&lt;String&gt;, logger: Logger, currentThread: java.lang.Thread = java.lang.Thread.currentThread(), stackTraces: MutableMap&lt;java.lang.Thread, Array&lt;StackTraceElement&gt;&gt; = java.lang.Thread.getAllStackTraces() )</ID>
1516
<ID>LongParameterList:ThreadState.kt$ThreadState$( stackTraces: MutableMap&lt;java.lang.Thread, Array&lt;StackTraceElement&gt;&gt;, currentThread: java.lang.Thread, exc: Throwable?, isUnhandled: Boolean, projectPackages: Collection&lt;String&gt;, logger: Logger )</ID>
1617
<ID>MagicNumber:DefaultDelivery.kt$DefaultDelivery$299</ID>

bugsnag-android-core/src/main/java/com/bugsnag/android/DeliveryDelegate.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.util.Date;
99
import java.util.HashMap;
10+
import java.util.HashSet;
1011
import java.util.List;
1112
import java.util.Map;
1213
import java.util.concurrent.RejectedExecutionException;
@@ -77,9 +78,7 @@ public void run() {
7778
@VisibleForTesting
7879
DeliveryStatus deliverPayloadInternal(@NonNull EventPayload payload, @NonNull Event event) {
7980
logger.d("DeliveryDelegate#deliverPayloadInternal() - attempting event delivery");
80-
81-
String apiKey = payload.getApiKey();
82-
DeliveryParams deliveryParams = immutableConfig.getErrorApiDeliveryParams(apiKey);
81+
DeliveryParams deliveryParams = immutableConfig.getErrorApiDeliveryParams(payload);
8382
Delivery delivery = immutableConfig.getDelivery();
8483
DeliveryStatus deliveryStatus = delivery.deliver(payload, deliveryParams);
8584

bugsnag-android-core/src/main/java/com/bugsnag/android/DeliveryHeaders.kt

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import java.util.Date
44

55
private const val HEADER_API_PAYLOAD_VERSION = "Bugsnag-Payload-Version"
66
private const val HEADER_BUGSNAG_SENT_AT = "Bugsnag-Sent-At"
7+
private const val HEADER_BUGSNAG_STACKTRACE_TYPES = "Bugsnag-Stacktrace-Types"
78
internal const val HEADER_API_KEY = "Bugsnag-Api-Key"
89
internal const val HEADER_INTERNAL_ERROR = "Bugsnag-Internal-Error"
910

@@ -12,11 +13,17 @@ internal const val HEADER_INTERNAL_ERROR = "Bugsnag-Internal-Error"
1213
*
1314
* @return the HTTP headers
1415
*/
15-
internal fun errorApiHeaders(apiKey: String) = mapOf(
16-
HEADER_API_PAYLOAD_VERSION to "4.0",
17-
HEADER_API_KEY to apiKey,
18-
HEADER_BUGSNAG_SENT_AT to DateUtils.toIso8601(Date())
19-
)
16+
internal fun errorApiHeaders(payload: EventPayload): Map<String, String> {
17+
val mutableHeaders = mutableMapOf(
18+
HEADER_API_PAYLOAD_VERSION to "4.0",
19+
HEADER_API_KEY to payload.apiKey!!,
20+
HEADER_BUGSNAG_SENT_AT to DateUtils.toIso8601(Date())
21+
)
22+
payload.getErrorTypes()?.let { header ->
23+
mutableHeaders[HEADER_BUGSNAG_STACKTRACE_TYPES] = header
24+
}
25+
return mutableHeaders.toMap()
26+
}
2027

2128
/**
2229
* Supplies the headers which must be used in any request sent to the Session Tracking API.

bugsnag-android-core/src/main/java/com/bugsnag/android/EventPayload.kt

+52
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,58 @@ class EventPayload : JsonStream.Streamable {
3030
this.notifier = notifier
3131
}
3232

33+
fun getErrorTypes(): String? {
34+
return when {
35+
event != null -> getErrorTypesFromStackframes(event)
36+
eventFile != null -> getErrorTypesFromFilename(eventFile)
37+
else -> null
38+
}
39+
}
40+
41+
private fun getErrorTypesFromStackframes(event: Event): String? {
42+
val errorTypes = mutableSetOf<ErrorType>()
43+
event.errors
44+
.map { it.stacktrace }
45+
.forEach {
46+
errorTypes.addAll(it.map(Stackframe::type).toSet())
47+
}
48+
return serializeErrorTypeHeader(errorTypes)
49+
}
50+
51+
private fun getErrorTypesFromFilename(eventFile: File): String? {
52+
val name = eventFile.name
53+
val end = name.lastIndexOf("_", name.lastIndexOf("_") - 1)
54+
val start = name.lastIndexOf("_", end - 1) + 1
55+
56+
if (start < end) {
57+
val errorTypes = name.substring(start, end)
58+
val validValues = ErrorType.values().map { it.desc }
59+
60+
// validate that this only contains valid error type info
61+
val valid = errorTypes.split(",").all { errorType ->
62+
validValues.contains(errorType)
63+
}
64+
if (valid) {
65+
return errorTypes
66+
}
67+
}
68+
return null
69+
}
70+
71+
/**
72+
* Serializes the error types to a comma delimited string
73+
*/
74+
private fun serializeErrorTypeHeader(errorTypes: Set<ErrorType>): String? {
75+
return when {
76+
errorTypes.isEmpty() -> null
77+
else -> errorTypes
78+
.map(ErrorType::desc)
79+
.reduce { accumulator, str ->
80+
"$accumulator,$str"
81+
}
82+
}
83+
}
84+
3385
@Throws(IOException::class)
3486
override fun toStream(writer: JsonStream) {
3587
writer.beginObject()

bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424
class EventStore extends FileStore {
2525

26-
private static final String STARTUP_CRASH = "_startupcrash";
26+
private static final String STARTUP_CRASH = "startupcrash";
2727
private static final long LAUNCH_CRASH_TIMEOUT_MS = 2000;
2828
private static final int LAUNCH_CRASH_POLL_MS = 50;
2929
private static final int MAX_EVENT_COUNT = 32;
@@ -154,7 +154,7 @@ private void flushEventFile(File eventFile) {
154154
}
155155

156156
EventPayload payload = new EventPayload(apiKey, eventFile, notifier);
157-
DeliveryParams deliveryParams = config.getErrorApiDeliveryParams(payload.getApiKey());
157+
DeliveryParams deliveryParams = config.getErrorApiDeliveryParams(payload);
158158
DeliveryStatus deliveryStatus = config.getDelivery().deliver(payload, deliveryParams);
159159

160160
switch (deliveryStatus) {
@@ -232,6 +232,7 @@ String getFilename(Object object) {
232232
String getFilename(Object object, String uuid, String apiKey,
233233
long timestamp, String storeDirectory) {
234234
String suffix = "";
235+
String errorTypes;
235236

236237
if (object instanceof Event) {
237238
Event event = (Event) object;
@@ -241,14 +242,17 @@ String getFilename(Object object, String uuid, String apiKey,
241242
suffix = STARTUP_CRASH;
242243
}
243244
apiKey = event.getApiKey();
245+
EventPayload payload = new EventPayload(apiKey, event, new Notifier());
246+
errorTypes = payload.getErrorTypes();
244247
} else { // generating a filename for an NDK event
245248
suffix = "not-jvm";
246249
if (apiKey.isEmpty()) {
247250
apiKey = config.getApiKey();
248251
}
252+
errorTypes = "c";
249253
}
250-
return String.format(Locale.US, "%s%d_%s_%s%s.json",
251-
storeDirectory, timestamp, apiKey, uuid, suffix);
254+
return String.format(Locale.US, "%s%d_%s_%s_%s_%s.json",
255+
storeDirectory, timestamp, apiKey, errorTypes, uuid, suffix);
252256
}
253257

254258
String getNdkFilename(Object object, String apiKey) {

bugsnag-android-core/src/main/java/com/bugsnag/android/ImmutableConfig.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package com.bugsnag.android
33
import android.content.Context
44
import android.content.pm.ApplicationInfo
55
import android.content.pm.PackageManager
6-
import java.util.Date
7-
import java.util.HashMap
86

97
internal data class ImmutableConfig(
108
val apiKey: String,
@@ -43,8 +41,8 @@ internal data class ImmutableConfig(
4341
enabledBreadcrumbTypes == null || enabledBreadcrumbTypes.contains(type)
4442

4543
@JvmName("getErrorApiDeliveryParams")
46-
internal fun getErrorApiDeliveryParams(apiKey: String) =
47-
DeliveryParams(endpoints.notify, errorApiHeaders(apiKey))
44+
internal fun getErrorApiDeliveryParams(payload: EventPayload) =
45+
DeliveryParams(endpoints.notify, errorApiHeaders(payload))
4846

4947
@JvmName("getSessionApiDeliveryParams")
5048
internal fun getSessionApiDeliveryParams() =

bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212

1313
import java.io.File;
1414
import java.io.IOException;
15+
import java.util.Collections;
1516
import java.util.Date;
1617
import java.util.Map;
18+
import java.util.Set;
1719
import java.util.concurrent.RejectedExecutionException;
1820

1921
class InternalReportDelegate implements EventStore.Delegate {
2022

2123
static final String INTERNAL_DIAGNOSTICS_TAB = "BugsnagDiagnostics";
2224

2325
final Logger logger;
24-
final ImmutableConfig immutableConfig;
26+
final ImmutableConfig config;
2527
final StorageManager storageManager;
2628

2729
final AppDataCollector appDataCollector;
@@ -39,7 +41,7 @@ class InternalReportDelegate implements EventStore.Delegate {
3941
SessionTracker sessionTracker,
4042
Notifier notifier) {
4143
this.logger = logger;
42-
this.immutableConfig = immutableConfig;
44+
this.config = immutableConfig;
4345
this.storageManager = storageManager;
4446
this.appDataCollector = appDataCollector;
4547
this.deviceDataCollector = deviceDataCollector;
@@ -52,7 +54,7 @@ class InternalReportDelegate implements EventStore.Delegate {
5254
public void onErrorIOFailure(Exception exc, File errorFile, String context) {
5355
// send an internal error to bugsnag with no cache
5456
HandledState handledState = HandledState.newInstance(REASON_UNHANDLED_EXCEPTION);
55-
Event err = new Event(exc, immutableConfig, handledState, logger);
57+
Event err = new Event(exc, config, handledState, logger);
5658
err.setContext(context);
5759

5860
err.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "canRead", errorFile.canRead());
@@ -95,7 +97,7 @@ void reportInternalBugsnagError(@NonNull Event event) {
9597

9698
event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "notifierName", notifier.getName());
9799
event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "notifierVersion", notifier.getVersion());
98-
event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "apiKey", immutableConfig.getApiKey());
100+
event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "apiKey", config.getApiKey());
99101

100102
final EventPayload eventPayload = new EventPayload(null, event, notifier);
101103
try {
@@ -104,10 +106,8 @@ void reportInternalBugsnagError(@NonNull Event event) {
104106
public void run() {
105107
try {
106108
logger.d("InternalReportDelegate - sending internal event");
107-
108-
Delivery delivery = immutableConfig.getDelivery();
109-
String apiKey = eventPayload.getApiKey();
110-
DeliveryParams params = immutableConfig.getErrorApiDeliveryParams(apiKey);
109+
Delivery delivery = config.getDelivery();
110+
DeliveryParams params = config.getErrorApiDeliveryParams(eventPayload);
111111

112112
// can only modify headers if DefaultDelivery is in use
113113
if (delivery instanceof DefaultDelivery) {

bugsnag-android-core/src/main/java/com/bugsnag/android/NativeInterface.java

+3
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ public boolean onError(@NonNull Event event) {
371371
errors.get(0).setErrorMessage(message);
372372

373373
for (Error error : errors) {
374+
for (Stackframe stackframe : error.getStacktrace()) {
375+
stackframe.setType(ErrorType.C);
376+
}
374377
error.setType(ErrorType.C);
375378
}
376379
}

bugsnag-android-core/src/main/java/com/bugsnag/android/NativeStackframe.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ class NativeStackframe internal constructor(
3535
/**
3636
* The address of the library where the event occurred.
3737
*/
38-
var loadAddress: Long?
38+
var loadAddress: Long?,
39+
40+
/**
41+
* The type of the error
42+
*/
43+
var type: ErrorType = ErrorType.C
3944
) : JsonStream.Streamable {
4045

4146
@Throws(IOException::class)
@@ -47,7 +52,7 @@ class NativeStackframe internal constructor(
4752
writer.name("frameAddress").value(frameAddress)
4853
writer.name("symbolAddress").value(symbolAddress)
4954
writer.name("loadAddress").value(loadAddress)
50-
writer.name("type").value("c")
55+
writer.name("type").value(type.desc)
5156
writer.endObject()
5257
}
5358
}

bugsnag-android-core/src/main/java/com/bugsnag/android/Stackframe.kt

+10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ class Stackframe : JsonStream.Streamable {
5050
*/
5151
var columnNumber: Number?
5252

53+
/**
54+
* The type of the error
55+
*/
56+
var type: ErrorType = ErrorType.ANDROID
57+
set(value) {
58+
nativeFrame?.type = value
59+
field = value
60+
}
61+
5362
@JvmOverloads
5463
internal constructor(method: String?, file: String?, lineNumber: Number?, inProject: Boolean?,
5564
code: Map<String, String?>? = null, columnNumber: Number? = null) {
@@ -82,6 +91,7 @@ class Stackframe : JsonStream.Streamable {
8291
writer.name("lineNumber").value(lineNumber)
8392
writer.name("inProject").value(inProject)
8493
writer.name("columnNumber").value(columnNumber)
94+
writer.name("type").value(type.desc)
8595

8696
code?.let { map: Map<String, String?> ->
8797
writer.name("code")

bugsnag-android-core/src/test/java/com/bugsnag/android/BugsnagTestUtils.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import org.jetbrains.annotations.NotNull;
44

5-
import java.util.Collections;
5+
import java.io.File;
66
import java.util.Date;
77
import java.util.HashMap;
88

@@ -26,6 +26,22 @@ static ImmutableConfig generateImmutableConfig() {
2626
return convert(generateConfiguration());
2727
}
2828

29+
static EventPayload generateEventPayload(ImmutableConfig config) {
30+
return new EventPayload(config.getApiKey(), generateEvent(), new Notifier());
31+
}
32+
33+
static Event generateEvent() {
34+
Throwable exc = new RuntimeException();
35+
Event event = new Event(
36+
exc,
37+
BugsnagTestUtils.generateImmutableConfig(),
38+
HandledState.newInstance(HandledState.REASON_HANDLED_EXCEPTION),
39+
NoopLogger.INSTANCE
40+
);
41+
event.setApp(generateAppWithState());
42+
event.setDevice(generateDeviceWithState());
43+
return event;
44+
}
2945

3046
static ImmutableConfig convert(Configuration config) {
3147
return ImmutableConfigKt.convertToImmutableConfig(config, null);

0 commit comments

Comments
 (0)