Skip to content

Commit 04b519c

Browse files
authored
Merge pull request #1765 from bugsnag/release/v5.28.0
v5.28.0
2 parents a84cfda + 3133bcd commit 04b519c

39 files changed

Lines changed: 719 additions & 90 deletions

File tree

.buildkite/pipeline.full.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ steps:
388388
concurrency_group: 'browserstack-app'
389389
concurrency_method: eager
390390

391-
- label: ':android: Android 13 Beta NDK r21 end-to-end tests - batch 1'
391+
- label: ':android: Android 13 NDK r21 end-to-end tests - batch 1'
392392
depends_on: "fixture-r21"
393393
timeout_in_minutes: 60
394394
plugins:
@@ -405,15 +405,15 @@ steps:
405405
- "--exclude=features/full_tests/[^a-k].*.feature"
406406
- "--app=/app/build/fixture-r21.apk"
407407
- "--farm=bs"
408-
- "--device=ANDROID_13_0_BETA"
408+
- "--device=ANDROID_13_0"
409409
- "--fail-fast"
410410
env:
411411
TEST_FIXTURE_SYMBOL_DIR: "build/fixture-r21"
412412
concurrency: 24
413413
concurrency_group: 'browserstack-app'
414414
concurrency_method: eager
415415

416-
- label: ':android: Android 13 Beta NDK r21 end-to-end tests - batch 2'
416+
- label: ':android: Android 13 NDK r21 end-to-end tests - batch 2'
417417
depends_on: "fixture-r21"
418418
timeout_in_minutes: 60
419419
plugins:
@@ -430,7 +430,7 @@ steps:
430430
- "--exclude=features/full_tests/[^l-z].*.feature"
431431
- "--app=/app/build/fixture-r21.apk"
432432
- "--farm=bs"
433-
- "--device=ANDROID_13_0_BETA"
433+
- "--device=ANDROID_13_0"
434434
- "--fail-fast"
435435
env:
436436
TEST_FIXTURE_SYMBOL_DIR: "build/fixture-r21"

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 5.28.0 (2022-10-13)
4+
5+
### Enhancements
6+
7+
* Bugsnag now supports up to 500 breadcrumbs, with a default max of 100. Note that breadcrumbs will be trimmed
8+
(oldest first) if the payload exceeds 1MB.
9+
[#1751](https://github.com/bugsnag/bugsnag-android/pull/1751)
10+
11+
### Bug fixes
12+
13+
* Fixed very rare crashes when attempting to unwind NDK stacks over protected memory pages
14+
[#1761](https://github.com/bugsnag/bugsnag-android/pull/1761)
15+
316
## 5.27.0 (2022-10-06)
417

518
### Enhancements

bugsnag-android-core/detekt-baseline.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<ID>LongParameterList:DeviceIdStore.kt$DeviceIdStore$( context: Context, deviceIdfile: File = File(context.filesDir, "device-id"), deviceIdGenerator: () -> UUID = { UUID.randomUUID() }, internalDeviceIdfile: File = File(context.filesDir, "internal-device-id"), internalDeviceIdGenerator: () -> UUID = { UUID.randomUUID() }, private val sharedPrefMigrator: SharedPrefMigrator, logger: Logger )</ID>
1616
<ID>LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap&lt;String, Any>, /** * 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>
1717
<ID>LongParameterList:EventFilenameInfo.kt$EventFilenameInfo.Companion$( obj: Any, uuid: String = UUID.randomUUID().toString(), apiKey: String?, timestamp: Long = System.currentTimeMillis(), config: ImmutableConfig, isLaunching: Boolean? = null )</ID>
18-
<ID>LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, breadcrumbs: MutableList&lt;Breadcrumb> = mutableListOf(), discardClasses: Set&lt;String> = setOf(), errors: MutableList&lt;Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection&lt;String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList&lt;Thread> = mutableListOf(), user: User = User(), redactionKeys: Set&lt;String>? = null )</ID>
18+
<ID>LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList&lt;Breadcrumb> = mutableListOf(), discardClasses: Set&lt;String> = setOf(), errors: MutableList&lt;Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection&lt;String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList&lt;Thread&gt; = mutableListOf(), user: User = User(), redactionKeys: Set&lt;String>? = null )</ID>
1919
<ID>LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState )</ID>
2020
<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?, /** * Whether this frame identifies the program counter */ var isPC: Boolean?, /** * The type of the error */ var type: ErrorType? = null, /** * Identifies the exact build this frame originates from. */ var codeIdentifier: String? = null, )</ID>
2121
<ID>LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, @JvmField val sendThreads: ThreadSendPolicy )</ID>
@@ -40,15 +40,14 @@
4040
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityInternal(severity: Severity)</ID>
4141
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityReason(@SeverityReason.SeverityReasonType reason: String)</ID>
4242
<ID>RethrowCaughtException:JsonHelper.kt$JsonHelper$throw ex</ID>
43-
<ID>ReturnCount:DefaultDelivery.kt$DefaultDelivery$fun deliver( urlString: String, streamable: JsonStream.Streamable, headers: Map&lt;String, String?> ): DeliveryStatus</ID>
43+
<ID>ReturnCount:DefaultDelivery.kt$DefaultDelivery$fun deliver( urlString: String, json: ByteArray, headers: Map&lt;String, String?> ): DeliveryStatus</ID>
4444
<ID>SwallowedException:BugsnagEventMapper.kt$BugsnagEventMapper$catch (pe: IllegalArgumentException) { ndkDateFormatHolder.get()!!.parse(this) ?: throw IllegalArgumentException("cannot parse date $this") }</ID>
4545
<ID>SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$catch (e: NullPointerException) { // in some rare cases we get a remote NullPointerException via Parcel.readException null }</ID>
4646
<ID>SwallowedException:ContextExtensions.kt$catch (exc: RuntimeException) { null }</ID>
4747
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false }</ID>
4848
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get battery status") }</ID>
4949
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") }</ID>
5050
<ID>SwallowedException:DeviceIdFilePersistence.kt$DeviceIdFilePersistence$catch (exc: OverlappingFileLockException) { Thread.sleep(FILE_LOCK_WAIT_MS) }</ID>
51-
<ID>SwallowedException:InternalMetrics.kt$InternalMetrics$catch (exc: Exception) { null }</ID>
5251
<ID>SwallowedException:JsonHelperTest.kt$JsonHelperTest$catch (e: IllegalArgumentException) { didThrow = true }</ID>
5352
<ID>SwallowedException:PluginClient.kt$PluginClient$catch (exc: ClassNotFoundException) { logger.d("Plugin '$clz' is not on the classpath - functionality will not be enabled.") null }</ID>
5453
<ID>ThrowsCount:JsonHelper.kt$JsonHelper$ fun jsonToLong(value: Any?): Long?</ID>

bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ManifestConfigLoaderTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class ManifestConfigLoaderTest {
4545
assertEquals(setOf("password"), redactedKeys)
4646

4747
// misc
48-
assertEquals(maxBreadcrumbs, 50)
48+
assertEquals(maxBreadcrumbs, 100)
4949
assertEquals(maxPersistedEvents, 32)
5050
assertEquals(maxPersistedSessions, 128)
5151
assertEquals(maxReportedThreads, 200)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.bugsnag.android
22

3+
import com.bugsnag.android.internal.StringUtils
4+
import com.bugsnag.android.internal.TrimMetrics
35
import java.io.IOException
46
import java.util.Date
57

@@ -22,6 +24,11 @@ internal class BreadcrumbInternal internal constructor(
2224
Date()
2325
)
2426

27+
internal fun trimMetadataStringsTo(maxStringLength: Int): TrimMetrics {
28+
val metadata = this.metadata ?: return TrimMetrics(0, 0)
29+
return StringUtils.trimNullableStringValuesTo(maxStringLength, metadata)
30+
}
31+
2532
@Throws(IOException::class)
2633
override fun toStream(writer: JsonStream) {
2734
writer.beginObject()

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.bugsnag.android
22

33
import com.bugsnag.android.internal.DateUtils
4+
import com.bugsnag.android.internal.InternalMetricsImpl
45
import java.text.DateFormat
56
import java.text.SimpleDateFormat
67
import java.util.Date
@@ -17,7 +18,7 @@ internal class BugsnagEventMapper(
1718

1819
@Suppress("UNCHECKED_CAST")
1920
internal fun convertToEventImpl(map: Map<in String, Any?>, apiKey: String): EventInternal {
20-
val event = EventInternal(apiKey)
21+
val event = EventInternal(apiKey, logger)
2122

2223
// populate exceptions. check this early to avoid unnecessary serialization if
2324
// no stacktrace was gathered.
@@ -86,6 +87,9 @@ internal class BugsnagEventMapper(
8687
event.updateSeverityReasonInternal(reason)
8788
event.normalizeStackframeErrorTypes()
8889

90+
// populate internalMetrics
91+
event.internalMetrics = InternalMetricsImpl(map["usage"] as MutableMap<String, Any>?)
92+
8993
return event
9094
}
9195

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ internal class ConfigInternal(
4242
var maxPersistedEvents: Int = DEFAULT_MAX_PERSISTED_EVENTS
4343
var maxPersistedSessions: Int = DEFAULT_MAX_PERSISTED_SESSIONS
4444
var maxReportedThreads: Int = DEFAULT_MAX_REPORTED_THREADS
45+
var maxStringValueLength: Int = DEFAULT_MAX_STRING_VALUE_LENGTH
4546
var context: String? = null
4647

4748
var redactedKeys: Set<String>
@@ -147,11 +148,12 @@ internal class ConfigInternal(
147148
}
148149

149150
companion object {
150-
private const val DEFAULT_MAX_BREADCRUMBS = 50
151+
private const val DEFAULT_MAX_BREADCRUMBS = 100
151152
private const val DEFAULT_MAX_PERSISTED_SESSIONS = 128
152153
private const val DEFAULT_MAX_PERSISTED_EVENTS = 32
153154
private const val DEFAULT_MAX_REPORTED_THREADS = 200
154155
private const val DEFAULT_LAUNCH_CRASH_THRESHOLD_MS: Long = 5000
156+
private const val DEFAULT_MAX_STRING_VALUE_LENGTH = 10000
155157

156158
@JvmStatic
157159
fun load(context: Context): Configuration = load(context, null)

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
public class Configuration implements CallbackAware, MetadataAware, UserAware, FeatureFlagAware {
1919

2020
private static final int MIN_BREADCRUMBS = 0;
21-
private static final int MAX_BREADCRUMBS = 100;
21+
private static final int MAX_BREADCRUMBS = 500;
2222
private static final int VALID_API_KEY_LEN = 32;
2323
private static final long MIN_LAUNCH_CRASH_THRESHOLD_MS = 0;
2424

@@ -512,7 +512,7 @@ public void setEndpoints(@NonNull EndpointConfiguration endpoints) {
512512
* Sets the maximum number of breadcrumbs which will be stored. Once the threshold is reached,
513513
* the oldest breadcrumbs will be deleted.
514514
*
515-
* By default, 50 breadcrumbs are stored: this can be amended up to a maximum of 100.
515+
* By default, 100 breadcrumbs are stored: this can be amended up to a maximum of 500.
516516
*/
517517
public int getMaxBreadcrumbs() {
518518
return impl.getMaxBreadcrumbs();
@@ -522,7 +522,7 @@ public int getMaxBreadcrumbs() {
522522
* Sets the maximum number of breadcrumbs which will be stored. Once the threshold is reached,
523523
* the oldest breadcrumbs will be deleted.
524524
*
525-
* By default, 50 breadcrumbs are stored: this can be amended up to a maximum of 100.
525+
* By default, 100 breadcrumbs are stored: this can be amended up to a maximum of 500.
526526
*/
527527
public void setMaxBreadcrumbs(int maxBreadcrumbs) {
528528
if (maxBreadcrumbs >= MIN_BREADCRUMBS && maxBreadcrumbs <= MAX_BREADCRUMBS) {
@@ -612,6 +612,32 @@ public void setMaxPersistedSessions(int maxPersistedSessions) {
612612
}
613613
}
614614

615+
/**
616+
* Gets the maximum string length in any metadata field. Once the threshold is
617+
* reached in a particular string, all excess characters will be deleted.
618+
*
619+
* By default, the limit is 10,000.
620+
*/
621+
public int getMaxStringValueLength() {
622+
return impl.getMaxStringValueLength();
623+
}
624+
625+
/**
626+
* Sets the maximum string length in any metadata field. Once the threshold is
627+
* reached in a particular string, all excess characters will be deleted.
628+
*
629+
* By default, the limit is 10,000.
630+
*/
631+
public void setMaxStringValueLength(int maxStringValueLength) {
632+
if (maxStringValueLength >= 0) {
633+
impl.setMaxStringValueLength(maxStringValueLength);
634+
} else {
635+
getLogger().e("Invalid configuration value detected. "
636+
+ "Option maxStringValueLength should be a positive integer."
637+
+ "Supplied value is " + maxStringValueLength);
638+
}
639+
}
640+
615641
/**
616642
* Bugsnag uses the concept of "contexts" to help display and group your errors. Contexts
617643
* represent what was happening in your application at the time an error occurs.

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

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,79 @@
11
package com.bugsnag.android
22

33
import android.net.TrafficStats
4-
import java.io.ByteArrayOutputStream
4+
import com.bugsnag.android.internal.JsonHelper
55
import java.io.IOException
6-
import java.io.PrintWriter
76
import java.net.HttpURLConnection
87
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
98
import java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT
109
import java.net.HttpURLConnection.HTTP_OK
1110
import java.net.URL
1211

13-
/**
14-
* Converts a [JsonStream.Streamable] into JSON, placing it in a [ByteArray]
15-
*/
16-
internal fun serializeJsonPayload(streamable: JsonStream.Streamable): ByteArray {
17-
return ByteArrayOutputStream().use { baos ->
18-
JsonStream(PrintWriter(baos).buffered()).use(streamable::toStream)
19-
baos.toByteArray()
20-
}
21-
}
22-
2312
internal class DefaultDelivery(
2413
private val connectivity: Connectivity?,
25-
val logger: Logger
14+
private val apiKey: String,
15+
private val maxStringValueLength: Int,
16+
private val logger: Logger
2617
) : Delivery {
2718

19+
companion object {
20+
// 1MB with some fiddle room in case of encoding overhead
21+
const val maxPayloadSize = 999700
22+
}
23+
2824
override fun deliver(payload: Session, deliveryParams: DeliveryParams): DeliveryStatus {
29-
val status = deliver(deliveryParams.endpoint, payload, deliveryParams.headers)
25+
val status = deliver(
26+
deliveryParams.endpoint,
27+
JsonHelper.serialize(payload),
28+
deliveryParams.headers
29+
)
3030
logger.i("Session API request finished with status $status")
3131
return status
3232
}
3333

34+
private fun serializePayload(payload: EventPayload): ByteArray {
35+
var json = JsonHelper.serialize(payload)
36+
if (json.size <= maxPayloadSize) {
37+
return json
38+
}
39+
40+
var event = payload.event
41+
if (event == null) {
42+
event = MarshalledEventSource(payload.eventFile!!, apiKey, logger).invoke()
43+
payload.event = event
44+
payload.apiKey = apiKey
45+
}
46+
47+
val (itemsTrimmed, dataTrimmed) = event.impl.trimMetadataStringsTo(maxStringValueLength)
48+
event.impl.internalMetrics.setMetadataTrimMetrics(
49+
itemsTrimmed,
50+
dataTrimmed
51+
)
52+
53+
json = JsonHelper.serialize(payload)
54+
if (json.size <= maxPayloadSize) {
55+
return json
56+
}
57+
58+
val breadcrumbAndBytesRemovedCounts =
59+
event.impl.trimBreadcrumbsBy(json.size - maxPayloadSize)
60+
event.impl.internalMetrics.setBreadcrumbTrimMetrics(
61+
breadcrumbAndBytesRemovedCounts.itemsTrimmed,
62+
breadcrumbAndBytesRemovedCounts.dataTrimmed
63+
)
64+
return JsonHelper.serialize(payload)
65+
}
66+
3467
override fun deliver(payload: EventPayload, deliveryParams: DeliveryParams): DeliveryStatus {
35-
val status = deliver(deliveryParams.endpoint, payload, deliveryParams.headers)
68+
val json = serializePayload(payload)
69+
val status = deliver(deliveryParams.endpoint, json, deliveryParams.headers)
3670
logger.i("Error API request finished with status $status")
3771
return status
3872
}
3973

4074
fun deliver(
4175
urlString: String,
42-
streamable: JsonStream.Streamable,
76+
json: ByteArray,
4377
headers: Map<String, String?>
4478
): DeliveryStatus {
4579

@@ -50,7 +84,6 @@ internal class DefaultDelivery(
5084
var conn: HttpURLConnection? = null
5185

5286
try {
53-
val json = serializeJsonPayload(streamable)
5487
conn = makeRequest(URL(urlString), json, headers)
5588

5689
// End the request, get the response code

0 commit comments

Comments
 (0)