Skip to content

Commit dad9228

Browse files
authored
Add DioException response data to error breadcrumb (#3164)
1 parent e10be73 commit dad9228

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
- Tag all spans with thread info on non-web platforms ([#3101](https://github.com/getsentry/sentry-dart/pull/3101), [#3144](https://github.com/getsentry/sentry-dart/pull/3144))
99
- feat(feedback): Add option to disable keyboard resize ([#3154](https://github.com/getsentry/sentry-dart/pull/3154))
1010

11+
### Enhancements
12+
13+
- Add `DioException` response data to error breadcrumb ([#3164](https://github.com/getsentry/sentry-dart/pull/3164))
14+
- Bumped `dio` min verion to `5.2.0`
15+
1116
## 9.7.0-beta.1
1217

1318
### Features

packages/dio/lib/src/breadcrumb_client_adapter.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class BreadcrumbClientAdapter implements HttpClientAdapter {
3636
final stopwatch = Stopwatch();
3737
stopwatch.start();
3838

39+
DioException? dioException;
3940
try {
4041
final response =
4142
await _client.fetch(options, requestStream, cancelFuture);
@@ -46,6 +47,10 @@ class BreadcrumbClientAdapter implements HttpClientAdapter {
4647
responseBodySize = HttpHeaderUtils.getContentLength(response.headers);
4748

4849
return response;
50+
} on DioException catch (e) {
51+
requestHadException = true;
52+
dioException = e;
53+
rethrow;
4954
} catch (_) {
5055
requestHadException = true;
5156
rethrow;
@@ -57,8 +62,18 @@ class BreadcrumbClientAdapter implements HttpClientAdapter {
5762
HttpSanitizer.sanitizeUrl(options.uri.toString()) ?? UrlDetails();
5863

5964
SentryLevel? level;
65+
6066
if (requestHadException) {
6167
level = SentryLevel.error;
68+
final dioExceptionResponse = dioException?.response;
69+
if (dioExceptionResponse != null) {
70+
statusCode = dioExceptionResponse.statusCode;
71+
reason = dioExceptionResponse.statusMessage;
72+
// ignore: invalid_use_of_internal_member
73+
responseBodySize = HttpHeaderUtils.getContentLength(
74+
dioExceptionResponse.headers.map,
75+
);
76+
}
6277
} else if (statusCode != null) {
6378
// ignore: invalid_use_of_internal_member
6479
level = getBreadcrumbLogLevelFromHttpStatusCode(statusCode);

packages/dio/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ platforms:
1818
web:
1919

2020
dependencies:
21-
dio: ^5.0.0
21+
dio: ^5.2.0
2222
sentry: 9.7.0-beta.1
2323

2424
dev_dependencies:

packages/dio/test/breadcrumb_client_adapter_test.dart

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// ignore_for_file: deprecated_member_use
22

3+
import 'dart:typed_data';
4+
35
import 'package:dio/dio.dart';
46
import 'package:mockito/mockito.dart';
57
import 'package:sentry/sentry.dart';
@@ -148,6 +150,39 @@ void main() {
148150
expect(breadcrumb.data?['duration'], isNotNull);
149151
});
150152

153+
test('breadcrumb gets added when DioException with response is thrown',
154+
() async {
155+
final sut = fixture.getSut(
156+
DioExceptionWithResponseAdapter(
157+
statusCode: 404,
158+
statusMessage: 'Not Found',
159+
headers: {
160+
'content-length': ['123'],
161+
},
162+
),
163+
);
164+
165+
try {
166+
await sut.get<dynamic>('');
167+
fail('Method did not throw');
168+
} on DioException catch (_) {}
169+
170+
expect(fixture.hub.addBreadcrumbCalls.length, 1);
171+
172+
final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb;
173+
174+
expect(breadcrumb.type, 'http');
175+
expect(breadcrumb.data?['url'], 'https://example.com');
176+
expect(breadcrumb.data?['method'], 'GET');
177+
expect(breadcrumb.data?['http.query'], 'foo=bar');
178+
expect(breadcrumb.data?['http.fragment'], 'baz');
179+
expect(breadcrumb.level, SentryLevel.error);
180+
expect(breadcrumb.data?['duration'], isNotNull);
181+
expect(breadcrumb.data?['status_code'], 404);
182+
expect(breadcrumb.data?['reason'], 'Not Found');
183+
expect(breadcrumb.data?['response_body_size'], 123);
184+
});
185+
151186
test('close does get called for user defined client', () async {
152187
final mockHub = MockHub();
153188

@@ -185,8 +220,45 @@ void main() {
185220

186221
class CloseableMockClientAdapter extends Mock implements HttpClientAdapter {}
187222

223+
class DioExceptionWithResponseAdapter implements HttpClientAdapter {
224+
DioExceptionWithResponseAdapter({
225+
required this.statusCode,
226+
required this.statusMessage,
227+
required this.headers,
228+
});
229+
230+
final int statusCode;
231+
final String statusMessage;
232+
final Map<String, List<String>> headers;
233+
234+
@override
235+
Future<ResponseBody> fetch(
236+
RequestOptions options,
237+
Stream<Uint8List>? requestStream,
238+
Future<dynamic>? cancelFuture,
239+
) async {
240+
final response = Response<dynamic>(
241+
requestOptions: options,
242+
statusCode: statusCode,
243+
statusMessage: statusMessage,
244+
headers: Headers.fromMap(headers),
245+
);
246+
247+
throw DioException.badResponse(
248+
requestOptions: options,
249+
response: response,
250+
statusCode: statusCode,
251+
);
252+
}
253+
254+
@override
255+
void close({bool force = false}) {
256+
// No-op for testing
257+
}
258+
}
259+
188260
class Fixture {
189-
Dio getSut([MockHttpClientAdapter? client]) {
261+
Dio getSut([HttpClientAdapter? client]) {
190262
final mc = client ?? getClient();
191263
final dio = Dio(
192264
BaseOptions(baseUrl: 'https://example.com?foo=bar#baz'),

0 commit comments

Comments
 (0)