Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bugsnag-android-core/api/bugsnag-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ public final class com/bugsnag/android/DeliveryStatus$Companion {
public final fun forHttpResponseCode (I)Lcom/bugsnag/android/DeliveryStatus;
}

public final class com/bugsnag/android/DeliveryStrategy : java/lang/Enum {
public static final field SEND_IMMEDIATELY Lcom/bugsnag/android/DeliveryStrategy;
public static final field STORE_AND_FLUSH Lcom/bugsnag/android/DeliveryStrategy;
public static final field STORE_AND_SEND Lcom/bugsnag/android/DeliveryStrategy;
public static final field STORE_ONLY Lcom/bugsnag/android/DeliveryStrategy;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/bugsnag/android/DeliveryStrategy;
public static fun values ()[Lcom/bugsnag/android/DeliveryStrategy;
}

public class com/bugsnag/android/Device : com/bugsnag/android/JsonStream$Streamable {
public final fun getCpuAbi ()[Ljava/lang/String;
public final fun getId ()Ljava/lang/String;
Expand Down Expand Up @@ -392,6 +402,7 @@ public class com/bugsnag/android/Event : com/bugsnag/android/FeatureFlagAware, c
public fun getApp ()Lcom/bugsnag/android/AppWithState;
public fun getBreadcrumbs ()Ljava/util/List;
public fun getContext ()Ljava/lang/String;
public fun getDeliveryStrategy ()Lcom/bugsnag/android/DeliveryStrategy;
public fun getDevice ()Lcom/bugsnag/android/DeviceWithState;
public fun getErrors ()Ljava/util/List;
public fun getFeatureFlags ()Ljava/util/List;
Expand All @@ -408,6 +419,7 @@ public class com/bugsnag/android/Event : com/bugsnag/android/FeatureFlagAware, c
public fun leaveBreadcrumb (Ljava/lang/String;Lcom/bugsnag/android/BreadcrumbType;Ljava/util/Map;)Lcom/bugsnag/android/Breadcrumb;
public fun setApiKey (Ljava/lang/String;)V
public fun setContext (Ljava/lang/String;)V
public fun setDeliveryStrategy (Lcom/bugsnag/android/DeliveryStrategy;)V
public fun setErrorReportingThread (J)V
public fun setErrorReportingThread (Lcom/bugsnag/android/Thread;)V
public fun setGroupingDiscriminator (Ljava/lang/String;)Ljava/lang/String;
Expand Down
2 changes: 1 addition & 1 deletion bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<ID>LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceIdStore: Provider&lt;DeviceIdStore.DeviceIds?>, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, private val rootedFuture: Provider&lt;Boolean>?, private val bgTaskService: BackgroundTaskService, private val logger: Logger )</ID>
<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>
<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>
<ID>LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList&lt;Breadcrumb> = mutableListOf(), discardClasses: Set&lt;Pattern> = 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;Pattern>? = null )</ID>
<ID>LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList&lt;Breadcrumb> = mutableListOf(), discardClasses: Set&lt;Pattern> = 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;Pattern>? = null, isAttemptDeliveryOnCrash: Boolean = false )</ID>
<ID>LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState )</ID>
<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>
<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, @JvmField val maxBreadcrumbs: Int )</ID>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.bugsnag.android;

import static com.bugsnag.android.SeverityReason.REASON_PROMISE_REJECTION;

import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.TaskType;
Expand Down Expand Up @@ -54,23 +52,25 @@ void deliver(@NonNull Event event) {
}
}

if (event.getImpl().getOriginalUnhandled()) {
// should only send unhandled errors if they don't terminate the process (i.e. ANRs)
String severityReasonType = event.getImpl().getSeverityReasonType();
boolean promiseRejection = REASON_PROMISE_REJECTION.equals(severityReasonType);
boolean anr = event.getImpl().isAnr(event);
if (anr || promiseRejection) {
cacheEvent(event, true);
} else if (immutableConfig.getAttemptDeliveryOnCrash()) {
switch (event.getDeliveryStrategy()) {
case STORE_AND_SEND:
cacheAndSendSynchronously(event);
} else {
break;
case STORE_ONLY:
cacheEvent(event, false);
}
} else if (callbackState.runOnSendTasks(event, logger)) {
// Build the eventPayload
String apiKey = event.getApiKey();
EventPayload eventPayload = new EventPayload(apiKey, event, notifier, immutableConfig);
deliverPayloadAsync(event, eventPayload);
break;
case SEND_IMMEDIATELY:
if (callbackState.runOnSendTasks(event, logger)) {
String apiKey = event.getApiKey();
EventPayload eventPayload = new EventPayload(
apiKey, event, notifier, immutableConfig);
deliverPayloadAsync(event, eventPayload);
}
break;
case STORE_AND_FLUSH:
default:
cacheEvent(event, true);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.bugsnag.android

enum class DeliveryStrategy {
STORE_ONLY,
STORE_AND_FLUSH,
STORE_AND_SEND,
SEND_IMMEDIATELY,
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.bugsnag.android;

import static com.bugsnag.android.SeverityReason.REASON_PROMISE_REJECTION;

import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.InternalMetrics;

Expand Down Expand Up @@ -575,4 +577,47 @@ void setRedactedKeys(Collection<Pattern> redactedKeys) {
void setInternalMetrics(InternalMetrics metrics) {
impl.setInternalMetrics(metrics);
}

/**
* Returns the delivery strategy for this event, which determines how the event
* should be delivered to the Bugsnag API.
*
* @return the delivery strategy, or null if no specific strategy is set
*/
@NonNull
public DeliveryStrategy getDeliveryStrategy() {
if (impl.getDeliveryStrategy() != null) {
return impl.getDeliveryStrategy();
}

if (getImpl().getOriginalUnhandled()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (getImpl().getOriginalUnhandled()) {
if (impl.getOriginalUnhandled()) {

String severityReasonType = getImpl().getSeverityReasonType();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
String severityReasonType = getImpl().getSeverityReasonType();
String severityReasonType = impl.getSeverityReasonType();

boolean promiseRejection = REASON_PROMISE_REJECTION.equals(severityReasonType);
boolean anr = getImpl().isAnr(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
boolean anr = getImpl().isAnr(this);
boolean anr = impl.isAnr(this);

if (anr || promiseRejection) {
return DeliveryStrategy.STORE_AND_FLUSH;
} else if (getImpl().isAttemptDeliveryOnCrash()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (getImpl().isAttemptDeliveryOnCrash()) {
} else if (impl.isAttemptDeliveryOnCrash()) {

return DeliveryStrategy.STORE_AND_SEND;
} else {
return DeliveryStrategy.STORE_ONLY;
}
} else {
return DeliveryStrategy.SEND_IMMEDIATELY;
}
}

/**
* Sets the delivery strategy for this event, which determines how the event
* should be delivered to the Bugsnag API. This allows customization of delivery
* behavior on a per-event basis.
*
* @param deliveryStrategy the delivery strategy to use for this event
*/
public void setDeliveryStrategy(@NonNull DeliveryStrategy deliveryStrategy) {
if (deliveryStrategy != null) {
impl.setDeliveryStrategy(deliveryStrategy);
} else {
logNull("deliveryStrategy");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
severityReason,
ThreadState(originalError, severityReason.unhandled, config).threads,
User(),
config.redactedKeys.toSet()
config.redactedKeys.toSet(),
config.attemptDeliveryOnCrash
)

internal constructor(
Expand All @@ -50,7 +51,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION),
threads: MutableList<Thread> = mutableListOf(),
user: User = User(),
redactionKeys: Set<Pattern>? = null
redactionKeys: Set<Pattern>? = null,
isAttemptDeliveryOnCrash: Boolean = false
) {
this.logger = logger
this.apiKey = apiKey
Expand All @@ -64,7 +66,7 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
this.severityReason = severityReason
this.threads = threads
this.userImpl = user

this.isAttemptDeliveryOnCrash = isAttemptDeliveryOnCrash
redactionKeys?.let {
this.redactedKeys = it
}
Expand All @@ -76,6 +78,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
val logger: Logger
val metadata: Metadata
val featureFlags: FeatureFlags
val isAttemptDeliveryOnCrash: Boolean

private val discardClasses: Set<Pattern>
internal var projectPackages: Collection<String>

Expand Down Expand Up @@ -123,6 +127,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata

var traceCorrelation: TraceCorrelation? = null

var deliveryStrategy: DeliveryStrategy? = null

fun getUnhandledOverridden(): Boolean = severityReason.unhandledOverridden

fun getOriginalUnhandled(): Boolean = severityReason.originalUnhandled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ internal class DeliveryDelegateTest {
notifier,
BackgroundTaskService()
)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
event.session = Session(
"123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey
)
}

@Test
Expand All @@ -63,6 +65,8 @@ internal class DeliveryDelegateTest {
assertEquals(0, event.session!!.handledCount)

assertEquals("BUGSNAG_API_KEY", event.session!!.apiKey)

assertEquals(DeliveryStrategy.STORE_ONLY, event.deliveryStrategy)
}

@Test
Expand All @@ -71,7 +75,9 @@ internal class DeliveryDelegateTest {
SeverityReason.REASON_HANDLED_EXCEPTION
)
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
event.session = Session(
"123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey
)

var msg: StateEvent.NotifyHandled? = null
deliveryDelegate.addObserver(
Expand All @@ -87,6 +93,8 @@ internal class DeliveryDelegateTest {
// check session count incremented
assertEquals(0, event.session!!.unhandledCount)
assertEquals(1, event.session!!.handledCount)

assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
}

@Test
Expand All @@ -107,6 +115,8 @@ internal class DeliveryDelegateTest {

// verify no payload was sent for an Event with no errors
assertNull(msg)

assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.bugsnag.android

import com.bugsnag.android.BugsnagTestUtils.generateImmutableConfig
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
import java.util.Date

@RunWith(MockitoJUnitRunner::class)
internal class EventDeliveryStrategyTest {

private val apiKey = "BUGSNAG_API_KEY"
private val notifier = Notifier()
private val config = generateImmutableConfig()
private var testException: RuntimeException? = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could reasonably be lateinit var instead of being nullable? Alternatively it could be replaced with fun createTestException()

private val event = Event(
RuntimeException("Whoops!"), config,
SeverityReason.newInstance(
SeverityReason.REASON_UNHANDLED_EXCEPTION
),
NoopLogger
)
Comment on lines +18 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is only used in testGenerateUnhandledReport it should be moved there


@Before
fun setUp() {
testException = RuntimeException("Example message")
event.session = Session(
"123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey
)
}

@Test
fun testGenerateUnhandledReport() {
assertEquals(DeliveryStrategy.STORE_ONLY, event.deliveryStrategy)
event.deliveryStrategy = DeliveryStrategy.SEND_IMMEDIATELY
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
}

@Test
fun testANRStoreEventAndFlush() {
val anrEvent = Event(
testException, config,
SeverityReason.newInstance(SeverityReason.REASON_ANR),
NoopLogger
)
anrEvent.getErrors().get(0).setErrorClass("ANR")
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, anrEvent.getDeliveryStrategy())
}

@Test
fun testPromiseRejectionEvetStoreAndFlush() {
val promiseRejectionEvent = Event(
testException, config,
SeverityReason.newInstance(SeverityReason.REASON_PROMISE_REJECTION),
NoopLogger
)
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, promiseRejectionEvent.getDeliveryStrategy())
}

@Test
fun testANRWithModifiedErrorClassStoreAndFlush() {
val modifiedAnrEvent = Event(
testException,
config,
SeverityReason.newInstance(SeverityReason.REASON_PROMISE_REJECTION),
NoopLogger
)
modifiedAnrEvent.getErrors().get(0).setErrorClass("ANR")
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, modifiedAnrEvent.getDeliveryStrategy())
}

@Test
fun testGenerateHandledReport() {
val state = SeverityReason.newInstance(
SeverityReason.REASON_HANDLED_EXCEPTION
)
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
event.session = Session(
"123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey
)
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
}

@Test
fun testDeliveryStrategyStoreAndSend() {
val configuration = Configuration(apiKey)
val newConfig = generateImmutableConfig(configuration).apply {
configuration.setAttemptDeliveryOnCrash(true)
}
val unhandledEvent = Event(
testException, newConfig,
SeverityReason.newInstance(SeverityReason.REASON_UNHANDLED_EXCEPTION),
NoopLogger
)
assertEquals(DeliveryStrategy.STORE_ONLY, unhandledEvent.getDeliveryStrategy())
}
}
1 change: 0 additions & 1 deletion bugsnag-plugin-android-exitinfo/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<CurrentIssues>
<ID>CyclomaticComplexMethod:CodeDescriptions.kt$@RequiresApi(Build.VERSION_CODES.R) @SuppressLint("SwitchIntDef") @Suppress("DEPRECATION") internal fun importanceDescriptionOf(exitInfo: ApplicationExitInfo)</ID>
<ID>CyclomaticComplexMethod:CodeDescriptions.kt$@RequiresApi(Build.VERSION_CODES.R) internal fun exitReasonOf(exitInfo: ApplicationExitInfo)</ID>
<ID>LongParameterList:TombstoneParser.kt$TombstoneParser$( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, fileDescriptorConsumer: (Int, String, String) -> Unit, logcatConsumer: (String) -> Unit )</ID>
<ID>MagicNumber:TraceParser.kt$TraceParser$16</ID>
<ID>MagicNumber:TraceParser.kt$TraceParser$3</ID>
<ID>MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so "</ID>
Expand Down
Loading