Updated Bugsnag

master
M66B 1 month ago
parent 113898de3b
commit 552d4403c2

@ -592,7 +592,7 @@ dependencies {
def minidns_version = "1.0.5"
def openpgp_version = "12.0"
def badge_version = "1.1.22"
def bugsnag_version = "6.4.0"
def bugsnag_version = "6.7.0"
def biweekly_version = "0.6.8"
def vcard_version = "0.12.1"
def relinker_version = "1.4.5"

@ -2,6 +2,8 @@ package com.bugsnag.android
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Build
import android.os.Bundle
import java.util.WeakHashMap
@ -11,8 +13,16 @@ internal class ActivityBreadcrumbCollector(
private val prevState = WeakHashMap<Activity, String>()
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) =
leaveBreadcrumb(activity, "onCreate()", savedInstanceState != null)
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
leaveBreadcrumb(
activity,
"onCreate()",
mutableMapOf<String, Any>().apply {
set("hasBundle", savedInstanceState != null)
setActivityIntentMetadata(activity.intent)
}
)
}
override fun onActivityStarted(activity: Activity) =
leaveBreadcrumb(activity, "onStart()")
@ -39,12 +49,8 @@ internal class ActivityBreadcrumbCollector(
private fun leaveBreadcrumb(
activity: Activity,
lifecycleCallback: String,
hasBundle: Boolean? = null
metadata: MutableMap<String, Any> = mutableMapOf()
) {
val metadata = mutableMapOf<String, Any>()
if (hasBundle != null) {
metadata["hasBundle"] = hasBundle
}
val previousVal = prevState[activity]
if (previousVal != null) {
@ -55,4 +61,24 @@ internal class ActivityBreadcrumbCollector(
cb("$activityName#$lifecycleCallback", metadata)
prevState[activity] = lifecycleCallback
}
private fun MutableMap<String, Any>.setActivityIntentMetadata(intent: Intent?) {
if (intent == null) return
intent.action?.let { set("action", it) }
intent.categories?.let { set("categories", it.joinToString(", ")) }
intent.type?.let { set("type", it) }
if (intent.flags != 0) {
@Suppress("MagicNumber") // hex radix
set("flags", "0x${intent.flags.toString(16)}")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
intent.identifier?.let { set("id", it) }
}
set("hasData", intent.data != null)
set("hasExtras", intent.extras?.keySet()?.joinToString(", ") ?: false)
}
}

@ -1,5 +1,6 @@
package com.bugsnag.android
import com.bugsnag.android.internal.DateUtils
import java.io.IOException
import java.util.concurrent.atomic.AtomicInteger
@ -42,8 +43,7 @@ internal class BreadcrumbState(
StateEvent.AddBreadcrumb(
breadcrumb.impl.message,
breadcrumb.impl.type,
// an encoding of milliseconds since the epoch
"t${breadcrumb.impl.timestamp.time}",
DateUtils.toIso8601(breadcrumb.impl.timestamp),
breadcrumb.impl.metadata ?: mutableMapOf()
)
}

@ -7,6 +7,7 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.UUID
internal class BugsnagEventMapper(
private val logger: Logger
@ -93,6 +94,16 @@ internal class BugsnagEventMapper(
// populate internalMetrics
event.internalMetrics = InternalMetricsImpl(map["usage"] as MutableMap<String, Any>?)
// populate correlation
(map["correlation"] as? Map<String, String>)?.let {
val traceId = parseTraceId(it["traceId"])
val spanId = it["spanId"]?.parseUnsignedLong()
if (traceId != null && spanId != null) {
event.traceCorrelation = TraceCorrelation(traceId, spanId)
}
}
return event
}
@ -184,14 +195,14 @@ internal class BugsnagEventMapper(
thread.readEntry("name"),
ErrorType.fromDescriptor(thread.readEntry("type")) ?: ErrorType.ANDROID,
thread["errorReportingThread"] == true,
thread.readEntry("state"),
thread["state"] as? String ?: "",
(thread["stacktrace"] as? List<Map<String, Any?>>)?.let { convertStacktrace(it) }
?: Stacktrace(emptyList())
?: Stacktrace(mutableListOf())
)
}
internal fun convertStacktrace(trace: List<Map<String, Any?>>): Stacktrace {
return Stacktrace(trace.map { Stackframe(it) })
return Stacktrace(trace.mapTo(ArrayList(trace.size)) { Stackframe(it) })
}
internal fun deserializeSeverityReason(
@ -234,16 +245,15 @@ internal class BugsnagEventMapper(
}
}
// SimpleDateFormat isn't thread safe, cache one instance per thread as needed.
private val ndkDateFormatHolder = object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat {
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
private fun String.toDate(): Date {
if (isNotEmpty() && this[0] == 't') {
// date is in the format 't{epoch millis}'
val timestamp = substring(1)
timestamp.toLongOrNull()?.let {
return Date(it)
}
}
}
private fun String.toDate(): Date {
return try {
DateUtils.fromIso8601(this)
} catch (pe: IllegalArgumentException) {
@ -251,4 +261,31 @@ internal class BugsnagEventMapper(
?: throw IllegalArgumentException("cannot parse date $this")
}
}
private fun parseTraceId(traceId: String?): UUID? {
if (traceId?.length != 32) return null
val mostSigBits = traceId.substring(0, 16).parseUnsignedLong() ?: return null
val leastSigBits = traceId.substring(16).parseUnsignedLong() ?: return null
return UUID(mostSigBits, leastSigBits)
}
private fun String.parseUnsignedLong(): Long? {
if (length != 16) return null
return try {
(substring(0, 2).toLong(16) shl 56) or
substring(2).toLong(16)
} catch (nfe: NumberFormatException) {
null
}
}
// SimpleDateFormat isn't thread safe, cache one instance per thread as needed.
private val ndkDateFormatHolder = object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat {
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
}
}
}

@ -22,7 +22,8 @@ internal class ClientObservable : BaseObservable() {
conf.releaseStage,
lastRunInfoPath,
consecutiveLaunchCrashes,
conf.sendThreads
conf.sendThreads,
conf.maxBreadcrumbs
)
}
}

@ -88,6 +88,16 @@ public class Error implements JsonStream.Streamable {
return impl.getStacktrace();
}
/**
* Add a new stackframe to the end of this Error returning the new Stackframe data object.
*/
@NonNull
public Stackframe addStackframe(@Nullable String method,
@Nullable String file,
long lineNumber) {
return impl.addStackframe(method, file, lineNumber);
}
@Override
public void toStream(@NonNull JsonStream stream) throws IOException {
impl.toStream(stream);

@ -7,17 +7,30 @@ internal class ErrorInternal @JvmOverloads internal constructor(
var type: ErrorType = ErrorType.ANDROID
) : JsonStream.Streamable {
val stacktrace: List<Stackframe> = stacktrace.trace
val stacktrace: MutableList<Stackframe> = stacktrace.trace
fun addStackframe(method: String?, file: String?, lineNumber: Long): Stackframe {
val frame = Stackframe(method, file, lineNumber, null)
stacktrace.add(frame)
return frame
}
internal companion object {
fun createError(exc: Throwable, projectPackages: Collection<String>, logger: Logger): MutableList<Error> {
fun createError(
exc: Throwable,
projectPackages: Collection<String>,
logger: Logger
): MutableList<Error> {
return exc.safeUnrollCauses()
.mapTo(mutableListOf()) { currentEx ->
// Somehow it's possible for stackTrace to be null in rare cases
val stacktrace = currentEx.stackTrace ?: arrayOf<StackTraceElement>()
val trace = Stacktrace(stacktrace, projectPackages, logger)
val errorInternal =
ErrorInternal(currentEx.javaClass.name, currentEx.localizedMessage, trace)
val errorInternal = ErrorInternal(
currentEx.javaClass.name,
currentEx.localizedMessage,
trace
)
return@mapTo Error(errorInternal, logger)
}

@ -10,6 +10,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
/**
@ -50,8 +51,8 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
}
/**
* The Throwable object that caused the event in your application.
*
* The {@link Throwable} object that caused the event in your application.
* <p>
* Manipulating this field does not affect the error information reported to the
* Bugsnag dashboard. Use {@link Event#getErrors()} to access and amend the representation of
* the error that will be sent.
@ -65,7 +66,7 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
* Information extracted from the {@link Throwable} that caused the event can be found in this
* field. The list contains at least one {@link Error} that represents the thrown object
* with subsequent elements in the list populated from {@link Throwable#getCause()}.
*
* <p>
* A reference to the actual {@link Throwable} object that caused the event is available
* through {@link Event#getOriginalError()} ()}.
*/
@ -74,6 +75,35 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
return impl.getErrors();
}
/**
* Add a new error to this event and return its Error data. The new Error will appear at the
* end of the {@link #getErrors() errors list}.
*/
@NonNull
public Error addError(@NonNull Throwable error) {
return impl.addError(error);
}
/**
* Add a new empty {@link ErrorType#ANDROID android} error to this event and return its Error
* data. The new Error will appear at the end of the {@link #getErrors() errors list}.
*/
@NonNull
public Error addError(@NonNull String errorClass, @Nullable String errorMessage) {
return impl.addError(errorClass, errorMessage, ErrorType.ANDROID);
}
/**
* Add a new empty error to this event and return its Error data. The new Error will appear
* at the end of the {@link #getErrors() errors list}.
*/
@NonNull
public Error addError(@NonNull String errorClass,
@Nullable String errorMessage,
@NonNull ErrorType errorType) {
return impl.addError(errorClass, errorMessage, errorType);
}
/**
* If thread state is being captured along with the event, this field will contain a
* list of {@link Thread} objects.
@ -83,6 +113,46 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
return impl.getThreads();
}
/**
* Create, add and return a new empty {@link Thread} object to this event with a given id
* and name. This can be used to augment the event with thread data that would not be picked
* up as part of a normal event being generated (for example: native threads managed
* by cross-platform toolkits).
*
* @return a new Thread object of type {@link ErrorType#ANDROID} with no stacktrace
*/
@NonNull
public Thread addThread(@NonNull String id,
@NonNull String name) {
return impl.addThread(
id,
name,
ErrorType.ANDROID,
false,
Thread.State.RUNNABLE.getDescriptor()
);
}
/**
* Create, add and return a new empty {@link Thread} object to this event with a given id
* and name. This can be used to augment the event with thread data that would not be picked
* up as part of a normal event being generated (for example: native threads managed
* by cross-platform toolkits).
*
* @return a new Thread object of type {@link ErrorType#ANDROID} with no stacktrace
*/
@NonNull
public Thread addThread(long id,
@NonNull String name) {
return impl.addThread(
Long.toString(id),
name,
ErrorType.ANDROID,
false,
Thread.State.RUNNABLE.getDescriptor()
);
}
/**
* A list of breadcrumbs leading up to the event. These values can be accessed and amended
* if necessary. See {@link Breadcrumb} for details of the data available.
@ -92,6 +162,26 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
return impl.getBreadcrumbs();
}
/**
* Add a new breadcrumb to this event and return its Breadcrumb object. The new breadcrumb
* will be added to the end of the {@link #getBreadcrumbs() breadcrumbs list} by this method.
*/
@NonNull
public Breadcrumb leaveBreadcrumb(@NonNull String message,
@NonNull BreadcrumbType type,
@Nullable Map<String, Object> metadata) {
return impl.leaveBreadcrumb(message, type, metadata);
}
/**
* Add a new breadcrumb to this event and return its Breadcrumb object. The new breadcrumb
* will be added to the end of the {@link #getBreadcrumbs() breadcrumbs list} by this# method.
*/
@NonNull
public Breadcrumb leaveBreadcrumb(@NonNull String message) {
return impl.leaveBreadcrumb(message, BreadcrumbType.MANUAL, null);
}
/**
* A list of feature flags active at the time of the event.
* See {@link FeatureFlag} for details of the data available.
@ -166,7 +256,7 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
* All events with the same grouping hash will be grouped together into one error. This is an
* advanced usage of the library and mis-using it will cause your events not to group properly
* in your dashboard.
*
* <p>
* As the name implies, this option accepts a hash of sorts.
*/
public void setGroupingHash(@Nullable String groupingHash) {
@ -178,7 +268,7 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
* All events with the same grouping hash will be grouped together into one error. This is an
* advanced usage of the library and mis-using it will cause your events not to group properly
* in your dashboard.
*
* <p>
* As the name implies, this option accepts a hash of sorts.
*/
@Nullable
@ -382,6 +472,21 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
impl.setUnhandled(unhandled);
}
/**
* Associate this event with a specific trace. This is usually done automatically when
* using bugsnag-android-performance, but can also be set manually if required.
*
* @param traceId the ID of the trace the event occurred within
* @param spanId the ID of the span that the event occurred within
*/
public void setTraceCorrelation(@NonNull UUID traceId, long spanId) {
if (traceId != null) {
impl.setTraceCorrelation(new TraceCorrelation(traceId, spanId));
} else {
logNull("traceId");
}
}
protected boolean shouldDiscardClass() {
return impl.shouldDiscardClass();
}

@ -6,6 +6,7 @@ import com.bugsnag.android.internal.InternalMetricsNoop
import com.bugsnag.android.internal.JsonHelper
import com.bugsnag.android.internal.TrimMetrics
import java.io.IOException
import java.util.Date
import java.util.regex.Pattern
internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, MetadataAware, UserAware {
@ -119,6 +120,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
*/
internal var userImpl: User
var traceCorrelation: TraceCorrelation? = null
fun getUnhandledOverridden(): Boolean = severityReason.unhandledOverridden
fun getOriginalUnhandled(): Boolean = severityReason.originalUnhandled
@ -193,6 +196,10 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
writer.name("featureFlags").value(featureFlags)
traceCorrelation?.let { correlation ->
writer.name("correlation").value(correlation)
}
if (session != null) {
val copy = Session.copySession(session)
writer.name("session").beginObject()
@ -318,4 +325,72 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
override fun clearFeatureFlag(name: String) = featureFlags.clearFeatureFlag(name)
override fun clearFeatureFlags() = featureFlags.clearFeatureFlags()
fun addError(thrownError: Throwable?): Error {
if (thrownError == null) {
val newError = Error(
ErrorInternal("null", null, Stacktrace(ArrayList())),
logger
)
errors.add(newError)
return newError
} else {
val newErrors = Error.createError(thrownError, projectPackages, logger)
errors.addAll(newErrors)
return newErrors.first()
}
}
fun addError(errorClass: String?, errorMessage: String?, errorType: ErrorType?): Error {
val error = Error(
ErrorInternal(
errorClass.toString(),
errorMessage,
Stacktrace(ArrayList()),
errorType ?: ErrorType.ANDROID
),
logger
)
errors.add(error)
return error
}
fun addThread(
id: String?,
name: String?,
errorType: ErrorType,
isErrorReportingThread: Boolean,
state: String
): Thread {
val thread = Thread(
ThreadInternal(
id.toString(),
name.toString(),
errorType,
isErrorReportingThread,
state,
Stacktrace(ArrayList())
),
logger
)
threads.add(thread)
return thread
}
fun leaveBreadcrumb(
message: String?,
type: BreadcrumbType?,
metadata: MutableMap<String, Any?>?
): Breadcrumb {
val breadcrumb = Breadcrumb(
message.toString(),
type ?: BreadcrumbType.MANUAL,
metadata,
Date(),
logger
)
breadcrumbs.add(breadcrumb)
return breadcrumb
}
}

@ -196,6 +196,7 @@ internal class EventStore(
return null
}
} catch (ioe: Exception) {
logger.w("could not parse event payload", ioe)
eventSource.clear()
}
val processedEvent = eventSource.event
@ -208,7 +209,7 @@ internal class EventStore(
}
private fun handleEventFlushFailure(exc: Exception, eventFile: File) {
delegate?.onErrorIOFailure(exc, eventFile, "Crash Report Deserialization")
logger.e(exc.message ?: "Failed to send event", exc)
deleteStoredFiles(setOf(eventFile))
}

@ -14,7 +14,7 @@ import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
internal abstract class FileStore(
private val storageDir: File,
val storageDir: File,
private val maxStoreCount: Int,
private val comparator: Comparator<in File?>,
protected open val logger: Logger,

@ -21,6 +21,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Used as the entry point for native code to allow proguard to obfuscate other areas if needed
*/
@ -430,6 +431,25 @@ public class NativeInterface {
}
}
/**
* Attempt to deliver an existing event file that is not current enqueued for delivery. The
* filename is expected to be in the standard {@link EventFilenameInfo} format, and the file
* should contain a correctly formatted {@link Event} object. This method will attempt to
* move the file into place, and flush the queue asynchronously. If the file cannot be moved
* into the queue directory, the file is deleted before returning.
*
* @param reportFile the file to enqueue for delivery
*/
public static void deliverReport(@NonNull File reportFile) {
EventStore eventStore = getClient().eventStore;
File eventFile = new File(eventStore.getStorageDir(), reportFile.getName());
if (reportFile.renameTo(eventFile)) {
eventStore.flushAsync();
} else {
reportFile.delete();
}
}
/**
* Notifies using the Android SDK
*

@ -7,7 +7,7 @@ import java.io.IOException
*/
class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier",
var version: String = "6.4.0",
var version: String = "6.7.0",
var url: String = "https://bugsnag.com"
) : JsonStream.Streamable {

@ -28,11 +28,11 @@ public final class Session implements JsonStream.Streamable, UserAware {
private App app;
private Device device;
private final AtomicBoolean autoCaptured = new AtomicBoolean(false);
private volatile boolean autoCaptured = false;
private final AtomicInteger unhandledCount = new AtomicInteger();
private final AtomicInteger handledCount = new AtomicInteger();
private final AtomicBoolean tracked = new AtomicBoolean(false);
final AtomicBoolean isPaused = new AtomicBoolean(false);
private final AtomicBoolean isPaused = new AtomicBoolean(false);
private String apiKey;
@ -41,7 +41,7 @@ public final class Session implements JsonStream.Streamable, UserAware {
session.unhandledCount.get(), session.handledCount.get(), session.notifier,
session.logger, session.getApiKey());
copy.tracked.set(session.tracked.get());
copy.autoCaptured.set(session.isAutoCaptured());
copy.autoCaptured = session.isAutoCaptured();
return copy;
}
@ -68,7 +68,7 @@ public final class Session implements JsonStream.Streamable, UserAware {
this.id = id;
this.startedAt = new Date(startedAt.getTime());
this.user = user;
this.autoCaptured.set(autoCaptured);
this.autoCaptured = autoCaptured;
this.apiKey = apiKey;
}
@ -198,16 +198,28 @@ public final class Session implements JsonStream.Streamable, UserAware {
return copySession(this);
}
AtomicBoolean isTracked() {
return tracked;
boolean markTracked() {
return tracked.compareAndSet(false, true);
}
boolean markResumed() {
return isPaused.compareAndSet(true, false);
}
void markPaused() {
isPaused.set(true);
}
boolean isPaused() {
return isPaused.get();
}
boolean isAutoCaptured() {
return autoCaptured.get();
return autoCaptured;
}
void setAutoCaptured(boolean autoCaptured) {
this.autoCaptured.set(autoCaptured);
this.autoCaptured = autoCaptured;
}
/**

@ -36,7 +36,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
private volatile Session currentSession = null;
final BackgroundTaskService backgroundTaskService;
final Logger logger;
private boolean shouldSuppressFirstAutoSession = false;
private boolean shouldSuppressFirstAutoSession = true;
SessionTracker(ImmutableConfig configuration,
CallbackState callbackState,
@ -108,9 +108,13 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
&& existingSession != null
&& !existingSession.isAutoCaptured()
&& shouldSuppressFirstAutoSession) {
shouldSuppressFirstAutoSession = true;
shouldSuppressFirstAutoSession = false;
return true;
}
if (autoCaptured) {
shouldSuppressFirstAutoSession = false;
}
}
return false;
}
@ -120,7 +124,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
Session session = currentSession;
if (session != null) {
session.isPaused.set(true);
session.markPaused();
updateState(StateEvent.PauseSession.INSTANCE);
}
}
@ -133,7 +137,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
session = startSession(false);
resumed = false;
} else {
resumed = session.isPaused.compareAndSet(true, false);
resumed = session.markResumed();
}
if (session != null) {
@ -191,7 +195,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
session.setDevice(client.getDeviceDataCollector().generateDevice());
boolean deliverSession = callbackState.runOnSessionTasks(session, logger);
if (deliverSession && session.isTracked().compareAndSet(false, true)) {
if (deliverSession && session.markTracked()) {
currentSession = session;
notifySessionStartObserver(session);
flushInMemorySession(session);
@ -205,7 +209,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
Session getCurrentSession() {
Session session = currentSession;
if (session != null && !session.isPaused.get()) {
if (session != null && !session.isPaused()) {
return session;
}
return null;

@ -1,6 +1,7 @@
package com.bugsnag.android
import java.io.IOException
import kotlin.math.min
/**
* Serialize an exception stacktrace and mark frames as "in-project"
@ -25,11 +26,35 @@ internal class Stacktrace : JsonStream.Streamable {
else -> null
}
}
fun serializeStackframe(
el: StackTraceElement,
projectPackages: Collection<String>,
logger: Logger
): Stackframe? {
try {
val className = el.className
val methodName = when {
className.isNotEmpty() -> className + "." + el.methodName
else -> el.methodName
}
return Stackframe(
methodName,
el.fileName ?: "Unknown",
el.lineNumber,
inProject(className, projectPackages)
)
} catch (lineEx: Exception) {
logger.w("Failed to serialize stacktrace", lineEx)
return null
}
}
}
val trace: List<Stackframe>
val trace: MutableList<Stackframe>
constructor(frames: List<Stackframe>) {
constructor(frames: MutableList<Stackframe>) {
trace = limitTraceLength(frames)
}
@ -38,48 +63,26 @@ internal class Stacktrace : JsonStream.Streamable {
projectPackages: Collection<String>,
logger: Logger
) {
val frames = limitTraceLength(stacktrace)
trace = frames.mapNotNull { serializeStackframe(it, projectPackages, logger) }
}
private fun limitTraceLength(frames: Array<StackTraceElement>): Array<StackTraceElement> {
return when {
frames.size >= STACKTRACE_TRIM_LENGTH -> frames.sliceArray(0 until STACKTRACE_TRIM_LENGTH)
else -> frames
// avoid allocating new subLists or Arrays by only copying the required number of frames
// mapping them to our internal Stackframes as we go, roughly equivalent to
// stacktrace.take(STACKTRACE_TRIM_LENGTH).mapNotNullTo(ArrayList()) { ... }
val frameCount = min(STACKTRACE_TRIM_LENGTH, stacktrace.size)
trace = ArrayList(frameCount)
for (i in 0 until frameCount) {
val frame = serializeStackframe(stacktrace[i], projectPackages, logger)
if (frame != null) {
trace.add(frame)
}
}
}
private fun limitTraceLength(frames: List<Stackframe>): List<Stackframe> {
private fun limitTraceLength(frames: MutableList<Stackframe>): MutableList<Stackframe> {
return when {
frames.size >= STACKTRACE_TRIM_LENGTH -> frames.subList(0, STACKTRACE_TRIM_LENGTH)
else -> frames
}
}
private fun serializeStackframe(
el: StackTraceElement,
projectPackages: Collection<String>,
logger: Logger
): Stackframe? {
try {
val className = el.className
val methodName = when {
className.isNotEmpty() -> className + "." + el.methodName
else -> el.methodName
}
return Stackframe(
methodName,
el.fileName ?: "Unknown",
el.lineNumber,
inProject(className, projectPackages)
)
} catch (lineEx: Exception) {
logger.w("Failed to serialize stacktrace", lineEx)
return null
}
}
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
writer.beginArray()

@ -10,7 +10,8 @@ sealed class StateEvent { // JvmField allows direct field access optimizations
@JvmField val releaseStage: String?,
@JvmField val lastRunInfoPath: String,
@JvmField val consecutiveLaunchCrashes: Int,
@JvmField val sendThreads: ThreadSendPolicy
@JvmField val sendThreads: ThreadSendPolicy,
@JvmField val maxBreadcrumbs: Int
) : StateEvent()
object DeliverPending : StateEvent()

@ -160,6 +160,16 @@ public class Thread implements JsonStream.Streamable {
return impl.getStacktrace();
}
/**
* Add a new stackframe to the end of this thread returning the new Stackframe data object.
*/
@NonNull
public Stackframe addStackframe(@Nullable String method,
@Nullable String file,
long lineNumber) {
return impl.addStackframe(method, file, lineNumber);
}
@Override
public void toStream(@NonNull JsonStream stream) throws IOException {
impl.toStream(stream);

@ -13,6 +13,12 @@ class ThreadInternal internal constructor(
var stacktrace: MutableList<Stackframe> = stacktrace.trace.toMutableList()
fun addStackframe(method: String?, file: String?, lineNumber: Long): Stackframe {
val frame = Stackframe(method, file, lineNumber, null)
stacktrace.add(frame)
return frame
}
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
writer.beginObject()

@ -0,0 +1,20 @@
package com.bugsnag.android
import java.util.UUID
internal data class TraceCorrelation(val traceId: UUID, val spanId: Long) : JsonStream.Streamable {
override fun toStream(writer: JsonStream) {
writer.beginObject()
.name("traceId").value(traceId.toHexString())
.name("spanId").value(spanId.toHexString())
writer.endObject()
}
private fun UUID.toHexString(): String {
return "%016x%016x".format(mostSignificantBits, leastSignificantBits)
}
private fun Long.toHexString(): String {
return "%016x".format(this)
}
}

@ -53,7 +53,7 @@ class InternalMetricsImpl(source: Map<String, Any>? = null) : InternalMetrics {
// This is currently the only place where we set static data.
// When that changes in future, we'll need a StaticData object to properly merge data
// coming from multiple sources.
NdkPluginCaller.setStaticData(mapOf("usage" to mapOf("config" to configDifferences)))
NdkPluginCaller.setStaticData(mapOf("config" to configDifferences))
}
override fun setCallbackCounts(newCallbackCounts: Map<String, Int>) {

Loading…
Cancel
Save