Skip to content

Commit ec82bc0

Browse files
committed
refactor: encode event file information in separate source file
1 parent 4e13c1d commit ec82bc0

File tree

8 files changed

+295
-169
lines changed

8 files changed

+295
-169
lines changed

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

+17-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,27 @@ internal fun errorApiHeaders(payload: EventPayload): Map<String, String> {
1919
HEADER_API_KEY to payload.apiKey!!,
2020
HEADER_BUGSNAG_SENT_AT to DateUtils.toIso8601(Date())
2121
)
22-
payload.getErrorTypes()?.let { header ->
23-
mutableHeaders[HEADER_BUGSNAG_STACKTRACE_TYPES] = header
22+
val errorTypes = payload.getErrorTypes()
23+
if (errorTypes.isNotEmpty()) {
24+
mutableHeaders[HEADER_BUGSNAG_STACKTRACE_TYPES] = serializeErrorTypeHeader(errorTypes)
2425
}
2526
return mutableHeaders.toMap()
2627
}
2728

29+
/**
30+
* Serializes the error types to a comma delimited string
31+
*/
32+
internal fun serializeErrorTypeHeader(errorTypes: Set<ErrorType>): String {
33+
return when {
34+
errorTypes.isEmpty() -> ""
35+
else -> errorTypes
36+
.map(ErrorType::desc)
37+
.reduce { accumulator, str ->
38+
"$accumulator,$str"
39+
}
40+
}
41+
}
42+
2843
/**
2944
* Supplies the headers which must be used in any request sent to the Session Tracking API.
3045
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.bugsnag.android
2+
3+
import java.io.File
4+
import java.util.Locale
5+
import java.util.UUID
6+
7+
/**
8+
* Represents important information about an event which is encoded/decoded from a filename.
9+
*/
10+
internal data class EventFilenameInfo(
11+
val apiKey: String,
12+
val uuid: String,
13+
val timestamp: Long,
14+
val suffix: String,
15+
val errorTypes: Set<ErrorType>
16+
) {
17+
18+
/**
19+
* Encodes event information into a filename
20+
*/
21+
fun encode(): String {
22+
return String.format(
23+
Locale.US,
24+
"%d_%s_%s_%s_%s.json",
25+
timestamp,
26+
apiKey,
27+
serializeErrorTypeHeader(errorTypes),
28+
uuid,
29+
suffix
30+
)
31+
}
32+
33+
fun isLaunchCrashReport(): Boolean = suffix == STARTUP_CRASH
34+
35+
internal companion object {
36+
private const val STARTUP_CRASH = "startupcrash"
37+
private const val NON_JVM_CRASH = "not-jvm"
38+
39+
/**
40+
* Generates a filename for the Event in the format
41+
* "[timestamp]_[apiKey]_[errorTypes]_[UUID]_[startupcrash|not-jvm].json"
42+
*/
43+
@JvmOverloads
44+
fun fromEvent(
45+
obj: Any,
46+
uuid: String = UUID.randomUUID().toString(),
47+
apiKey: String?,
48+
timestamp: Long = System.currentTimeMillis(),
49+
config: ImmutableConfig
50+
): EventFilenameInfo {
51+
val sanitizedApiKey = when {
52+
obj is Event -> obj.apiKey
53+
apiKey.isNullOrEmpty() -> config.apiKey
54+
else -> apiKey
55+
}
56+
57+
return EventFilenameInfo(
58+
sanitizedApiKey,
59+
uuid,
60+
timestamp,
61+
findSuffixForEvent(obj, config),
62+
findErrorTypesForEvent(obj, sanitizedApiKey)
63+
)
64+
}
65+
66+
/**
67+
* Reads event information from a filename.
68+
*/
69+
fun fromFile(file: File, config: ImmutableConfig): EventFilenameInfo {
70+
return EventFilenameInfo(
71+
findApiKeyInFilename(file, config),
72+
"", // ignore UUID field when reading from file as unused
73+
-1, // ignore timestamp when reading from file as unused
74+
findSuffixInFilename(file),
75+
findErrorTypesInFilename(file)
76+
)
77+
}
78+
79+
/**
80+
* Retrieves the api key encoded in the filename, or an empty string if this information
81+
* is not encoded for the given event
82+
*/
83+
private fun findApiKeyInFilename(file: File, config: ImmutableConfig): String {
84+
val name = file.name.replace("_startupcrash.json".toRegex(), "")
85+
val start = name.indexOf("_") + 1
86+
val end = name.indexOf("_", start)
87+
val apiKey = if (start == 0 || end == -1 || end <= start) {
88+
null
89+
} else {
90+
name.substring(start, end)
91+
}
92+
return apiKey ?: config.apiKey
93+
}
94+
95+
/**
96+
* Retrieves the error types encoded in the filename, or an empty string if this
97+
* information is not encoded for the given event
98+
*/
99+
internal fun findErrorTypesInFilename(eventFile: File): Set<ErrorType> {
100+
val name = eventFile.name
101+
val end = name.lastIndexOf("_", name.lastIndexOf("_") - 1)
102+
val start = name.lastIndexOf("_", end - 1) + 1
103+
104+
if (start < end) {
105+
val encodedValues: List<String> = name.substring(start, end).split(",")
106+
return ErrorType.values().filter {
107+
encodedValues.contains(it.desc)
108+
}.toSet()
109+
}
110+
return emptySet()
111+
}
112+
113+
/**
114+
* Retrieves the error types encoded in the filename, or an empty string if this
115+
* information is not encoded for the given event
116+
*/
117+
private fun findSuffixInFilename(eventFile: File): String {
118+
val name = eventFile.nameWithoutExtension
119+
val suffix = name.substring(name.lastIndexOf("_") + 1)
120+
return when (suffix) {
121+
STARTUP_CRASH, NON_JVM_CRASH -> suffix
122+
else -> ""
123+
}
124+
}
125+
126+
/**
127+
* Retrieves the error types for the given event
128+
*/
129+
private fun findErrorTypesForEvent(obj: Any, sanitizedApiKey: String): Set<ErrorType> {
130+
return when (obj) {
131+
is Event -> {
132+
val payload = EventPayload(sanitizedApiKey, obj, Notifier())
133+
payload.getErrorTypes()
134+
}
135+
else -> {
136+
setOf(ErrorType.C)
137+
}
138+
}
139+
}
140+
141+
/**
142+
* Calculates the suffix for the given event
143+
*/
144+
private fun findSuffixForEvent(obj: Any, config: ImmutableConfig): String {
145+
return when (obj) {
146+
is Event -> {
147+
val duration = obj.app.duration
148+
if (duration != null && isStartupCrash(duration.toLong(), config)) {
149+
STARTUP_CRASH
150+
} else {
151+
""
152+
}
153+
}
154+
else -> {
155+
NON_JVM_CRASH
156+
}
157+
}
158+
}
159+
160+
private fun isStartupCrash(durationMs: Long, config: ImmutableConfig): Boolean {
161+
return durationMs < config.launchCrashThresholdMs
162+
}
163+
}
164+
}
165+

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

+6-42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.bugsnag.android
22

3+
import com.bugsnag.android.EventFilenameInfo.Companion.findErrorTypesInFilename
34
import java.io.File
45
import java.io.IOException
56

@@ -30,56 +31,20 @@ class EventPayload : JsonStream.Streamable {
3031
this.notifier = notifier
3132
}
3233

33-
fun getErrorTypes(): String? {
34+
internal fun getErrorTypes(): Set<ErrorType> {
3435
return when {
3536
event != null -> getErrorTypesFromStackframes(event)
36-
eventFile != null -> getErrorTypesFromFilename(eventFile)
37-
else -> null
37+
eventFile != null -> findErrorTypesInFilename(eventFile)
38+
else -> emptySet()
3839
}
3940
}
4041

41-
private fun getErrorTypesFromStackframes(event: Event): String? {
42+
private fun getErrorTypesFromStackframes(event: Event): Set<ErrorType> {
4243
val errorTypes = event.errors.mapNotNull(Error::getType).toSet()
4344
val frameOverrideTypes = event.errors
4445
.map { it.stacktrace }
4546
.flatMap { it.mapNotNull(Stackframe::type) }
46-
47-
val distinctTypes = errorTypes.plus(frameOverrideTypes)
48-
return serializeErrorTypeHeader(distinctTypes)
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-
}
47+
return errorTypes.plus(frameOverrideTypes)
8348
}
8449

8550
@Throws(IOException::class)
@@ -88,7 +53,6 @@ class EventPayload : JsonStream.Streamable {
8853
writer.name("apiKey").value(apiKey)
8954
writer.name("payloadVersion").value("4.0")
9055
writer.name("notifier").value(notifier)
91-
9256
writer.name("events").beginArray()
9357

9458
when {

0 commit comments

Comments
 (0)