Skip to content

Commit

Permalink
feat: HTTP Crumb level based on response status (#4779)
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 authored Jan 31, 2025
1 parent ee7b41e commit 3dcba20
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 2 deletions.
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

- HTTP Breadcrumb level based on response status code (#4779) 4xx is warning, 5xx is error.

### Improvements

- Add more debug logs for SentryViewHierarchy (#4780)
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 3dcba20

Please sign in to comment.