Skip to content

Commit 04fb4e9

Browse files
committed
feat(event) added DeliveryStrategy to delivery for event
1 parent e4155f1 commit 04fb4e9

File tree

9 files changed

+218
-23
lines changed

9 files changed

+218
-23
lines changed

bugsnag-android-core/api/bugsnag-android-core.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,16 @@ public final class com/bugsnag/android/DeliveryStatus$Companion {
275275
public final fun forHttpResponseCode (I)Lcom/bugsnag/android/DeliveryStatus;
276276
}
277277

278+
public final class com/bugsnag/android/DeliveryStrategy : java/lang/Enum {
279+
public static final field SEND_IMMEDIATELY Lcom/bugsnag/android/DeliveryStrategy;
280+
public static final field STORE_AND_FLUSH Lcom/bugsnag/android/DeliveryStrategy;
281+
public static final field STORE_AND_SEND Lcom/bugsnag/android/DeliveryStrategy;
282+
public static final field STORE_ONLY Lcom/bugsnag/android/DeliveryStrategy;
283+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
284+
public static fun valueOf (Ljava/lang/String;)Lcom/bugsnag/android/DeliveryStrategy;
285+
public static fun values ()[Lcom/bugsnag/android/DeliveryStrategy;
286+
}
287+
278288
public class com/bugsnag/android/Device : com/bugsnag/android/JsonStream$Streamable {
279289
public final fun getCpuAbi ()[Ljava/lang/String;
280290
public final fun getId ()Ljava/lang/String;
@@ -392,6 +402,7 @@ public class com/bugsnag/android/Event : com/bugsnag/android/FeatureFlagAware, c
392402
public fun getApp ()Lcom/bugsnag/android/AppWithState;
393403
public fun getBreadcrumbs ()Ljava/util/List;
394404
public fun getContext ()Ljava/lang/String;
405+
public fun getDeliveryStrategy ()Lcom/bugsnag/android/DeliveryStrategy;
395406
public fun getDevice ()Lcom/bugsnag/android/DeviceWithState;
396407
public fun getErrors ()Ljava/util/List;
397408
public fun getFeatureFlags ()Ljava/util/List;
@@ -408,6 +419,7 @@ public class com/bugsnag/android/Event : com/bugsnag/android/FeatureFlagAware, c
408419
public fun leaveBreadcrumb (Ljava/lang/String;Lcom/bugsnag/android/BreadcrumbType;Ljava/util/Map;)Lcom/bugsnag/android/Breadcrumb;
409420
public fun setApiKey (Ljava/lang/String;)V
410421
public fun setContext (Ljava/lang/String;)V
422+
public fun setDeliveryStrategy (Lcom/bugsnag/android/DeliveryStrategy;)V
411423
public fun setErrorReportingThread (J)V
412424
public fun setErrorReportingThread (Lcom/bugsnag/android/Thread;)V
413425
public fun setGroupingDiscriminator (Ljava/lang/String;)Ljava/lang/String;

bugsnag-android-core/detekt-baseline.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<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>
2121
<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>
2222
<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>
23-
<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>
23+
<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>
2424
<ID>LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState )</ID>
2525
<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>
2626
<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>

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

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

3-
import static com.bugsnag.android.SeverityReason.REASON_PROMISE_REJECTION;
4-
53
import com.bugsnag.android.internal.BackgroundTaskService;
64
import com.bugsnag.android.internal.ImmutableConfig;
75
import com.bugsnag.android.internal.TaskType;
@@ -54,23 +52,26 @@ void deliver(@NonNull Event event) {
5452
}
5553
}
5654

57-
if (event.getImpl().getOriginalUnhandled()) {
58-
// should only send unhandled errors if they don't terminate the process (i.e. ANRs)
59-
String severityReasonType = event.getImpl().getSeverityReasonType();
60-
boolean promiseRejection = REASON_PROMISE_REJECTION.equals(severityReasonType);
61-
boolean anr = event.getImpl().isAnr(event);
62-
if (anr || promiseRejection) {
55+
switch (event.getDeliveryStrategy()) {
56+
case STORE_AND_FLUSH:
6357
cacheEvent(event, true);
64-
} else if (immutableConfig.getAttemptDeliveryOnCrash()) {
58+
break;
59+
case STORE_AND_SEND:
6560
cacheAndSendSynchronously(event);
66-
} else {
61+
break;
62+
case STORE_ONLY:
6763
cacheEvent(event, false);
68-
}
69-
} else if (callbackState.runOnSendTasks(event, logger)) {
70-
// Build the eventPayload
71-
String apiKey = event.getApiKey();
72-
EventPayload eventPayload = new EventPayload(apiKey, event, notifier, immutableConfig);
73-
deliverPayloadAsync(event, eventPayload);
64+
break;
65+
case SEND_IMMEDIATELY:
66+
if (callbackState.runOnSendTasks(event, logger)) {
67+
String apiKey = event.getApiKey();
68+
EventPayload eventPayload = new EventPayload(
69+
apiKey, event, notifier, immutableConfig);
70+
deliverPayloadAsync(event, eventPayload);
71+
}
72+
break;
73+
default:
74+
event.setDeliveryStrategy(DeliveryStrategy.STORE_AND_FLUSH);
7475
}
7576
}
7677

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.bugsnag.android
2+
3+
enum class DeliveryStrategy {
4+
STORE_ONLY,
5+
STORE_AND_FLUSH,
6+
STORE_AND_SEND,
7+
SEND_IMMEDIATELY,
8+
}

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

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

3+
import static com.bugsnag.android.SeverityReason.REASON_PROMISE_REJECTION;
4+
35
import com.bugsnag.android.internal.ImmutableConfig;
46
import com.bugsnag.android.internal.InternalMetrics;
57

@@ -575,4 +577,47 @@ void setRedactedKeys(Collection<Pattern> redactedKeys) {
575577
void setInternalMetrics(InternalMetrics metrics) {
576578
impl.setInternalMetrics(metrics);
577579
}
580+
581+
/**
582+
* Returns the delivery strategy for this event, which determines how the event
583+
* should be delivered to the Bugsnag API.
584+
*
585+
* @return the delivery strategy, or null if no specific strategy is set
586+
*/
587+
@NonNull
588+
public DeliveryStrategy getDeliveryStrategy() {
589+
if (impl.getDeliveryStrategy() != null) {
590+
return impl.getDeliveryStrategy();
591+
} else {
592+
if (getImpl().getOriginalUnhandled()) {
593+
String severityReasonType = getImpl().getSeverityReasonType();
594+
boolean promiseRejection = REASON_PROMISE_REJECTION.equals(severityReasonType);
595+
boolean anr = getImpl().isAnr(this);
596+
if (anr || promiseRejection) {
597+
return DeliveryStrategy.STORE_AND_FLUSH;
598+
} else if (getImpl().isAttemptDeliveryOnCrash()) {
599+
return DeliveryStrategy.STORE_AND_SEND;
600+
} else {
601+
return DeliveryStrategy.STORE_ONLY;
602+
}
603+
} else {
604+
return DeliveryStrategy.SEND_IMMEDIATELY;
605+
}
606+
}
607+
}
608+
609+
/**
610+
* Sets the delivery strategy for this event, which determines how the event
611+
* should be delivered to the Bugsnag API. This allows customization of delivery
612+
* behavior on a per-event basis.
613+
*
614+
* @param deliveryStrategy the delivery strategy to use for this event
615+
*/
616+
public void setDeliveryStrategy(@NonNull DeliveryStrategy deliveryStrategy) {
617+
if (deliveryStrategy != null) {
618+
impl.setDeliveryStrategy(deliveryStrategy);
619+
} else {
620+
logNull("DeliveryStrategy");
621+
}
622+
}
578623
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
3434
severityReason,
3535
ThreadState(originalError, severityReason.unhandled, config).threads,
3636
User(),
37-
config.redactedKeys.toSet()
37+
config.redactedKeys.toSet(),
38+
config.attemptDeliveryOnCrash
3839
)
3940

4041
internal constructor(
@@ -50,7 +51,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
5051
severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION),
5152
threads: MutableList<Thread> = mutableListOf(),
5253
user: User = User(),
53-
redactionKeys: Set<Pattern>? = null
54+
redactionKeys: Set<Pattern>? = null,
55+
isAttemptDeliveryOnCrash: Boolean = false
5456
) {
5557
this.logger = logger
5658
this.apiKey = apiKey
@@ -64,7 +66,7 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
6466
this.severityReason = severityReason
6567
this.threads = threads
6668
this.userImpl = user
67-
69+
this.isAttemptDeliveryOnCrash = isAttemptDeliveryOnCrash
6870
redactionKeys?.let {
6971
this.redactedKeys = it
7072
}
@@ -76,6 +78,8 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
7678
val logger: Logger
7779
val metadata: Metadata
7880
val featureFlags: FeatureFlags
81+
val isAttemptDeliveryOnCrash: Boolean
82+
7983
private val discardClasses: Set<Pattern>
8084
internal var projectPackages: Collection<String>
8185

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

124128
var traceCorrelation: TraceCorrelation? = null
125129

130+
var deliveryStrategy: DeliveryStrategy? = null
131+
126132
fun getUnhandledOverridden(): Boolean = severityReason.unhandledOverridden
127133

128134
fun getOriginalUnhandled(): Boolean = severityReason.originalUnhandled

bugsnag-android-core/src/test/java/com/bugsnag/android/DeliveryDelegateTest.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ internal class DeliveryDelegateTest {
4242
notifier,
4343
BackgroundTaskService()
4444
)
45-
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
45+
event.session =
46+
Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
4647
}
4748

4849
@Test
@@ -63,6 +64,8 @@ internal class DeliveryDelegateTest {
6364
assertEquals(0, event.session!!.handledCount)
6465

6566
assertEquals("BUGSNAG_API_KEY", event.session!!.apiKey)
67+
68+
assertEquals(DeliveryStrategy.STORE_ONLY, event.deliveryStrategy)
6669
}
6770

6871
@Test
@@ -71,7 +74,8 @@ internal class DeliveryDelegateTest {
7174
SeverityReason.REASON_HANDLED_EXCEPTION
7275
)
7376
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
74-
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
77+
event.session =
78+
Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
7579

7680
var msg: StateEvent.NotifyHandled? = null
7781
deliveryDelegate.addObserver(
@@ -87,6 +91,8 @@ internal class DeliveryDelegateTest {
8791
// check session count incremented
8892
assertEquals(0, event.session!!.unhandledCount)
8993
assertEquals(1, event.session!!.handledCount)
94+
95+
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
9096
}
9197

9298
@Test
@@ -107,6 +113,8 @@ internal class DeliveryDelegateTest {
107113

108114
// verify no payload was sent for an Event with no errors
109115
assertNull(msg)
116+
117+
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
110118
}
111119

112120
@Test
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.bugsnag.android
2+
3+
import com.bugsnag.android.BugsnagTestUtils.generateImmutableConfig
4+
import com.bugsnag.android.internal.BackgroundTaskService
5+
import com.bugsnag.android.internal.StateObserver
6+
import com.bugsnag.android.internal.dag.ValueProvider
7+
import org.junit.Assert.assertEquals
8+
import org.junit.Assert.assertNotNull
9+
import org.junit.Before
10+
import org.junit.Test
11+
import org.junit.runner.RunWith
12+
import org.mockito.Mock
13+
import org.mockito.junit.MockitoJUnitRunner
14+
import java.util.Date
15+
16+
@RunWith(MockitoJUnitRunner::class)
17+
internal class EventDeliveryStrategyTest {
18+
19+
@Mock
20+
lateinit var eventStore: EventStore
21+
22+
private val apiKey = "BUGSNAG_API_KEY"
23+
private val notifier = Notifier()
24+
val config = generateImmutableConfig()
25+
val callbackState = CallbackState()
26+
private val logger = InterceptingLogger()
27+
lateinit var deliveryDelegate: DeliveryDelegate
28+
val handledState = SeverityReason.newInstance(
29+
SeverityReason.REASON_UNHANDLED_EXCEPTION
30+
)
31+
32+
private var testException: RuntimeException? = null
33+
val event = Event(RuntimeException("Whoops!"), config, handledState, NoopLogger)
34+
35+
@Before
36+
fun setUp() {
37+
testException = RuntimeException("Example message")
38+
deliveryDelegate =
39+
DeliveryDelegate(
40+
logger,
41+
ValueProvider(eventStore),
42+
config,
43+
callbackState,
44+
notifier,
45+
BackgroundTaskService()
46+
)
47+
event.session =
48+
Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
49+
}
50+
51+
@Test
52+
fun generateUnhandledReport() {
53+
assertEquals(DeliveryStrategy.STORE_ONLY, event.deliveryStrategy)
54+
}
55+
56+
@Test
57+
fun testDeliveryStrategyStoreAndFlush() {
58+
// Test ANR case
59+
val anrEvent = Event(
60+
testException, config,
61+
SeverityReason.newInstance(SeverityReason.REASON_ANR),
62+
NoopLogger
63+
)
64+
anrEvent.getErrors().get(0).setErrorClass("ANR")
65+
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, anrEvent.getDeliveryStrategy())
66+
67+
// Test promise rejection case
68+
val promiseRejectionEvent = Event(
69+
testException, config,
70+
SeverityReason.newInstance(SeverityReason.REASON_PROMISE_REJECTION),
71+
NoopLogger
72+
)
73+
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, promiseRejectionEvent.getDeliveryStrategy())
74+
75+
// Test ANR with modified error class
76+
val modifiedAnrEvent = Event(
77+
testException,
78+
config,
79+
SeverityReason.newInstance(SeverityReason.REASON_PROMISE_REJECTION),
80+
NoopLogger
81+
)
82+
modifiedAnrEvent.getErrors().get(0).setErrorClass("ANR")
83+
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, modifiedAnrEvent.getDeliveryStrategy())
84+
}
85+
86+
@Test
87+
fun generateHandledReport() {
88+
val state = SeverityReason.newInstance(
89+
SeverityReason.REASON_HANDLED_EXCEPTION
90+
)
91+
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
92+
event.session =
93+
Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
94+
95+
var msg: StateEvent.NotifyHandled? = null
96+
deliveryDelegate.addObserver(
97+
StateObserver {
98+
msg = it as StateEvent.NotifyHandled
99+
}
100+
)
101+
deliveryDelegate.deliver(event)
102+
assertNotNull(msg)
103+
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
104+
}
105+
106+
@Test
107+
fun testDeliveryStrategyStoreAndSend() {
108+
val unhandledEvent = Event(
109+
testException, config,
110+
SeverityReason.newInstance(SeverityReason.REASON_UNHANDLED_EXCEPTION),
111+
NoopLogger
112+
)
113+
114+
assertEquals(DeliveryStrategy.STORE_ONLY, unhandledEvent.getDeliveryStrategy())
115+
}
116+
}

bugsnag-plugin-android-exitinfo/detekt-baseline.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
<CurrentIssues>
55
<ID>CyclomaticComplexMethod:CodeDescriptions.kt$@RequiresApi(Build.VERSION_CODES.R) @SuppressLint("SwitchIntDef") @Suppress("DEPRECATION") internal fun importanceDescriptionOf(exitInfo: ApplicationExitInfo)</ID>
66
<ID>CyclomaticComplexMethod:CodeDescriptions.kt$@RequiresApi(Build.VERSION_CODES.R) internal fun exitReasonOf(exitInfo: ApplicationExitInfo)</ID>
7-
<ID>LongParameterList:TombstoneParser.kt$TombstoneParser$( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, fileDescriptorConsumer: (Int, String, String) -> Unit, logcatConsumer: (String) -> Unit )</ID>
87
<ID>MagicNumber:TraceParser.kt$TraceParser$16</ID>
98
<ID>MagicNumber:TraceParser.kt$TraceParser$3</ID>
109
<ID>MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so "</ID>

0 commit comments

Comments
 (0)