bugsnag/bugsnag-js

React Native iOS does not report error causes (or multiple errors from an error array)

getsaf opened this issue · 2 comments

getsaf commented

Describe the bug

package: @bugsnag/react-native
iOS does not report error causes (It works fine in Android).

Root cause is that the iOS event deserializer is hard-coded to only inspect/deserialize the first error that is reported

Additionally, I found that the bugsnag-cocoa pod also hard-codes attachment of stack traces to the first error in the BugsnagEvent#errors array in the attachCustomStacktrace function which is also called in the event deserializer here (this makes it difficult to provide a full solution without also making changes to bugsnag-cocoa or duplicating some logic from bugsnag-cocoa into the bugsnag-js repo).

- (void)attachCustomStacktrace:(NSArray *)frames withType:(NSString *)type {
    BugsnagError *error = self.errors.firstObject;
    error.stacktrace = [BugsnagStacktrace stacktraceFromJson:frames].trace;
    error.typeString = type;
}

Steps to reproduce

  1. Report an error with a cause property assigned
  2. View the report in the Bugsnag web UI
  3. See the cause is not reported

Environment

  • Bugsnag version: 7.20.2
  • Browser framework version (if any):
    • n/a
  • Server framework version (if any):
    • n/a
  • Device (e.g. iphonex):
    • Any iPhone device

Example code snippet

  const cause = new Error('This is the cause');
  const error = new Error('This is the error', {cause});
  bugsnag.notify(error);
getsaf commented

My current solution is to apply this patch to the 7.20.2 version of the @bugsnag/react-native package:

diff --git a/ios/BugsnagReactNative/BugsnagEventDeserializer.m b/ios/BugsnagReactNative/BugsnagEventDeserializer.m
index 280a788672eba80d678e790b427bc32c83c364e3..88a3c66a65dab7b64575982ebfe325863a7a7fa5 100644
--- a/ios/BugsnagReactNative/BugsnagEventDeserializer.m
+++ b/ios/BugsnagReactNative/BugsnagEventDeserializer.m
@@ -9,6 +9,8 @@
 #import "BugsnagEventDeserializer.h"
 
 #import "BugsnagInternals.h"
+#import "BugsnagError+Private.h"
+#import "BugsnagStacktrace.h"
 
 @implementation BugsnagEventDeserializer
 
@@ -45,25 +47,31 @@ - (BugsnagEvent *)deserializeEvent:(NSDictionary *)payload {
         }
     }
 
-    NSDictionary *error = payload[@"errors"][0];
-
-    if (error != nil) {
-        event.errors[0].errorClass = error[@"errorClass"];
-        event.errors[0].errorMessage = error[@"errorMessage"];
-        NSArray<NSDictionary *> *stacktrace = error[@"stacktrace"];
-        NSArray<NSString *> *nativeStack = payload[@"nativeStack"];
-        if (nativeStack) {
-            NSMutableArray<NSDictionary *> *mixedStack = [NSMutableArray array];
-            for (BugsnagStackframe *frame in [BugsnagStackframe stackframesWithCallStackSymbols:nativeStack]) {
-                frame.type = BugsnagStackframeTypeCocoa;
-                [frame symbolicateIfNeeded];
-                [mixedStack addObject:[frame toDictionary]];
+    // Deserialize *all* errors instead of just the first one
+    // See issue: https://github.com/bugsnag/bugsnag-js/issues/1956
+    [payload[@"errors"] enumerateObjectsUsingBlock:^(NSDictionary *error, NSUInteger idx, BOOL *stop) {
+        if (error != nil) {
+            if (event.errors.count < idx + 1) {
+                event.errors = [event.errors arrayByAddingObject:[BugsnagError new]];
             }
-            [mixedStack addObjectsFromArray:stacktrace];
-            stacktrace = mixedStack;
+            event.errors[idx].errorClass = error[@"errorClass"];
+            event.errors[idx].errorMessage = error[@"errorMessage"];
+            NSArray<NSDictionary *> *stacktrace = error[@"stacktrace"];
+            NSArray<NSString *> *nativeStack = payload[@"nativeStack"];
+            if (nativeStack) {
+                NSMutableArray<NSDictionary *> *mixedStack = [NSMutableArray array];
+                for (BugsnagStackframe *frame in [BugsnagStackframe stackframesWithCallStackSymbols:nativeStack]) {
+                    frame.type = BugsnagStackframeTypeCocoa;
+                    [frame symbolicateIfNeeded];
+                    [mixedStack addObject:[frame toDictionary]];
+                }
+                [mixedStack addObjectsFromArray:stacktrace];
+                stacktrace = mixedStack;
+            }
+            event.errors[idx].stacktrace = [BugsnagStacktrace stacktraceFromJson:stacktrace].trace;
+            event.errors[idx].typeString = @"reactnativejs";
         }
-        [event attachCustomStacktrace:stacktrace withType:@"reactnativejs"];
-    }
+    }];
 
     return event;
 }

Hi @getsaf, thanks for raising, we will look to get this fixed.