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

Add http fields to span.data #1497

Merged
merged 13 commits into from
May 31, 2023
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

### Enhancements

- Add http fields to `span.data` ([#1497](https://github.com/getsentry/sentry-dart/pull/1497))
- Set `http.response.status_code`
- Set `http.response_content_length`

## 7.6.3

### Fixes
Expand Down
2 changes: 2 additions & 0 deletions dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ export 'src/exception_stacktrace_extractor.dart';
export 'src/utils/http_sanitizer.dart';
// ignore: invalid_export_of_internal_element
export 'src/utils/url_details.dart';
// ignore: invalid_export_of_internal_element
export 'src/utils/http_header_utils.dart';
2 changes: 2 additions & 0 deletions dart/lib/src/http_client/tracing_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class TracingClient extends BaseClient {
}

response = await _client.send(request);
span?.setData('http.response.status_code', response.statusCode);
span?.setData('http.response_content_length', response.contentLength);
span?.status = SpanStatus.fromHttpStatusCode(response.statusCode);
} catch (exception) {
span?.throwable = exception;
Expand Down
16 changes: 16 additions & 0 deletions dart/lib/src/utils/http_header_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:meta/meta.dart';

/// Helper to extract header data
@internal
class HttpHeaderUtils {
/// Get `Content-Length` header
static int? getContentLength(Map<String, List<String>> headers) {
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
final contentLengthHeader =
headers['content-length'] ?? headers['Content-Length'];
if (contentLengthHeader != null && contentLengthHeader.isNotEmpty) {
final headerValue = contentLengthHeader.first;
return int.tryParse(headerValue);
}
return null;
}
}
4 changes: 3 additions & 1 deletion dart/test/http_client/tracing_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ void main() {
expect(span.data['url'], 'https://example.com');
expect(span.data['http.query'], 'foo=bar');
expect(span.data['http.fragment'], 'baz');
expect(span.data['http.response.status_code'], 200);
expect(span.data['http.response_content_length'], 2);
});

test('finish span if errored request', () async {
Expand Down Expand Up @@ -225,7 +227,7 @@ class Fixture {
MockClient getClient({int statusCode = 200, String? reason}) {
return MockClient((request) async {
expect(request.url, requestUri);
return Response('', statusCode, reasonPhrase: reason, request: request);
return Response('{}', statusCode, reasonPhrase: reason, request: request);
});
}
}
Expand Down
18 changes: 18 additions & 0 deletions dart/test/utils/http_header_utils_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:sentry/sentry.dart';
import 'package:test/test.dart';

void main() {
test('get content length lower case', () {
final headers = {
'content-length': ['12']
};
expect(HttpHeaderUtils.getContentLength(headers), 12);
});

test('get content length camel case', () {
final headers = {
'Content-Length': ['12']
};
expect(HttpHeaderUtils.getContentLength(headers), 12);
});
}
7 changes: 2 additions & 5 deletions dio/lib/src/breadcrumb_client_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,8 @@ class BreadcrumbClientAdapter implements HttpClientAdapter {

statusCode = response.statusCode;
reason = response.statusMessage;
final contentLengthHeader = response.headers['content-length'];
if (contentLengthHeader != null && contentLengthHeader.isNotEmpty) {
final headerValue = contentLengthHeader.first;
responseBodySize = int.tryParse(headerValue);
}
// ignore: invalid_use_of_internal_member
responseBodySize = HttpHeaderUtils.getContentLength(response.headers);

return response;
} catch (_) {
Expand Down
7 changes: 7 additions & 0 deletions dio/lib/src/tracing_client_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ class TracingClientAdapter implements HttpClientAdapter {

response = await _client.fetch(options, requestStream, cancelFuture);
span?.status = SpanStatus.fromHttpStatusCode(response.statusCode);
span?.setData('http.response.status_code', response.statusCode);
final contentLengthHeader =
// ignore: invalid_use_of_internal_member
HttpHeaderUtils.getContentLength(response.headers);
if (contentLengthHeader != null) {
span?.setData('http.response_content_length', contentLengthHeader);
}
} catch (exception) {
span?.throwable = exception;
span?.status = const SpanStatus.internalError();
Expand Down
28 changes: 21 additions & 7 deletions dio/test/tracing_client_adapter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ void main() {

test('captured span if successful request', () async {
final sut = fixture.getSut(
client: fixture.getClient(statusCode: 200, reason: 'OK'),
client:
fixture.getClient(statusCode: 200, reason: 'OK', contentLength: 2),
);
final tr = fixture._hub.startTransaction(
'name',
Expand All @@ -46,6 +47,8 @@ void main() {
expect(span.data['url'], 'https://example.com');
expect(span.data['http.query'], 'foo=bar');
expect(span.data['http.fragment'], 'baz');
expect(span.data['http.response.status_code'], 200);
expect(span.data['http.response_content_length'], 2);
});

test('finish span if errored request', () async {
Expand Down Expand Up @@ -175,16 +178,27 @@ class Fixture {
return dio;
}

MockHttpClientAdapter getClient({int statusCode = 200, String? reason}) {
MockHttpClientAdapter getClient({
int statusCode = 200,
String? reason,
int? contentLength,
}) {
return MockHttpClientAdapter((options, requestStream, cancelFuture) async {
expect(options.uri, requestUri);

final headers = options.headers.map(
(key, dynamic value) =>
MapEntry(key, <String>[value?.toString() ?? '']),
);

if (contentLength != null) {
headers['Content-Length'] = [contentLength.toString()];
}

return ResponseBody.fromString(
'',
'{}',
statusCode,
headers: options.headers.map(
(key, dynamic value) =>
MapEntry(key, <String>[value?.toString() ?? '']),
),
headers: headers,
);
});
}
Expand Down