Skip to content

Commit f4cc744

Browse files
authored
Add SentryRequest context for HttpException and SocketException (#1118)
1 parent 453e1bc commit f4cc744

19 files changed

+390
-59
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Add request context to `HttpException`, `SocketException` and `NetworkImageLoadException` ([#1118](https://github.com/getsentry/sentry-dart/pull/1118))
8+
- `SocketException` and `FileSystemException` with `OSError`s report the `OSError` as root exception ([#1118](https://github.com/getsentry/sentry-dart/pull/1118))
9+
510
### Fixes
611

712
- VendorId should be a String ([#1112](https://github.com/getsentry/sentry-dart/pull/1112))

dart/lib/src/enricher/enricher_event_processor.dart

-8
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import '../../event_processor.dart';
2+
import '../../sentry_options.dart';
3+
import 'io_enricher_event_processor.dart'
4+
if (dart.library.html) 'web_enricher_event_processor.dart';
5+
6+
abstract class EnricherEventProcessor implements EventProcessor {
7+
factory EnricherEventProcessor(SentryOptions options) =>
8+
enricherEventProcessor(options);
9+
}

dart/lib/src/enricher/io_enricher_event_processor.dart renamed to dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import 'dart:async';
22
import 'dart:io';
33
import 'dart:isolate';
44

5-
import '../event_processor.dart';
6-
import '../protocol.dart';
7-
import '../sentry_options.dart';
5+
import '../../protocol.dart';
6+
import '../../sentry_options.dart';
7+
import 'enricher_event_processor.dart';
88

9-
EventProcessor enricherEventProcessor(SentryOptions options) {
9+
EnricherEventProcessor enricherEventProcessor(SentryOptions options) {
1010
return IoEnricherEventProcessor(options);
1111
}
1212

1313
/// Enriches [SentryEvents] with various kinds of information.
1414
/// Uses Darts [Platform](https://api.dart.dev/stable/dart-io/Platform-class.html)
1515
/// class to read information.
16-
class IoEnricherEventProcessor extends EventProcessor {
16+
class IoEnricherEventProcessor implements EnricherEventProcessor {
1717
IoEnricherEventProcessor(
1818
this._options,
1919
);

dart/lib/src/enricher/web_enricher_event_processor.dart renamed to dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import 'dart:async';
22
import 'dart:html' as html show window, Window;
33

4-
import '../event_processor.dart';
5-
import '../protocol.dart';
6-
import '../sentry_options.dart';
4+
import '../../protocol.dart';
5+
import '../../sentry_options.dart';
6+
import 'enricher_event_processor.dart';
77

8-
EventProcessor enricherEventProcessor(SentryOptions options) {
8+
EnricherEventProcessor enricherEventProcessor(SentryOptions options) {
99
return WebEnricherEventProcessor(
1010
html.window,
1111
options,
1212
);
1313
}
1414

15-
class WebEnricherEventProcessor extends EventProcessor {
15+
class WebEnricherEventProcessor implements EnricherEventProcessor {
1616
WebEnricherEventProcessor(
1717
this._window,
1818
this._options,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import '../../event_processor.dart';
2+
import '../../sentry_options.dart';
3+
import 'io_exception_event_processor.dart'
4+
if (dart.library.html) 'web_exception_event_processor.dart';
5+
6+
abstract class ExceptionEventProcessor implements EventProcessor {
7+
factory ExceptionEventProcessor(SentryOptions options) =>
8+
exceptionEventProcessor(options);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import 'dart:io';
2+
3+
import '../../protocol.dart';
4+
import '../../sentry_options.dart';
5+
import 'exception_event_processor.dart';
6+
7+
ExceptionEventProcessor exceptionEventProcessor(SentryOptions options) =>
8+
IoExceptionEventProcessor(options);
9+
10+
class IoExceptionEventProcessor implements ExceptionEventProcessor {
11+
IoExceptionEventProcessor(this._options);
12+
13+
final SentryOptions _options;
14+
15+
@override
16+
SentryEvent apply(SentryEvent event, {dynamic hint}) {
17+
final throwable = event.throwable;
18+
if (throwable is HttpException) {
19+
return _applyHttpException(throwable, event);
20+
}
21+
if (throwable is SocketException) {
22+
return _applySocketException(throwable, event);
23+
}
24+
if (throwable is FileSystemException) {
25+
return _applyFileSystemException(throwable, event);
26+
}
27+
28+
return event;
29+
}
30+
31+
// https://api.dart.dev/stable/dart-io/HttpException-class.html
32+
SentryEvent _applyHttpException(HttpException exception, SentryEvent event) {
33+
final uri = exception.uri;
34+
if (uri == null) {
35+
return event;
36+
}
37+
return event.copyWith(
38+
request: event.request ?? SentryRequest.fromUri(uri: uri),
39+
);
40+
}
41+
42+
// https://api.dart.dev/stable/dart-io/SocketException-class.html
43+
SentryEvent _applySocketException(
44+
SocketException exception,
45+
SentryEvent event,
46+
) {
47+
final address = exception.address;
48+
final osError = exception.osError;
49+
if (address == null) {
50+
return event.copyWith(
51+
exceptions: [
52+
// OSError is the underlying error
53+
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
54+
// https://api.dart.dev/stable/dart-io/OSError-class.html
55+
if (osError != null) _sentryExceptionfromOsError(osError),
56+
...?event.exceptions,
57+
],
58+
);
59+
}
60+
SentryRequest? request;
61+
try {
62+
var uri = Uri.parse(address.host);
63+
request = SentryRequest.fromUri(uri: uri);
64+
} catch (exception, stackTrace) {
65+
_options.logger(
66+
SentryLevel.error,
67+
'Could not parse ${address.host} to Uri',
68+
exception: exception,
69+
stackTrace: stackTrace,
70+
);
71+
}
72+
73+
return event.copyWith(
74+
request: event.request ?? request,
75+
exceptions: [
76+
// OSError is the underlying error
77+
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
78+
// https://api.dart.dev/stable/dart-io/OSError-class.html
79+
if (osError != null) _sentryExceptionfromOsError(osError),
80+
...?event.exceptions,
81+
],
82+
);
83+
}
84+
85+
// https://api.dart.dev/stable/dart-io/FileSystemException-class.html
86+
SentryEvent _applyFileSystemException(
87+
FileSystemException exception,
88+
SentryEvent event,
89+
) {
90+
final osError = exception.osError;
91+
return event.copyWith(
92+
exceptions: [
93+
// OSError is the underlying error
94+
// https://api.dart.dev/stable/dart-io/FileSystemException/osError.html
95+
// https://api.dart.dev/stable/dart-io/OSError-class.html
96+
if (osError != null) _sentryExceptionfromOsError(osError),
97+
...?event.exceptions,
98+
],
99+
);
100+
}
101+
}
102+
103+
SentryException _sentryExceptionfromOsError(OSError osError) {
104+
return SentryException(
105+
type: osError.runtimeType.toString(),
106+
value: osError.toString(),
107+
// osError.errorCode is likely a posix signal
108+
// https://develop.sentry.dev/sdk/event-payloads/types/#mechanismmeta
109+
mechanism: Mechanism(
110+
type: 'OSError',
111+
meta: {
112+
'errno': {'number': osError.errorCode},
113+
},
114+
),
115+
);
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import '../../protocol.dart';
2+
import '../../sentry_options.dart';
3+
import 'exception_event_processor.dart';
4+
5+
ExceptionEventProcessor exceptionEventProcessor(SentryOptions _) =>
6+
WebExcptionEventProcessor();
7+
8+
class WebExcptionEventProcessor implements ExceptionEventProcessor {
9+
@override
10+
SentryEvent apply(SentryEvent event, {dynamic hint}) => event;
11+
}

dart/lib/src/http_client/failed_request_client.dart

+2-15
Original file line numberDiff line numberDiff line change
@@ -154,29 +154,16 @@ class FailedRequestClient extends BaseClient {
154154
required BaseRequest request,
155155
required StreamedResponse? response,
156156
}) async {
157-
// As far as I can tell there's no way to get the uri without the query part
158-
// so we replace it with an empty string.
159-
final urlWithoutQuery = request.url
160-
.replace(query: '', fragment: '')
161-
.toString()
162-
.replaceAll('?', '')
163-
.replaceAll('#', '');
164-
165-
final query = request.url.query.isEmpty ? null : request.url.query;
166-
final fragment = request.url.fragment.isEmpty ? null : request.url.fragment;
167-
168-
final sentryRequest = SentryRequest(
157+
final sentryRequest = SentryRequest.fromUri(
169158
method: request.method,
170159
headers: sendDefaultPii ? request.headers : null,
171-
url: urlWithoutQuery,
172-
queryString: query,
160+
uri: request.url,
173161
data: sendDefaultPii ? _getDataFromRequest(request) : null,
174162
// ignore: deprecated_member_use_from_same_package
175163
other: {
176164
'content_length': request.contentLength.toString(),
177165
'duration': requestDuration.toString(),
178166
},
179-
fragment: fragment,
180167
);
181168

182169
final mechanism = Mechanism(

dart/lib/src/protocol/sentry_request.dart

+36
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,42 @@ class SentryRequest {
8181
_env = env != null ? Map.from(env) : null,
8282
_other = other != null ? Map.from(other) : null;
8383

84+
factory SentryRequest.fromUri({
85+
required Uri uri,
86+
String? method,
87+
String? cookies,
88+
dynamic data,
89+
Map<String, String>? headers,
90+
Map<String, String>? env,
91+
@Deprecated('Will be removed in v7.') Map<String, String>? other,
92+
}) {
93+
// As far as I can tell there's no way to get the uri without the query part
94+
// so we replace it with an empty string.
95+
final urlWithoutQuery = uri
96+
.replace(query: '', fragment: '')
97+
.toString()
98+
.replaceAll('?', '')
99+
.replaceAll('#', '');
100+
101+
// Future proof, Dio does not support it yet and even if passing in the path,
102+
// the parsing of the uri returns empty.
103+
final query = uri.query.isEmpty ? null : uri.query;
104+
final fragment = uri.fragment.isEmpty ? null : uri.fragment;
105+
106+
return SentryRequest(
107+
url: urlWithoutQuery,
108+
fragment: fragment,
109+
queryString: query,
110+
method: method,
111+
cookies: cookies,
112+
data: data,
113+
headers: headers,
114+
env: env,
115+
// ignore: deprecated_member_use_from_same_package
116+
other: other,
117+
);
118+
}
119+
84120
/// Deserializes a [SentryRequest] from JSON [Map].
85121
factory SentryRequest.fromJson(Map<String, dynamic> json) {
86122
return SentryRequest(

dart/lib/src/sentry.dart

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import 'dart:async';
33
import 'package:meta/meta.dart';
44

55
import 'default_integrations.dart';
6-
import 'enricher/enricher_event_processor.dart';
6+
import 'event_processor/enricher/enricher_event_processor.dart';
77
import 'environment/environment_variables.dart';
88
import 'event_processor/deduplication_event_processor.dart';
9+
import 'event_processor/exception/exception_event_processor.dart';
910
import 'hub.dart';
1011
import 'hub_adapter.dart';
1112
import 'integration.dart';
@@ -73,7 +74,8 @@ class Sentry {
7374
options.addIntegrationByIndex(0, IsolateErrorIntegration());
7475
}
7576

76-
options.addEventProcessor(getEnricherEventProcessor(options));
77+
options.addEventProcessor(EnricherEventProcessor(options));
78+
options.addEventProcessor(ExceptionEventProcessor(options));
7779
options.addEventProcessor(DeduplicationEventProcessor(options));
7880
}
7981

dart/test/enricher/io_enricher_test.dart renamed to dart/test/event_processor/enricher/io_enricher_test.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
@TestOn('vm')
22

33
import 'package:sentry/sentry.dart';
4-
import 'package:sentry/src/enricher/io_enricher_event_processor.dart';
4+
import 'package:sentry/src/event_processor/enricher/io_enricher_event_processor.dart';
55
import 'package:test/test.dart';
66

7-
import '../mocks.dart';
8-
import '../mocks/mock_platform_checker.dart';
7+
import '../../mocks.dart';
8+
import '../../mocks/mock_platform_checker.dart';
99

1010
void main() {
1111
group('io_enricher', () {

dart/test/enricher/web_enricher_test.dart renamed to dart/test/event_processor/enricher/web_enricher_test.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import 'dart:html' as html;
33

44
import 'package:sentry/sentry.dart';
5-
import 'package:sentry/src/enricher/web_enricher_event_processor.dart';
5+
import 'package:sentry/src/event_processor/enricher/web_enricher_event_processor.dart';
66
import 'package:test/test.dart';
77

8-
import '../mocks.dart';
9-
import '../mocks/mock_platform_checker.dart';
8+
import '../../mocks.dart';
9+
import '../../mocks/mock_platform_checker.dart';
1010

1111
// can be tested on command line with
1212
// `dart test -p chrome --name web_enricher`

0 commit comments

Comments
 (0)