Skip to content

Commit 0156ea8

Browse files
committed
feat: encode error types in filename
1 parent df4802a commit 0156ea8

14 files changed

+253
-41
lines changed

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/Stackframe.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class Stackframe : JsonStream.Streamable {
5353
/**
5454
* The type of the error
5555
*/
56-
internal var type: ErrorType = ErrorType.ANDROID
56+
var type: ErrorType = ErrorType.ANDROID
5757

5858
@JvmOverloads
5959
internal constructor(method: String?, file: String?, lineNumber: Number?, inProject: Boolean?,

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);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.bugsnag.android
2+
3+
import com.bugsnag.android.BugsnagTestUtils.generateConfiguration
4+
import com.bugsnag.android.BugsnagTestUtils.generateEventPayload
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertNotNull
7+
import org.junit.Assert.assertNull
8+
import org.junit.Test
9+
import java.io.File
10+
import java.util.Collections
11+
12+
class DeliveryHeadersTest {
13+
14+
@Test
15+
fun verifyErrorApiHeaders() {
16+
val config = convertToImmutableConfig(generateConfiguration())
17+
val payload = generateEventPayload(config)
18+
val headers = config.getErrorApiDeliveryParams(payload).headers
19+
assertEquals(config.apiKey, headers["Bugsnag-Api-Key"])
20+
assertNotNull(headers["Bugsnag-Sent-At"])
21+
assertNotNull(headers["Bugsnag-Payload-Version"])
22+
assertNotNull(headers["Bugsnag-Stacktrace-Types"])
23+
}
24+
25+
@Test
26+
fun verifyErrorApiHeadersDefaultStacktrace() {
27+
val config = convertToImmutableConfig(generateConfiguration())
28+
val payload = generateEventPayload(config)
29+
val headers = config.getErrorApiDeliveryParams(payload).headers
30+
assertEquals(config.apiKey, headers["Bugsnag-Api-Key"])
31+
assertNotNull(headers["Bugsnag-Sent-At"])
32+
assertEquals("4.0", headers["Bugsnag-Payload-Version"])
33+
assertEquals("android", headers["Bugsnag-Stacktrace-Types"])
34+
}
35+
36+
@Test
37+
fun verifyErrorApiHeadersNoStacktrace() {
38+
val config = convertToImmutableConfig(generateConfiguration())
39+
val file = File("1504255147933_0000111122223333aaaabbbbcccc9999_my-uuid-123.json")
40+
val payload = EventPayload(config.apiKey, file, Notifier())
41+
val headers = config.getErrorApiDeliveryParams(payload).headers
42+
assertNull(headers["Bugsnag-Stacktrace-Types"])
43+
}
44+
45+
@Test
46+
fun verifyErrorApiHeadersMultiStacktrace() {
47+
val config = convertToImmutableConfig(generateConfiguration())
48+
val payload = generateEventPayload(config)
49+
50+
// alter stacktrace to contain two types
51+
val error = requireNotNull(payload.event!!.errors[0])
52+
error.stacktrace[0].type = ErrorType.C
53+
error.stacktrace[1].type = ErrorType.REACTNATIVEJS
54+
55+
val headers = config.getErrorApiDeliveryParams(payload).headers
56+
assertEquals("c,reactnativejs,android", headers["Bugsnag-Stacktrace-Types"])
57+
}
58+
}

0 commit comments

Comments
 (0)