Skip to content

Commit

Permalink
Merge 4c77aa0 into 25a5e8b
Browse files Browse the repository at this point in the history
  • Loading branch information
philipphofmann authored May 3, 2023
2 parents 25a5e8b + 4c77aa0 commit 60a6dd8
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 20 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

## Unreleased

- Swift Error Names (#2960)

Instead of only the Swift error name and error code, the SDK now sends the Swift error name for enum-based errors.

```Swift
enum LoginError: Error {
case wrongUser(id: String)
case wrongPassword
}

SentrySDK.capture(error: LoginError.wrongUser("12345678"))
```

Capturing the above Swift error will now result in the following error message in Sentry: `wrongUser(id: "12345678") (Code: 1)` instead of only `(Code: 1)`.
[Customized error descriptions](https://docs.sentry.io/platforms/apple/usage/#customizing-error-descriptions) have precedence over this feature.
This change has no impact on grouping of the issues in Sentry.

### Fixes

- Propagate span when copying scope (#2952)
Expand Down
17 changes: 0 additions & 17 deletions Samples/iOS-Swift/iOS-Swift/Tools/RandomErrors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,6 @@ enum SampleError: Error {
case awesomeCentaur
}

extension SampleError: CustomNSError {
var errorUserInfo: [String: Any] {
func getDebugDescription() -> String {
switch self {
case SampleError.bestDeveloper:
return "bestDeveloper"
case .happyCustomer:
return "happyCustomer"
case .awesomeCentaur:
return "awesomeCentaur"
}
}

return [NSDebugDescriptionErrorKey: getDebugDescription()]
}
}

class RandomErrorGenerator {

static func generate() throws {
Expand Down
14 changes: 14 additions & 0 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#import "SentrySDK+Private.h"
#import "SentryScope+Private.h"
#import "SentryStacktraceBuilder.h"
#import "SentrySwift.h"
#import "SentryThreadInspector.h"
#import "SentryTraceContext.h"
#import "SentryTracer.h"
Expand Down Expand Up @@ -248,9 +249,22 @@ - (SentryEvent *)buildErrorEvent:(NSError *)error

// If the error has a debug description, use that.
NSString *customExceptionValue = [[error userInfo] valueForKey:NSDebugDescriptionErrorKey];

NSString *swiftErrorDescription = nil;
// SwiftNativeNSError is the subclass of NSError used to represent bridged native Swift errors,
// see
// https://github.com/apple/swift/blob/067e4ec50147728f2cb990dbc7617d66692c1554/stdlib/public/runtime/ErrorObject.mm#L63-L73
NSString *errorClass = NSStringFromClass(error.class);
if ([errorClass containsString:@"SwiftNativeNSError"]) {
swiftErrorDescription = [SwiftDescriptor getSwiftErrorDescription:error];
}

if (customExceptionValue != nil) {
exceptionValue =
[NSString stringWithFormat:@"%@ (Code: %ld)", customExceptionValue, (long)error.code];
} else if (swiftErrorDescription != nil) {
exceptionValue =
[NSString stringWithFormat:@"%@ (Code: %ld)", swiftErrorDescription, (long)error.code];
} else {
exceptionValue = [NSString stringWithFormat:@"Code: %ld", (long)error.code];
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/Swift/SwiftDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ public class SwiftDescriptor: NSObject {
return String(describing: type(of: object))
}

@objc
public static func getSwiftErrorDescription(_ error: Error) -> String? {
return String(describing: error)
}

}
84 changes: 81 additions & 3 deletions Tests/SentryTests/SentryClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ class SentryClientTest: XCTestCase {
eventId.assertIsNotEmpty()
let error = TestError.invalidTest as NSError
assertLastSentEvent { actual in
assertValidErrorEvent(actual, error)
assertValidErrorEvent(actual, error, exceptionValue: "invalidTest (Code: 0)")
}
}

Expand Down Expand Up @@ -456,6 +456,62 @@ class SentryClientTest: XCTestCase {
}
}
}

func testCaptureSwiftError_UsesSwiftStringDescription() {
let eventId = fixture.getSut().capture(error: SentryClientError.someError)

eventId.assertIsNotEmpty()
assertLastSentEvent { actual in
do {
let exceptions = try XCTUnwrap(actual.exceptions)
XCTAssertEqual("someError (Code: 1)", try XCTUnwrap(exceptions.first).value)
} catch {
XCTFail("Exception expected but was nil")
}
}
}

func testCaptureSwiftErrorStruct_UsesSwiftStringDescription() {
let eventId = fixture.getSut().capture(error: XMLParsingError(line: 10, column: 12, kind: .internalError))

eventId.assertIsNotEmpty()
assertLastSentEvent { actual in
do {
let exceptions = try XCTUnwrap(actual.exceptions)
XCTAssertEqual("XMLParsingError(line: 10, column: 12, kind: SentryTests.XMLParsingError.ErrorKind.internalError) (Code: 1)", try XCTUnwrap(exceptions.first).value)
} catch {
XCTFail("Exception expected but was nil")
}
}
}

func testCaptureSwiftErrorWithData_UsesSwiftStringDescription() {
let eventId = fixture.getSut().capture(error: SentryClientError.invalidInput("hello"))

eventId.assertIsNotEmpty()
assertLastSentEvent { actual in
do {
let exceptions = try XCTUnwrap(actual.exceptions)
XCTAssertEqual("invalidInput(\"hello\") (Code: 0)", try XCTUnwrap(exceptions.first).value)
} catch {
XCTFail("Exception expected but was nil")
}
}
}

func testCaptureSwiftErrorWithDebugDescription_UsesDebugDescription() {
let eventId = fixture.getSut().capture(error: SentryClientErrorWithDebugDescription.someError)

eventId.assertIsNotEmpty()
assertLastSentEvent { actual in
do {
let exceptions = try XCTUnwrap(actual.exceptions)
XCTAssertEqual("anotherError (Code: 0)", try XCTUnwrap(exceptions.first).value)
} catch {
XCTFail("Exception expected but was nil")
}
}
}

func testCaptureErrorWithComplexUserInfo() {
let url = URL(string: "https://github.com/getsentry")!
Expand Down Expand Up @@ -1458,7 +1514,7 @@ class SentryClientTest: XCTestCase {
}
}

private func assertValidErrorEvent(_ event: Event, _ error: NSError) {
private func assertValidErrorEvent(_ event: Event, _ error: NSError, exceptionValue: String? = nil) {
XCTAssertEqual(SentryLevel.error, event.level)
XCTAssertEqual(error, event.error as NSError?)

Expand All @@ -1469,7 +1525,7 @@ class SentryClientTest: XCTestCase {
let exception = exceptions[0]
XCTAssertEqual(error.domain, exception.type)

XCTAssertEqual("Code: \(error.code)", exception.value)
XCTAssertEqual(exceptionValue ?? "Code: \(error.code)", exception.value)

XCTAssertNil(exception.threadId)
XCTAssertNil(exception.stacktrace)
Expand Down Expand Up @@ -1559,4 +1615,26 @@ class SentryClientTest: XCTestCase {

}

enum SentryClientError: Error {
case someError
case invalidInput(String)
}

enum SentryClientErrorWithDebugDescription: Error {
case someError
}

extension SentryClientErrorWithDebugDescription: CustomNSError {
var errorUserInfo: [String: Any] {
func getDebugDescription() -> String {
switch self {
case .someError:
return "anotherError"
}
}

return [NSDebugDescriptionErrorKey: getDebugDescription()]
}
}

// swiftlint:enable file_length
41 changes: 41 additions & 0 deletions Tests/SentryTests/SwiftDescriptorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,48 @@ class SwiftDescriptorTests: XCTestCase {
XCTAssertEqual(name, "InnerClass")
}

func testGetSwiftErrorDescription_EnumValue() {
let actual = SwiftDescriptor.getSwiftErrorDescription(LoginError.wrongPassword)
XCTAssertEqual("wrongPassword", actual)
}

func testGetSwiftErrorDescription_EnumValueWithData() {
let actual = SwiftDescriptor.getSwiftErrorDescription(LoginError.wrongUser(name: "Max"))
XCTAssertEqual("wrongUser(name: \"Max\")", actual)
}

func testGetSwiftErrorDescription_StructWithData() {
let actual = SwiftDescriptor.getSwiftErrorDescription(XMLParsingError(line: 10, column: 12, kind: .internalError))
XCTAssertEqual("XMLParsingError(line: 10, column: 12, kind: SentryTests.XMLParsingError.ErrorKind.internalError)", actual)
}

func testGetSwiftErrorDescription_StructWithOneParam() {
let actual = SwiftDescriptor.getSwiftErrorDescription(StructWithOneParam(line: 10))
XCTAssertEqual("StructWithOneParam(line: 10)", actual)
}

private func sanitize(_ name: AnyObject) -> String {
return SwiftDescriptor.getObjectClassName(name)
}
}

enum LoginError: Error {
case wrongUser(name: String)
case wrongPassword
}

struct XMLParsingError: Error {
enum ErrorKind {
case invalidCharacter
case mismatchedTag
case internalError
}

let line: Int
let column: Int
let kind: ErrorKind
}

struct StructWithOneParam: Error {
let line: Int
}

0 comments on commit 60a6dd8

Please sign in to comment.