Skip to content

Commit

Permalink
Add SentryRequest context for HttpException and SocketException (#1118)
Browse files Browse the repository at this point in the history
  • Loading branch information
ueman authored Nov 17, 2022
1 parent 453e1bc commit f4cc744
Show file tree
Hide file tree
Showing 19 changed files with 390 additions and 59 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Features

- Add request context to `HttpException`, `SocketException` and `NetworkImageLoadException` ([#1118](https://github.com/getsentry/sentry-dart/pull/1118))
- `SocketException` and `FileSystemException` with `OSError`s report the `OSError` as root exception ([#1118](https://github.com/getsentry/sentry-dart/pull/1118))

### Fixes

- VendorId should be a String ([#1112](https://github.com/getsentry/sentry-dart/pull/1112))
Expand Down
8 changes: 0 additions & 8 deletions dart/lib/src/enricher/enricher_event_processor.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import '../../event_processor.dart';
import '../../sentry_options.dart';
import 'io_enricher_event_processor.dart'
if (dart.library.html) 'web_enricher_event_processor.dart';

abstract class EnricherEventProcessor implements EventProcessor {
factory EnricherEventProcessor(SentryOptions options) =>
enricherEventProcessor(options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import '../event_processor.dart';
import '../protocol.dart';
import '../sentry_options.dart';
import '../../protocol.dart';
import '../../sentry_options.dart';
import 'enricher_event_processor.dart';

EventProcessor enricherEventProcessor(SentryOptions options) {
EnricherEventProcessor enricherEventProcessor(SentryOptions options) {
return IoEnricherEventProcessor(options);
}

/// Enriches [SentryEvents] with various kinds of information.
/// Uses Darts [Platform](https://api.dart.dev/stable/dart-io/Platform-class.html)
/// class to read information.
class IoEnricherEventProcessor extends EventProcessor {
class IoEnricherEventProcessor implements EnricherEventProcessor {
IoEnricherEventProcessor(
this._options,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import 'dart:async';
import 'dart:html' as html show window, Window;

import '../event_processor.dart';
import '../protocol.dart';
import '../sentry_options.dart';
import '../../protocol.dart';
import '../../sentry_options.dart';
import 'enricher_event_processor.dart';

EventProcessor enricherEventProcessor(SentryOptions options) {
EnricherEventProcessor enricherEventProcessor(SentryOptions options) {
return WebEnricherEventProcessor(
html.window,
options,
);
}

class WebEnricherEventProcessor extends EventProcessor {
class WebEnricherEventProcessor implements EnricherEventProcessor {
WebEnricherEventProcessor(
this._window,
this._options,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import '../../event_processor.dart';
import '../../sentry_options.dart';
import 'io_exception_event_processor.dart'
if (dart.library.html) 'web_exception_event_processor.dart';

abstract class ExceptionEventProcessor implements EventProcessor {
factory ExceptionEventProcessor(SentryOptions options) =>
exceptionEventProcessor(options);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'dart:io';

import '../../protocol.dart';
import '../../sentry_options.dart';
import 'exception_event_processor.dart';

ExceptionEventProcessor exceptionEventProcessor(SentryOptions options) =>
IoExceptionEventProcessor(options);

class IoExceptionEventProcessor implements ExceptionEventProcessor {
IoExceptionEventProcessor(this._options);

final SentryOptions _options;

@override
SentryEvent apply(SentryEvent event, {dynamic hint}) {
final throwable = event.throwable;
if (throwable is HttpException) {
return _applyHttpException(throwable, event);
}
if (throwable is SocketException) {
return _applySocketException(throwable, event);
}
if (throwable is FileSystemException) {
return _applyFileSystemException(throwable, event);
}

return event;
}

// https://api.dart.dev/stable/dart-io/HttpException-class.html
SentryEvent _applyHttpException(HttpException exception, SentryEvent event) {
final uri = exception.uri;
if (uri == null) {
return event;
}
return event.copyWith(
request: event.request ?? SentryRequest.fromUri(uri: uri),
);
}

// https://api.dart.dev/stable/dart-io/SocketException-class.html
SentryEvent _applySocketException(
SocketException exception,
SentryEvent event,
) {
final address = exception.address;
final osError = exception.osError;
if (address == null) {
return event.copyWith(
exceptions: [
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
if (osError != null) _sentryExceptionfromOsError(osError),
...?event.exceptions,
],
);
}
SentryRequest? request;
try {
var uri = Uri.parse(address.host);
request = SentryRequest.fromUri(uri: uri);
} catch (exception, stackTrace) {
_options.logger(
SentryLevel.error,
'Could not parse ${address.host} to Uri',
exception: exception,
stackTrace: stackTrace,
);
}

return event.copyWith(
request: event.request ?? request,
exceptions: [
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
if (osError != null) _sentryExceptionfromOsError(osError),
...?event.exceptions,
],
);
}

// https://api.dart.dev/stable/dart-io/FileSystemException-class.html
SentryEvent _applyFileSystemException(
FileSystemException exception,
SentryEvent event,
) {
final osError = exception.osError;
return event.copyWith(
exceptions: [
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/FileSystemException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
if (osError != null) _sentryExceptionfromOsError(osError),
...?event.exceptions,
],
);
}
}

SentryException _sentryExceptionfromOsError(OSError osError) {
return SentryException(
type: osError.runtimeType.toString(),
value: osError.toString(),
// osError.errorCode is likely a posix signal
// https://develop.sentry.dev/sdk/event-payloads/types/#mechanismmeta
mechanism: Mechanism(
type: 'OSError',
meta: {
'errno': {'number': osError.errorCode},
},
),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import '../../protocol.dart';
import '../../sentry_options.dart';
import 'exception_event_processor.dart';

ExceptionEventProcessor exceptionEventProcessor(SentryOptions _) =>
WebExcptionEventProcessor();

class WebExcptionEventProcessor implements ExceptionEventProcessor {
@override
SentryEvent apply(SentryEvent event, {dynamic hint}) => event;
}
17 changes: 2 additions & 15 deletions dart/lib/src/http_client/failed_request_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,29 +154,16 @@ class FailedRequestClient extends BaseClient {
required BaseRequest request,
required StreamedResponse? response,
}) async {
// As far as I can tell there's no way to get the uri without the query part
// so we replace it with an empty string.
final urlWithoutQuery = request.url
.replace(query: '', fragment: '')
.toString()
.replaceAll('?', '')
.replaceAll('#', '');

final query = request.url.query.isEmpty ? null : request.url.query;
final fragment = request.url.fragment.isEmpty ? null : request.url.fragment;

final sentryRequest = SentryRequest(
final sentryRequest = SentryRequest.fromUri(
method: request.method,
headers: sendDefaultPii ? request.headers : null,
url: urlWithoutQuery,
queryString: query,
uri: request.url,
data: sendDefaultPii ? _getDataFromRequest(request) : null,
// ignore: deprecated_member_use_from_same_package
other: {
'content_length': request.contentLength.toString(),
'duration': requestDuration.toString(),
},
fragment: fragment,
);

final mechanism = Mechanism(
Expand Down
36 changes: 36 additions & 0 deletions dart/lib/src/protocol/sentry_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,42 @@ class SentryRequest {
_env = env != null ? Map.from(env) : null,
_other = other != null ? Map.from(other) : null;

factory SentryRequest.fromUri({
required Uri uri,
String? method,
String? cookies,
dynamic data,
Map<String, String>? headers,
Map<String, String>? env,
@Deprecated('Will be removed in v7.') Map<String, String>? other,
}) {
// As far as I can tell there's no way to get the uri without the query part
// so we replace it with an empty string.
final urlWithoutQuery = uri
.replace(query: '', fragment: '')
.toString()
.replaceAll('?', '')
.replaceAll('#', '');

// Future proof, Dio does not support it yet and even if passing in the path,
// the parsing of the uri returns empty.
final query = uri.query.isEmpty ? null : uri.query;
final fragment = uri.fragment.isEmpty ? null : uri.fragment;

return SentryRequest(
url: urlWithoutQuery,
fragment: fragment,
queryString: query,
method: method,
cookies: cookies,
data: data,
headers: headers,
env: env,
// ignore: deprecated_member_use_from_same_package
other: other,
);
}

/// Deserializes a [SentryRequest] from JSON [Map].
factory SentryRequest.fromJson(Map<String, dynamic> json) {
return SentryRequest(
Expand Down
6 changes: 4 additions & 2 deletions dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import 'dart:async';
import 'package:meta/meta.dart';

import 'default_integrations.dart';
import 'enricher/enricher_event_processor.dart';
import 'event_processor/enricher/enricher_event_processor.dart';
import 'environment/environment_variables.dart';
import 'event_processor/deduplication_event_processor.dart';
import 'event_processor/exception/exception_event_processor.dart';
import 'hub.dart';
import 'hub_adapter.dart';
import 'integration.dart';
Expand Down Expand Up @@ -73,7 +74,8 @@ class Sentry {
options.addIntegrationByIndex(0, IsolateErrorIntegration());
}

options.addEventProcessor(getEnricherEventProcessor(options));
options.addEventProcessor(EnricherEventProcessor(options));
options.addEventProcessor(ExceptionEventProcessor(options));
options.addEventProcessor(DeduplicationEventProcessor(options));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
@TestOn('vm')

import 'package:sentry/sentry.dart';
import 'package:sentry/src/enricher/io_enricher_event_processor.dart';
import 'package:sentry/src/event_processor/enricher/io_enricher_event_processor.dart';
import 'package:test/test.dart';

import '../mocks.dart';
import '../mocks/mock_platform_checker.dart';
import '../../mocks.dart';
import '../../mocks/mock_platform_checker.dart';

void main() {
group('io_enricher', () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import 'dart:html' as html;

import 'package:sentry/sentry.dart';
import 'package:sentry/src/enricher/web_enricher_event_processor.dart';
import 'package:sentry/src/event_processor/enricher/web_enricher_event_processor.dart';
import 'package:test/test.dart';

import '../mocks.dart';
import '../mocks/mock_platform_checker.dart';
import '../../mocks.dart';
import '../../mocks/mock_platform_checker.dart';

// can be tested on command line with
// `dart test -p chrome --name web_enricher`
Expand Down
Loading

0 comments on commit f4cc744

Please sign in to comment.