Skip to content

Commit 662cb33

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

File tree

9 files changed

+217
-24
lines changed

9 files changed

+217
-24
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 & 17 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,25 @@ 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) {
63-
cacheEvent(event, true);
64-
} else if (immutableConfig.getAttemptDeliveryOnCrash()) {
55+
switch (event.getDeliveryStrategy()) {
56+
case STORE_AND_SEND:
6557
cacheAndSendSynchronously(event);
66-
} else {
58+
break;
59+
case STORE_ONLY:
6760
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);
61+
break;
62+
case SEND_IMMEDIATELY:
63+
if (callbackState.runOnSendTasks(event, logger)) {
64+
String apiKey = event.getApiKey();
65+
EventPayload eventPayload = new EventPayload(
66+
apiKey, event, notifier, immutableConfig);
67+
deliverPayloadAsync(event, eventPayload);
68+
}
69+
break;
70+
case STORE_AND_FLUSH:
71+
default:
72+
cacheEvent(event, true);
73+
break;
7474
}
7575
}
7676

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+
}
592+
593+
if (getImpl().getOriginalUnhandled()) {
594+
String severityReasonType = getImpl().getSeverityReasonType();
595+
boolean promiseRejection = REASON_PROMISE_REJECTION.equals(severityReasonType);
596+
boolean anr = getImpl().isAnr(this);
597+
if (anr || promiseRejection) {
598+
return DeliveryStrategy.STORE_AND_FLUSH;
599+
} else if (getImpl().isAttemptDeliveryOnCrash()) {
600+
return DeliveryStrategy.STORE_AND_SEND;
601+
} else {
602+
return DeliveryStrategy.STORE_ONLY;
603+
}
604+
} else {
605+
return DeliveryStrategy.SEND_IMMEDIATELY;
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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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.dag.ValueProvider
6+
import org.junit.Assert.assertEquals
7+
import org.junit.Before
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
import org.mockito.Mock
11+
import org.mockito.junit.MockitoJUnitRunner
12+
import java.util.Date
13+
14+
@RunWith(MockitoJUnitRunner::class)
15+
internal class EventDeliveryStrategyTest {
16+
17+
@Mock
18+
private lateinit var eventStore: EventStore
19+
20+
private val apiKey = "BUGSNAG_API_KEY"
21+
private val notifier = Notifier()
22+
private val config = generateImmutableConfig()
23+
private val callbackState = CallbackState()
24+
private val logger = InterceptingLogger()
25+
private lateinit var deliveryDelegate: DeliveryDelegate
26+
private val handledState = SeverityReason.newInstance(
27+
SeverityReason.REASON_UNHANDLED_EXCEPTION
28+
)
29+
private var testException: RuntimeException? = null
30+
private val event = Event(
31+
RuntimeException("Whoops!"), config, handledState, NoopLogger
32+
)
33+
34+
@Before
35+
fun setUp() {
36+
testException = RuntimeException("Example message")
37+
deliveryDelegate = DeliveryDelegate(
38+
logger,
39+
ValueProvider(eventStore),
40+
config,
41+
callbackState,
42+
notifier,
43+
BackgroundTaskService()
44+
)
45+
event.session = Session(
46+
"123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey
47+
)
48+
}
49+
50+
@Test
51+
fun testGenerateUnhandledReport() {
52+
assertEquals(DeliveryStrategy.STORE_ONLY, event.deliveryStrategy)
53+
event.deliveryStrategy = DeliveryStrategy.SEND_IMMEDIATELY
54+
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
55+
}
56+
57+
@Test
58+
fun testANRStoreEventAndFlush() {
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+
68+
@Test
69+
fun testPromiseRejectionEvetStoreAndFlush() {
70+
val promiseRejectionEvent = Event(
71+
testException, config,
72+
SeverityReason.newInstance(SeverityReason.REASON_PROMISE_REJECTION),
73+
NoopLogger
74+
)
75+
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, promiseRejectionEvent.getDeliveryStrategy())
76+
}
77+
78+
@Test
79+
fun testANRWithModifiedErrorClassStoreAndFlush() {
80+
val modifiedAnrEvent = Event(
81+
testException,
82+
config,
83+
SeverityReason.newInstance(SeverityReason.REASON_PROMISE_REJECTION),
84+
NoopLogger
85+
)
86+
modifiedAnrEvent.getErrors().get(0).setErrorClass("ANR")
87+
assertEquals(DeliveryStrategy.STORE_AND_FLUSH, modifiedAnrEvent.getDeliveryStrategy())
88+
}
89+
90+
@Test
91+
fun testGenerateHandledReport() {
92+
val state = SeverityReason.newInstance(
93+
SeverityReason.REASON_HANDLED_EXCEPTION
94+
)
95+
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
96+
event.session = Session(
97+
"123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey
98+
)
99+
assertEquals(DeliveryStrategy.SEND_IMMEDIATELY, event.deliveryStrategy)
100+
}
101+
102+
@Test
103+
fun testDeliveryStrategyStoreAndSend() {
104+
val configuration = Configuration(apiKey)
105+
val newConfig = generateImmutableConfig(configuration).apply {
106+
configuration.setAttemptDeliveryOnCrash(true)
107+
}
108+
val unhandledEvent = Event(
109+
testException, newConfig,
110+
SeverityReason.newInstance(SeverityReason.REASON_UNHANDLED_EXCEPTION),
111+
NoopLogger
112+
)
113+
assertEquals(DeliveryStrategy.STORE_ONLY, unhandledEvent.getDeliveryStrategy())
114+
}
115+
}

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)