Skip to content

Commit

Permalink
feat: HTTP Crumb level based on response status
Browse files Browse the repository at this point in the history
Set the HTTP crumb level based on the response status code. 4xx is
warning and 5xx is error.

Fixes GH-4776
  • Loading branch information
philipphofmann committed Jan 30, 2025
1 parent e99d20e commit 92c91a4
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Add protocol for custom screenName for UIViewControllers (#4646)
- Allow hybrid SDK to set replay options tags information (#4710)
- Add threshold to always log fatal logs (#4707)
- HTTP Breadcrumb level based on response status code (#4779) 4xx is warning, 5xx is error.

### Internal

Expand Down
24 changes: 22 additions & 2 deletions Sources/Sentry/SentryNetworkTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"];

Expand All @@ -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;
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 92c91a4

Please sign in to comment.