diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b532c296f..15c53b989f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- HTTP Breadcrumb level based on response status code (#4779) 4xx is warning, 5xx is error. + ### Improvements - Add more debug logs for SentryViewHierarchy (#4780) diff --git a/Sources/Sentry/SentryNetworkTracker.m b/Sources/Sentry/SentryNetworkTracker.m index e3e99bff72..b3cc2515f7 100644 --- a/Sources/Sentry/SentryNetworkTracker.m +++ b/Sources/Sentry/SentryNetworkTracker.m @@ -492,7 +492,10 @@ - (void)addBreadcrumbForSessionTask:(NSURLSessionTask *)sessionTask NSDate *requestStart = objc_getAssociatedObject(sessionTask, &SENTRY_NETWORK_REQUEST_START_DATE); - SentryLevel breadcrumbLevel = sessionTask.error != nil ? kSentryLevelError : kSentryLevelInfo; + NSInteger responseStatusCode = [self urlResponseStatusCode:sessionTask.response]; + SentryLevel breadcrumbLevel = [self getBreadcrumbLevel:sessionTask + responseStatusCode:responseStatusCode]; + SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] initWithLevel:breadcrumbLevel category:@"http"]; @@ -507,7 +510,7 @@ - (void)addBreadcrumbForSessionTask:(NSURLSessionTask *)sessionTask [NSNumber numberWithLongLong:sessionTask.countOfBytesSent]; breadcrumbData[@"response_body_size"] = [NSNumber numberWithLongLong:sessionTask.countOfBytesReceived]; - NSInteger responseStatusCode = [self urlResponseStatusCode:sessionTask.response]; + if (responseStatusCode != -1) { NSNumber *statusCode = [NSNumber numberWithInteger:responseStatusCode]; breadcrumbData[@"status_code"] = statusCode; @@ -601,4 +604,21 @@ - (SentrySpanStatus)spanStatusForHttpResponseStatusCode:(NSInteger)statusCode return kSentrySpanStatusUndefined; } +- (SentryLevel)getBreadcrumbLevel:(NSURLSessionTask *)sessionTask + responseStatusCode:(NSInteger)responseStatusCode +{ + SentryLevel breadcrumbLevel = kSentryLevelInfo; + if (responseStatusCode >= 400 && responseStatusCode < 500) { + breadcrumbLevel = kSentryLevelWarning; + } else if (responseStatusCode >= 500 && responseStatusCode < 600) { + breadcrumbLevel = kSentryLevelError; + } + + if (sessionTask.error != nil) { + breadcrumbLevel = kSentryLevelError; + } + + return breadcrumbLevel; +} + @end diff --git a/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift index 5413680487..7b0f50a899 100644 --- a/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift @@ -578,6 +578,147 @@ class SentryNetworkTrackerTests: XCTestCase { let breadcrumbs = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? XCTAssertEqual(breadcrumbs?.count, 0) } + + func test_Breadcrumb_HTTP400_HasLevelWarning() throws { + // Arrange + fixture.options.enableAutoPerformanceTracing = false + + let task = createDataTask() + task.setResponse(createResponse(code: 400)) + let _ = spanForTask(task: task)! + + //Act + try setTaskState(task, state: .completed) + + //Assert + let breadcrumbsDynamic = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? + let breadcrumbs = try XCTUnwrap(breadcrumbsDynamic) + XCTAssertEqual(breadcrumbs.count, 1) + let breadcrumb = try XCTUnwrap(breadcrumbs.first) + + XCTAssertEqual(breadcrumb.category, "http") + XCTAssertEqual(breadcrumb.level, .warning) + XCTAssertEqual(breadcrumb.type, "http") + + let data = try XCTUnwrap(breadcrumb.data) + XCTAssertEqual(SentryNetworkTrackerTests.testUrl, data["url"] as? String) + XCTAssertEqual("GET", data["method"] as? String) + XCTAssertEqual(400, data["status_code"] as? Int) + XCTAssertEqual("bad request", data["reason"] as? String) + } + + func test_Breadcrumb_HTTP499_HasLevelWarning() throws { + // Arrange + fixture.options.enableAutoPerformanceTracing = false + + let task = createDataTask() + task.setResponse(createResponse(code: 499)) + let _ = spanForTask(task: task)! + + //Act + try setTaskState(task, state: .completed) + + //Assert + let breadcrumbsDynamic = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? + let breadcrumbs = try XCTUnwrap(breadcrumbsDynamic) + XCTAssertEqual(breadcrumbs.count, 1) + let breadcrumb = try XCTUnwrap(breadcrumbs.first) + + XCTAssertEqual(breadcrumb.category, "http") + XCTAssertEqual(breadcrumb.level, .warning) + XCTAssertEqual(breadcrumb.type, "http") + + let data = try XCTUnwrap(breadcrumb.data) + XCTAssertEqual(SentryNetworkTrackerTests.testUrl, data["url"] as? String) + XCTAssertEqual("GET", data["method"] as? String) + XCTAssertEqual(499, data["status_code"] as? Int) + XCTAssertEqual("client error", data["reason"] as? String) + } + + func testBreadcrumb_SessionTaskError_HTTP400_HasLevelError() throws { + // Arrange + fixture.options.enableAutoPerformanceTracing = false + + let task = createDataTask() + task.setResponse(createResponse(code: 400)) + task.setError(NSError(domain: "Some Error", code: 1, userInfo: nil)) + let _ = spanForTask(task: task)! + + //Act + try setTaskState(task, state: .completed) + + //Assert + let breadcrumbsDynamic = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? + let breadcrumbs = try XCTUnwrap(breadcrumbsDynamic) + XCTAssertEqual(breadcrumbs.count, 1) + let breadcrumb = try XCTUnwrap(breadcrumbs.first) + + XCTAssertEqual(breadcrumb.category, "http") + XCTAssertEqual(breadcrumb.level, .error) + XCTAssertEqual(breadcrumb.type, "http") + + let data = try XCTUnwrap(breadcrumb.data) + XCTAssertEqual(SentryNetworkTrackerTests.testUrl, data["url"] as? String) + XCTAssertEqual("GET", data["method"] as? String) + XCTAssertEqual(400, data["status_code"] as? Int) + XCTAssertEqual("bad request", data["reason"] as? String) + } + + func test_Breadcrumb_HTTP500_HasLevelError() throws { + // Arrange + fixture.options.enableAutoPerformanceTracing = false + + let task = createDataTask() + task.setResponse(createResponse(code: 500)) + let _ = spanForTask(task: task)! + + //Act + try setTaskState(task, state: .completed) + + //Assert + let breadcrumbsDynamic = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? + let breadcrumbs = try XCTUnwrap(breadcrumbsDynamic) + XCTAssertEqual(breadcrumbs.count, 1) + let breadcrumb = try XCTUnwrap(breadcrumbs.first) + + XCTAssertEqual(breadcrumb.category, "http") + XCTAssertEqual(breadcrumb.level, .error) + XCTAssertEqual(breadcrumb.type, "http") + + let data = try XCTUnwrap(breadcrumb.data) + XCTAssertEqual(SentryNetworkTrackerTests.testUrl, data["url"] as? String) + XCTAssertEqual("GET", data["method"] as? String) + XCTAssertEqual(500, data["status_code"] as? Int) + XCTAssertEqual("internal server error", data["reason"] as? String) + } + + func test_Breadcrumb_HTTP599_HasLevelError() throws { + // Arrange + fixture.options.enableAutoPerformanceTracing = false + + let task = createDataTask() + task.setResponse(createResponse(code: 599)) + let _ = spanForTask(task: task)! + + //Act + try setTaskState(task, state: .completed) + + //Assert + let breadcrumbsDynamic = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? + let breadcrumbs = try XCTUnwrap(breadcrumbsDynamic) + XCTAssertEqual(breadcrumbs.count, 1) + let breadcrumb = try XCTUnwrap(breadcrumbs.first) + + XCTAssertEqual(breadcrumb.category, "http") + XCTAssertEqual(breadcrumb.level, .error) + XCTAssertEqual(breadcrumb.type, "http") + + let data = try XCTUnwrap(breadcrumb.data) + XCTAssertEqual(SentryNetworkTrackerTests.testUrl, data["url"] as? String) + XCTAssertEqual("GET", data["method"] as? String) + XCTAssertEqual(599, data["status_code"] as? Int) + XCTAssertEqual("server error", data["reason"] as? String) + } func testResumeAfterCompleted_OnlyOneSpanCreated() throws { let task = createDataTask()