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

Feature: Improve Dio exception reports #718

Merged
merged 33 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0a38749
Improve Dio
ueman Jan 26, 2022
b5892ac
Improve DioError EventProcessor
ueman Jan 26, 2022
61a5b31
Improve example
ueman Jan 26, 2022
0234ec4
some more improvements
ueman Jan 26, 2022
f810592
Update dio/lib/src/dio_event_processor.dart
ueman Jan 27, 2022
c3fce27
wip
ueman Jan 27, 2022
8906540
revert changes
ueman Jan 27, 2022
9b2ec26
add some tests
ueman Jan 27, 2022
b902a54
revert
ueman Jan 27, 2022
c899deb
wip
ueman Jan 28, 2022
7a84f03
Merge branch 'main' into improve-dio
ueman Feb 10, 2022
5b8648a
Revert "wip"
ueman Feb 10, 2022
3be1706
improvements
ueman Feb 10, 2022
f8ee5d0
add test
ueman Feb 10, 2022
ee0be84
changelog
ueman Feb 10, 2022
58245ab
read PII setting from options
ueman Feb 10, 2022
577375b
add integration
ueman Feb 10, 2022
0ca1ddc
clean up example
ueman Feb 10, 2022
65ba38f
fix url
ueman Feb 10, 2022
f0f1efa
fix test
ueman Feb 10, 2022
ff4dc79
Merge remote-tracking branch 'origin/main' into improve-dio
ueman Feb 11, 2022
2b79904
some more tests
ueman Feb 11, 2022
d291d03
more tests
ueman Feb 11, 2022
1fcf734
fix test
ueman Feb 11, 2022
76adf4c
further improve errors
ueman Feb 11, 2022
0286f4d
remove type
ueman Feb 12, 2022
f88aaa3
lint clean up
ueman Feb 12, 2022
a38c2d8
further improve reports
ueman Feb 12, 2022
7d74c5b
improve in app frames in example
ueman Feb 12, 2022
d75dd3d
Merge branch 'main' into improve-dio
marandaneto Feb 15, 2022
79b9df9
Merge branch 'main' into improve-dio
marandaneto Feb 21, 2022
c7289d9
Merge branch 'main' into improve-dio
marandaneto Feb 21, 2022
43f2daa
Merge branch 'main' into improve-dio
marandaneto Feb 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [Dio] Ref: Replace FailedRequestAdapter with FailedRequestInterceptor (#728)
* Fix: Add missing return values - dart analyzer (#742)
* Feature: Add `DioEventProcessor` which improves DioError crash reports (#718)

# 6.3.0

Expand Down
19 changes: 0 additions & 19 deletions dart/lib/src/http_client/failed_request_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,22 +217,3 @@ extension _ListX on List<SentryStatusCode> {
return any((element) => element.isInRange(statusCode));
}
}

extension _MaxRequestBodySizeX on MaxRequestBodySize {
bool shouldAddBody(int contentLength) {
if (this == MaxRequestBodySize.never) {
return false;
}
if (this == MaxRequestBodySize.always) {
return true;
}
if (this == MaxRequestBodySize.medium && contentLength <= 10000) {
return true;
}

if (this == MaxRequestBodySize.small && contentLength <= 4000) {
return true;
}
return false;
}
}
19 changes: 19 additions & 0 deletions dart/lib/src/protocol/max_request_body_size.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,22 @@ enum MaxRequestBodySize {
/// make sense of it
always,
}

extension MaxRequestBodySizeX on MaxRequestBodySize {
bool shouldAddBody(int contentLength) {
if (this == MaxRequestBodySize.never) {
return false;
}
if (this == MaxRequestBodySize.always) {
return true;
}
if (this == MaxRequestBodySize.medium && contentLength <= 10000) {
return true;
}

if (this == MaxRequestBodySize.small && contentLength <= 4000) {
return true;
}
return false;
}
}
4 changes: 4 additions & 0 deletions dio/lib/sentry_dio.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
library sentry_dio;

export 'src/sentry_dio_extension.dart';

// Export the processor in case people want to use it standalone.
// Normally one doesn't need to use it directly.
export 'src/dio_event_processor.dart';
140 changes: 140 additions & 0 deletions dio/lib/src/dio_event_processor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// ignore_for_file: implementation_imports

import 'dart:async';

import 'package:dio/dio.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry/src/sentry_exception_factory.dart';

/// This is an [EventProcessor], which improves crash reports of [DioError]s.
/// It adds information about [DioError.response] if present and also about
/// the inner exceptions.
class DioEventProcessor implements EventProcessor {
/// This is an [EventProcessor], which improves crash reports of [DioError]s.
DioEventProcessor(this._options, this._maxRequestBodySize);

final SentryOptions _options;
final MaxRequestBodySize _maxRequestBodySize;

SentryExceptionFactory get _sentryExceptionFactory =>
// ignore: invalid_use_of_internal_member
_options.exceptionFactory;
marandaneto marked this conversation as resolved.
Show resolved Hide resolved

@override
FutureOr<SentryEvent?> apply(SentryEvent event, {dynamic hint}) {
final dynamic dioError = event.throwable;
if (dioError is! DioError) {
return event;
}

// Don't override just parts of the original request.
// Keep the original one or if there's none create one.
event = event.copyWith(request: event.request ?? _requestFrom(dioError));

final innerDioStackTrace = dioError.stackTrace;
final innerDioErrorException = dioError.error as Object?;

// If the inner errors stacktrace is null,
// there's nothing to create chained exception
if (innerDioStackTrace == null) {
return event;
}

try {
var innerException = _sentryExceptionFactory.getSentryException(
innerDioErrorException ?? 'DioError inner stacktrace',
stackTrace: innerDioStackTrace,
);
if (innerDioErrorException.runtimeType == String) {
innerException =
innerException.copyWith(type: 'DioError inner stacktrace');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for not using it's type? String in this case.


final exceptions = _removeDioErrorStackTraceFromValue(
List<SentryException>.from(event.exceptions ?? <SentryException>[]),
dioError,
);

return event.copyWith(
exceptions: [
innerException,
...exceptions,
ueman marked this conversation as resolved.
Show resolved Hide resolved
],
);
} catch (e, stackTrace) {
_options.logger(
SentryLevel.debug,
'Could not convert DioError to SentryException',
exception: e,
stackTrace: stackTrace,
);
}
return event;
}

/// Remove the StackTrace from [dioError] so the message on Sentry looks
/// much better.
List<SentryException> _removeDioErrorStackTraceFromValue(
List<SentryException> exceptions,
DioError dioError,
) {
var dioSentryException = exceptions
.where((element) => element.type == dioError.runtimeType.toString())
.first;

final exceptionIndex = exceptions.indexOf(dioSentryException);
exceptions.remove(dioSentryException);

// remove stacktrace, so that it looks better on Sentry.io
dioError.stackTrace = null;

dioSentryException = dioSentryException.copyWith(
value: dioError.toString(),
);

exceptions.insert(exceptionIndex, dioSentryException);

return exceptions;
}

SentryRequest? _requestFrom(DioError dioError) {
final options = dioError.requestOptions;
// 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 = options.uri.replace(query: '').toString();

final query = options.uri.query.isEmpty ? null : options.uri.query;

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

return SentryRequest(
method: options.method,
headers: _options.sendDefaultPii ? headers : null,
url: urlWithoutQuery,
queryString: query,
cookies: _options.sendDefaultPii
? options.headers['Cookie']?.toString()
: null,
data: _getRequestData(dioError.response?.data),
ueman marked this conversation as resolved.
Show resolved Hide resolved
);
}

/// Returns the request data, if possible according to the users settings.
/// Type checks are based on DIOs [ResponseType].
Object? _getRequestData(dynamic data) {
if (!_options.sendDefaultPii) {
return null;
}
if (data is String) {
if (_maxRequestBodySize.shouldAddBody(data.codeUnits.length)) {
return data;
}
} else if (data is List<int>) {
if (_maxRequestBodySize.shouldAddBody(data.length)) {
return data;
}
}
return null;
}
}
21 changes: 18 additions & 3 deletions dio/lib/src/sentry_dio_extension.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:sentry/sentry.dart';
import 'dio_event_processor.dart';
import 'failed_request_interceptor.dart';
import 'sentry_transformer.dart';
import 'sentry_dio_client_adapter.dart';
Expand All @@ -16,10 +17,20 @@ extension SentryDioExtension on Dio {
bool recordBreadcrumbs = true,
bool networkTracing = true,
MaxRequestBodySize maxRequestBodySize = MaxRequestBodySize.never,
List<SentryStatusCode> failedRequestStatusCodes = const [],
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
bool captureFailedRequests = false,
bool sendDefaultPii = false,
Hub? hub,
}) {
hub = hub ?? HubAdapter();

// ignore: invalid_use_of_internal_member
final options = hub.options;

// Add DioEventProcessor when it's not already present
if (options.eventProcessors.whereType<DioEventProcessor>().isEmpty) {
options.sdk.addIntegration('sentry_dio');
options.addEventProcessor(DioEventProcessor(options, maxRequestBodySize));
}

if (captureFailedRequests) {
// Add FailedRequestInterceptor at index 0, so it's the first interceptor.
// This ensures that it is called and not skipped by any previous interceptor.
Expand All @@ -31,9 +42,13 @@ extension SentryDioExtension on Dio {
client: httpClientAdapter,
recordBreadcrumbs: recordBreadcrumbs,
networkTracing: networkTracing,
hub: hub,
);

// intercept transformations
transformer = SentryTransformer(transformer: transformer);
transformer = SentryTransformer(
transformer: transformer,
hub: hub,
);
}
}
Loading