Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add underlying error info to reported NSErrors #3230

Merged
merged 10 commits into from
Oct 23, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Enrich error events with any underlying NSErrors reported by Cocoa APIs (#3230)

### Fixes

- Missing `mechanism.handled` is not considered crash (#3353)
Expand Down
6 changes: 3 additions & 3 deletions Sources/Sentry/Public/SentryEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ NS_SWIFT_NAME(Event)
/**
* The error of the event. This property adds convenience to access the error directly in
* @c beforeSend. This property is not serialized. Instead when preparing the event the
* @c SentryClient puts the error into exceptions.
* @c SentryClient puts the error and any underlying errors into exceptions.
*/
@property (nonatomic, copy) NSError *_Nullable error;

Expand Down Expand Up @@ -138,8 +138,8 @@ NS_SWIFT_NAME(Event)
@property (nonatomic, strong) NSArray<SentryThread *> *_Nullable threads;

/**
* General information about the @c SentryException, usually there is only one
* exception in the array.
* General information about the @c SentryException. Multiple exceptions indicate a chain of
* exceptions encountered, starting with the oldest at the beginning of the array.
*/
@property (nonatomic, strong) NSArray<SentryException *> *_Nullable exceptions;

Expand Down
34 changes: 29 additions & 5 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,34 @@ - (SentryEvent *)buildErrorEvent:(NSError *)error
{
SentryEvent *event = [[SentryEvent alloc] initWithError:error];

// flatten any recursive description of underlying errors into a list, to ultimately report them
// as a list of exceptions with error mechanisms, sorted oldest to newest (so, the leaf node
// underlying error as oldest, with the root as the newest)
NSMutableArray<NSError *> *errors = [NSMutableArray<NSError *> arrayWithObject:error];
NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
while (underlyingError != nil) {
[errors addObject:underlyingError];
underlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
}

NSMutableArray<SentryException *> *exceptions = [NSMutableArray<SentryException *> array];
[errors enumerateObjectsWithOptions:NSEnumerationReverse
usingBlock:^(NSError *_Nonnull nextError, NSUInteger __unused idx,
BOOL *_Nonnull __unused stop) {
[exceptions addObject:[self exceptionForError:nextError]];
}];

event.exceptions = exceptions;

// Once the UI displays the mechanism data we can the userInfo from the event.context using only
// the root error's userInfo.
[self setUserInfo:[error.userInfo sentry_sanitize] withEvent:event];

return event;
}

- (SentryException *)exceptionForError:(NSError *)error
{
NSString *exceptionValue;

// If the error has a debug description, use that.
Expand Down Expand Up @@ -274,12 +302,8 @@ - (SentryEvent *)buildErrorEvent:(NSError *)error
NSDictionary<NSString *, id> *userInfo = [error.userInfo sentry_sanitize];
mechanism.data = userInfo;
exception.mechanism = mechanism;
event.exceptions = @[ exception ];

// Once the UI displays the mechanism data we can the userInfo from the event.context.
[self setUserInfo:userInfo withEvent:event];

return event;
return exception;
}

- (SentryId *)captureCrashEvent:(SentryEvent *)event withScope:(SentryScope *)scope
Expand Down
5 changes: 3 additions & 2 deletions Tests/SentryTests/Protocol/SentryMechanismMetaTests.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import SentryTestUtils
import XCTest

class SentryMechanismMetaTests: XCTestCase {
Expand All @@ -19,8 +20,8 @@ class SentryMechanismMetaTests: XCTestCase {
return
}
let nsError = expected.error! as SentryNSError
XCTAssertEqual(nsError.domain, error["domain"] as? String)
XCTAssertEqual(nsError.code, error["code"] as? Int)
XCTAssertEqual(Dynamic(nsError).domain, error["domain"] as? String)
XCTAssertEqual(Dynamic(nsError).code, error["code"] as? Int)

guard let signal = actual["signal"] as? [String: Any] else {
XCTFail("The serialization doesn't contain signal")
Expand Down
Loading