Skip to content

Commit abe1683

Browse files
committed
feat(event) implement ErrorOptions and CaptureOptions class
1 parent ee680d2 commit abe1683

File tree

7 files changed

+250
-31
lines changed

7 files changed

+250
-31
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public final class com/bugsnag/android/Bugsnag {
8282
public static fun leaveBreadcrumb (Ljava/lang/String;Ljava/util/Map;Lcom/bugsnag/android/BreadcrumbType;)V
8383
public static fun markLaunchCompleted ()V
8484
public static fun notify (Ljava/lang/Throwable;)V
85+
public static fun notify (Ljava/lang/Throwable;Lcom/bugsnag/android/ErrorOptions;Lcom/bugsnag/android/OnErrorCallback;)V
8586
public static fun notify (Ljava/lang/Throwable;Lcom/bugsnag/android/OnErrorCallback;)V
8687
public static fun pauseSession ()V
8788
public static fun removeOnBreadcrumb (Lcom/bugsnag/android/OnBreadcrumbCallback;)V
@@ -111,6 +112,22 @@ public class com/bugsnag/android/BugsnagVmViolationListener : android/os/StrictM
111112
public fun onVmViolation (Landroid/os/strictmode/Violation;)V
112113
}
113114

115+
public final class com/bugsnag/android/CaptureOptions {
116+
public fun <init> ()V
117+
public final fun getBreadcrumbs ()Z
118+
public final fun getFeatureFlags ()Z
119+
public final fun getMetadata ()Ljava/util/Set;
120+
public final fun getStacktrace ()Z
121+
public final fun getThreads ()Z
122+
public final fun getUser ()Z
123+
public final fun setBreadcrumbs (Z)V
124+
public final fun setFeatureFlags (Z)V
125+
public final fun setMetadata (Ljava/util/Set;)V
126+
public final fun setStacktrace (Z)V
127+
public final fun setThreads (Z)V
128+
public final fun setUser (Z)V
129+
}
130+
114131
public class com/bugsnag/android/Client : com/bugsnag/android/CallbackAware, com/bugsnag/android/FeatureFlagAware, com/bugsnag/android/MetadataAware, com/bugsnag/android/UserAware {
115132
public fun <init> (Landroid/content/Context;)V
116133
public fun <init> (Landroid/content/Context;Lcom/bugsnag/android/Configuration;)V
@@ -139,6 +156,7 @@ public class com/bugsnag/android/Client : com/bugsnag/android/CallbackAware, com
139156
public fun leaveBreadcrumb (Ljava/lang/String;Ljava/util/Map;Lcom/bugsnag/android/BreadcrumbType;)V
140157
public fun markLaunchCompleted ()V
141158
public fun notify (Ljava/lang/Throwable;)V
159+
public fun notify (Ljava/lang/Throwable;Lcom/bugsnag/android/ErrorOptions;Lcom/bugsnag/android/OnErrorCallback;)V
142160
public fun notify (Ljava/lang/Throwable;Lcom/bugsnag/android/OnErrorCallback;)V
143161
public fun pauseSession ()V
144162
public fun removeOnBreadcrumb (Lcom/bugsnag/android/OnBreadcrumbCallback;)V
@@ -355,6 +373,14 @@ public class com/bugsnag/android/Error : com/bugsnag/android/JsonStream$Streamab
355373
public fun toStream (Lcom/bugsnag/android/JsonStream;)V
356374
}
357375

376+
public final class com/bugsnag/android/ErrorOptions {
377+
public fun <init> ()V
378+
public fun <init> (Lcom/bugsnag/android/CaptureOptions;)V
379+
public synthetic fun <init> (Lcom/bugsnag/android/CaptureOptions;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
380+
public final fun getCapture ()Lcom/bugsnag/android/CaptureOptions;
381+
public final fun setCapture (Lcom/bugsnag/android/CaptureOptions;)V
382+
}
383+
358384
public final class com/bugsnag/android/ErrorType : java/lang/Enum {
359385
public static final field ANDROID Lcom/bugsnag/android/ErrorType;
360386
public static final field C Lcom/bugsnag/android/ErrorType;

bugsnag-android-core/detekt-baseline.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<ID>CyclomaticComplexMethod:ConfigInternal.kt$ConfigInternal$fun getConfigDifferences(): Map&lt;String, Any></ID>
88
<ID>CyclomaticComplexMethod:JsonCollectionParser.kt$JsonCollectionParser$private fun parseNumber(): Number</ID>
99
<ID>CyclomaticComplexMethod:JsonCollectionParser.kt$JsonCollectionParser$private fun readUtf8Char(): Int</ID>
10+
<ID>EmptyDefaultConstructor:ErrorOptions.kt$CaptureOptions$()</ID>
1011
<ID>ImplicitDefaultLocale:Deliverable.kt$Deliverable$String.format("%02x", byte)</ID>
1112
<ID>LongMethod:EventSerializationTest.kt$EventSerializationTest.Companion$@JvmStatic @Parameters fun testCases(): Collection&lt;Pair&lt;Event, String>></ID>
1213
<ID>LongMethod:EventSerializationTest.kt$private fun createMetadataStressTest(event: Event)</ID>
@@ -22,6 +23,7 @@
2223
<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>
2324
<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>
2425
<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>
26+
<ID>LongParameterList:EventInternal.kt$EventInternal$( originalError: Throwable? = null, config: ImmutableConfig, severityReason: SeverityReason, data: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), captureStacktrace: Boolean = true, captureThreads: Boolean = true )</ID>
2527
<ID>LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState )</ID>
2628
<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>
2729
<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>
@@ -64,7 +66,6 @@
6466
<ID>MagicNumber:JsonHelper.kt$JsonHelper$8</ID>
6567
<ID>MagicNumber:JsonStream.kt$JsonStream$128</ID>
6668
<ID>MagicNumber:JsonStream.kt$JsonStream$32</ID>
67-
<ID>MagicNumber:JsonStream.kt$JsonStream.Companion$128</ID>
6869
<ID>MagicNumber:LastRunInfoStore.kt$LastRunInfoStore$3</ID>
6970
<ID>MagicNumber:SessionStore.kt$SessionStore$60</ID>
7071
<ID>MaxLineLength:LastRunInfo.kt$LastRunInfo$return "LastRunInfo(consecutiveLaunchCrashes=$consecutiveLaunchCrashes, crashed=$crashed, crashedDuringLaunch=$crashedDuringLaunch)"</ID>

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,20 @@ public static void notify(@NonNull final Throwable exception,
255255
getClient().notify(exception, onError);
256256
}
257257

258+
/**
259+
* Notify Bugsnag of a handled exception
260+
*
261+
* @param exception the exception to send to Bugsnag
262+
* @param onError callback invoked on the generated error report for
263+
* additional modification
264+
* @param options the error options
265+
*/
266+
public static void notify(@NonNull final Throwable exception,
267+
@Nullable final ErrorOptions options,
268+
@Nullable final OnErrorCallback onError) {
269+
getClient().notify(exception, options, onError);
270+
}
271+
258272
/**
259273
* Adds a map of multiple metadata key-value pairs to the specified section.
260274
*/

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

Lines changed: 138 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -396,21 +396,21 @@ public Unit invoke(String oldOrientation, String newOrientation) {
396396
}
397397
}, new Function2<Boolean, Integer, Unit>() {
398398
@Override
399-
public Unit invoke(Boolean isLowMemory, Integer memoryTrimLevel) {
399+
public Unit invoke(Boolean isLowMemory, Integer memoryTrimLevel) {
400400
memoryTrimState.setLowMemory(Boolean.TRUE.equals(isLowMemory));
401401
if (memoryTrimState.updateMemoryTrimLevel(memoryTrimLevel)) {
402402
leaveAutoBreadcrumb(
403-
"Trim Memory",
404-
BreadcrumbType.STATE,
405-
Collections.<String, Object>singletonMap(
406-
"trimLevel", memoryTrimState.getTrimLevelDescription()
407-
)
403+
"Trim Memory",
404+
BreadcrumbType.STATE,
405+
Collections.<String, Object>singletonMap(
406+
"trimLevel", memoryTrimState.getTrimLevelDescription()
407+
)
408408
);
409409
}
410410

411411
memoryTrimState.emitObservableEvent();
412412
return null;
413-
}
413+
}
414414
}
415415
));
416416
}
@@ -745,7 +745,7 @@ public void removeOnSession(@NonNull OnSessionCallback onSession) {
745745
* @param exception the exception to send to Bugsnag
746746
*/
747747
public void notify(@NonNull Throwable exception) {
748-
notify(exception, null);
748+
notify(exception, null, null);
749749
}
750750

751751
/**
@@ -756,22 +756,68 @@ public void notify(@NonNull Throwable exception) {
756756
* additional modification
757757
*/
758758
public void notify(@NonNull Throwable exc, @Nullable OnErrorCallback onError) {
759+
notify(exc, null, onError);
760+
}
761+
762+
/**
763+
* Notify Bugsnag of a handled exception
764+
*
765+
* @param exc the exception to send to Bugsnag
766+
* @param onError callback invoked on the generated error report for
767+
* additional modification
768+
* @param options the error options
769+
*/
770+
public void notify(
771+
@NonNull Throwable exc,
772+
@Nullable ErrorOptions options,
773+
@Nullable OnErrorCallback onError
774+
) {
759775
if (exc != null) {
760776
if (immutableConfig.shouldDiscardError(exc)) {
761777
return;
762778
}
763779
SeverityReason severityReason = SeverityReason.newInstance(REASON_HANDLED_EXCEPTION);
764-
Metadata metadata = metadataState.getMetadata();
765-
FeatureFlags featureFlags = featureFlagState.getFeatureFlags();
766-
Event event = new Event(exc, immutableConfig, severityReason, metadata, featureFlags,
767-
logger);
780+
Event event = createEventWithOptions(exc, severityReason, options);
768781
event.setGroupingDiscriminator(getGroupingDiscriminator());
769782
populateAndNotifyAndroidEvent(event, onError);
770783
} else {
771784
logNull("notify");
772785
}
773786
}
774787

788+
private Event createEventWithOptions(
789+
@NonNull Throwable exc,
790+
@NonNull SeverityReason severityReason,
791+
@NonNull ErrorOptions options
792+
) {
793+
if (options == null) {
794+
return new Event(
795+
exc,
796+
immutableConfig,
797+
severityReason,
798+
metadataState.getMetadata(),
799+
featureFlagState.getFeatureFlags(),
800+
logger
801+
);
802+
}
803+
final CaptureOptions capture =
804+
options.getCapture() != null ? options.getCapture() : new CaptureOptions();
805+
final Metadata metadata = new Metadata();
806+
final FeatureFlags featureFlags = capture.getFeatureFlags()
807+
? featureFlagState.getFeatureFlags()
808+
: new FeatureFlags();
809+
return new Event(
810+
exc,
811+
immutableConfig,
812+
severityReason,
813+
metadata,
814+
featureFlags,
815+
capture.getStacktrace(),
816+
capture.getThreads(),
817+
logger
818+
);
819+
}
820+
775821
/**
776822
* Caches an error then attempts to notify.
777823
*
@@ -786,7 +832,7 @@ void notifyUnhandledException(@NonNull Throwable exc, Metadata metadata,
786832
Event event = new Event(exc, immutableConfig, handledState,
787833
data, featureFlagState.getFeatureFlags(), logger);
788834
event.setGroupingDiscriminator(getGroupingDiscriminator());
789-
populateAndNotifyAndroidEvent(event, null);
835+
populateAndNotifyAndroidEvent(event, null, null);
790836

791837
// persist LastRunInfo so that on relaunch users can check the app crashed
792838
int consecutiveLaunchCrashes = lastRunInfo == null ? 0
@@ -803,31 +849,99 @@ void notifyUnhandledException(@NonNull Throwable exc, Metadata metadata,
803849
bgTaskService.shutdown();
804850
}
805851

852+
// Keep the 2-arg overload (compat)
806853
void populateAndNotifyAndroidEvent(@NonNull Event event,
807854
@Nullable OnErrorCallback onError) {
808-
// Capture the state of the app and device and attach diagnostics to the event
855+
// Default capture when caller didn't pass options on this overload
856+
populateAndNotifyAndroidEvent(event, onError, null);
857+
}
858+
859+
void populateAndNotifyAndroidEvent(@NonNull Event event,
860+
@Nullable OnErrorCallback onError,
861+
@Nullable ErrorOptions options) {
862+
// Always-captured models and metadata tabs
863+
populateDeviceAndAppData(event);
864+
865+
if (options == null) {
866+
populateAllEventData(event);
867+
} else {
868+
populateConditionalEventData(event, options);
869+
}
870+
871+
// Always-captured context and internals
872+
event.setContext(contextState.getContext());
873+
event.setInternalMetrics(internalMetrics);
874+
event.setGroupingDiscriminator(getGroupingDiscriminator());
875+
876+
notifyInternal(event, onError);
877+
}
878+
879+
private void populateDeviceAndAppData(@NonNull Event event) {
809880
event.setDevice(deviceDataCollector.generateDeviceWithState(new Date().getTime()));
810881
event.addMetadata("device", deviceDataCollector.getDeviceMetadata());
811-
812-
// add additional info that belongs in metadata
813-
// generate new object each time, as this can be mutated by end-users
814882
event.setApp(appDataCollector.generateAppWithState());
815883
event.addMetadata("app", appDataCollector.getAppDataMetadata());
884+
}
816885

817-
// Attach breadcrumbState to the event
886+
private void populateAllEventData(@NonNull Event event) {
818887
event.setBreadcrumbs(breadcrumbState.copy());
819888

820-
// Attach user info to the event
821889
User user = userState.get().getUser();
822890
event.setUser(user.getId(), user.getEmail(), user.getName());
891+
}
823892

824-
// Attach context to the event
825-
event.setContext(contextState.getContext());
893+
private void populateConditionalEventData(@NonNull Event event, @NonNull ErrorOptions options) {
894+
final CaptureOptions capture =
895+
options.getCapture() != null ? options.getCapture() : new CaptureOptions();
826896

827-
event.setInternalMetrics(internalMetrics);
828-
event.setGroupingDiscriminator(getGroupingDiscriminator());
897+
if (capture.getBreadcrumbs()) {
898+
event.setBreadcrumbs(breadcrumbState.copy());
899+
}
829900

830-
notifyInternal(event, onError);
901+
if (capture.getUser()) {
902+
User user = userState.get().getUser();
903+
event.setUser(user.getId(), user.getEmail(), user.getName());
904+
}
905+
906+
copySelectedMetadataTabs(event, capture.getMetadata());
907+
}
908+
909+
910+
/**
911+
* Copies only the selected metadata tabs from state into the event.
912+
*/
913+
private void copySelectedMetadataTabs(@NonNull Event event, @Nullable Set<String> allow) {
914+
// Take a snapshot of all current tabs (excluding app/device which we already added)
915+
Map<String, Map<String, Object>> all = snapshotAllMetadataTabsExcludingAppDevice();
916+
if (allow == null) {
917+
// null => copy ALL tabs
918+
for (Map.Entry<String, Map<String, Object>> e : all.entrySet()) {
919+
event.addMetadata(e.getKey(), e.getValue());
920+
}
921+
return;
922+
}
923+
if (allow.isEmpty()) {
924+
// empty set => only app/device (already present) -> copy nothing else
925+
return;
926+
}
927+
// whitelist -> copy only those
928+
for (String tab : allow) {
929+
Object value = all.get(tab);
930+
if (value != null) {
931+
event.addMetadata("", tab, value);
932+
}
933+
}
934+
}
935+
936+
/**
937+
* Returns a shallow snapshot of all metadata tabs except "app" and "device".
938+
* Implement using existing MetadataState APIs; if none exist, add a safe accessor there.
939+
*/
940+
private Map<String, Map<String, Object>> snapshotAllMetadataTabsExcludingAppDevice() {
941+
Map<String, Map<String, Object>> snapshot = metadataState.getMetadata().toMap();
942+
snapshot.remove("app");
943+
snapshot.remove("device");
944+
return snapshot;
831945
}
832946

833947
void notifyInternal(@NonNull Event event,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// ErrorOptions.kt
2+
package com.bugsnag.android
3+
4+
/**
5+
* Options for controlling how handled errors are reported.
6+
*/
7+
class ErrorOptions @JvmOverloads constructor(
8+
/** Controls which data fields are captured during Event creation. */
9+
var capture: CaptureOptions = CaptureOptions()
10+
)
11+
12+
/**
13+
* Granular flags for controlling data capture at notify time.
14+
* Java-friendly: mutable fields with default values and a no-arg constructor.
15+
*/
16+
class CaptureOptions() {
17+
/** Whether to capture breadcrumbs. Defaults to true. */
18+
var breadcrumbs: Boolean = true
19+
20+
/** Whether to capture feature flags. Defaults to true. */
21+
var featureFlags: Boolean = true
22+
23+
/**
24+
* Controls which custom metadata tabs are included.
25+
* - null: all metadata tabs captured
26+
* - empty set: only app and device tabs captured
27+
* - set of names: app, device, and specified tabs captured
28+
*
29+
* Note: app and device tabs are always captured.
30+
*/
31+
var metadata: Set<String>? = null
32+
33+
/** Whether to capture stacktrace. Defaults to true. */
34+
var stacktrace: Boolean = true
35+
36+
/** Whether to capture thread state. Defaults to true. */
37+
var threads: Boolean = true
38+
39+
/** Whether to capture user information. Defaults to true. */
40+
var user: Boolean = true
41+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware, F
4343
logger);
4444
}
4545

46+
Event(@Nullable Throwable originalError,
47+
@NonNull ImmutableConfig config,
48+
@NonNull SeverityReason severityReason,
49+
@NonNull Metadata metadata,
50+
@NonNull FeatureFlags featureFlags,
51+
boolean captureStacktrace,
52+
boolean captureThreads,
53+
@NonNull Logger logger) {
54+
this(new EventInternal(originalError, config, severityReason, metadata, featureFlags,
55+
captureStacktrace, captureThreads),
56+
logger);
57+
}
58+
4659
Event(@NonNull EventInternal impl, @NonNull Logger logger) {
4760
this.impl = impl;
4861
this.logger = logger;

0 commit comments

Comments
 (0)