-
-
Notifications
You must be signed in to change notification settings - Fork 239
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
Support for DIO HTTP library #673
Comments
I use DIO as well, can share what I am currently using. /// A [Dio] interceptor that adds [Breadcrumb]s and
/// transaction spans to existing transactions for
/// performance monitoring.
class HttpBreadcrumbInterceptor extends Interceptor {
final _request = HttpRequestBreadcrumbInterceptor();
final _success = HttpSuccessBreadcrumbInterceptor();
final _error = HttpErrorBreadcrumbInterceptor();
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) {
final currentSpan = Sentry.getSpan();
final span = currentSpan?.startChild(
'http.client',
description: '${options.method} ${options.uri}',
);
if (span != null) {
final traceHeader = span.toSentryTrace();
options.headers[traceHeader.name] = traceHeader.value;
options.extra['sentry-span'] = span;
}
return _request.onRequest(options, handler);
}
@override
Future<void> onResponse(
Response<dynamic> response,
ResponseInterceptorHandler handler,
) async {
final span = response.requestOptions.extra['sentry-span'] as ISentrySpan?;
span?.status = SpanStatus.fromHttpStatusCode(response.statusCode!);
await _success.onResponse(response, handler);
await span?.finish();
}
@override
Future<void> onError(
DioError err,
ErrorInterceptorHandler handler,
) async {
final span = err.requestOptions.extra['sentry-span'] as ISentrySpan?;
await _error.onError(err, handler);
span?.throwable = err;
span?.status = const SpanStatus.internalError();
await span?.finish();
}
}
class HttpRequestBreadcrumbInterceptor extends Interceptor {
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
Sentry.addBreadcrumb(
HttpBreadcrumb.onRequest(
HttpBreadcrumbData(
url: options.uri.toString(),
method: options.method,
),
),
);
handler.next(options);
}
}
class HttpSuccessBreadcrumbInterceptor extends Interceptor {
@override
Future<void> onResponse(
Response<dynamic> response,
ResponseInterceptorHandler handler,
) async {
Sentry.addBreadcrumb(
HttpBreadcrumb.onResponse(
HttpBreadcrumbData(
url: response.requestOptions.uri.toString(),
statusCode: response.statusCode,
method: response.requestOptions.method,
reason: response.statusMessage,
),
),
);
handler.next(response);
}
}
class HttpErrorBreadcrumbInterceptor extends Interceptor {
@override
Future<void> onError(
DioError error,
ErrorInterceptorHandler handler,
) async {
await addHttpErrorBreadcrumb(error);
handler.next(error);
}
static Future<void> addHttpErrorBreadcrumb(DioError error) async {
String? body;
if (error.response?.data != null) {
try {
body = jsonEncode(error.response!.data);
} catch (_) {
// Ignore and call toString()
body = 'JSON encoding failed: ${error.response!.data.toString()}';
}
}
Sentry.addBreadcrumb(
HttpBreadcrumb.onResponse(
HttpBreadcrumbData(
url: error.requestOptions.uri.toString(),
statusCode: error.response?.statusCode,
method: error.requestOptions.method,
reason: error.response?.statusMessage,
data: {
'type': error.type.toString(),
'err': error.message,
if (body != null) 'body': body,
},
),
),
);
}
}
class HttpBreadcrumb {
const HttpBreadcrumb._();
static Breadcrumb _httpBreadcrumb(
String message, {
DateTime? timestamp,
required String category,
required Map<String, String?> data,
}) =>
Breadcrumb(
message: message,
timestamp: timestamp ?? DateTime.now().toUtc(),
type: 'http',
category: category,
data: data,
);
static Breadcrumb onRequest(HttpBreadcrumbData request) => _httpBreadcrumb(
'${request.method}: ${request.url}',
category: 'Request',
data: request.toMap(),
);
static Breadcrumb onResponse(HttpBreadcrumbData response) => _httpBreadcrumb(
'${response.statusCode} ${response.method}: ${response.url}',
category: 'Response: ${response.statusCode}',
data: response.toMap(),
);
}
|
@kuhnroyal Do you mind also sharing the source for |
Added it above, it is very basic and the breadcrumbs are from the old Sentry client way back. |
Awesome, thanks! |
thanks @kuhnroyal |
After looking into it some more, I believe we can use dios The failing request client is a little bit more work to migrate, but the code below already works just as expected. var dio = Dio();
dio.httpClientAdapter = SentryHttpClient(recordBreadcrumbs: true, networkTracing: true); import 'dart:typed_data';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:sentry/sentry.dart';
class BreadcrumbClient extends HttpClientAdapter {
BreadcrumbClient({HttpClientAdapter? client, Hub? hub})
: _hub = hub ?? HubAdapter(),
_client = client ?? DefaultHttpClientAdapter();
final HttpClientAdapter _client;
final Hub _hub;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/
var requestHadException = false;
int? statusCode;
String? reason;
int? responseBodySize;
final stopwatch = Stopwatch();
stopwatch.start();
try {
final response =
await _client.fetch(options, requestStream, cancelFuture);
statusCode = response.statusCode;
reason = response.statusMessage;
return response;
} catch (_) {
requestHadException = true;
rethrow;
} finally {
stopwatch.stop();
final breadcrumb = Breadcrumb.http(
level: requestHadException ? SentryLevel.error : SentryLevel.info,
url: options.uri,
method: options.method,
statusCode: statusCode,
reason: reason,
requestDuration: stopwatch.elapsed,
responseBodySize: responseBodySize,
);
_hub.addBreadcrumb(breadcrumb);
}
}
@override
void close({bool force = false}) => _client.close(force: force);
}
import 'dart:typed_data';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// A [http](https://pub.dev/packages/http)-package compatible HTTP client
/// which adds support to Sentry Performance feature.
/// https://develop.sentry.dev/sdk/performance
class TracingClient extends HttpClientAdapter {
TracingClient({HttpClientAdapter? client, Hub? hub})
: _hub = hub ?? HubAdapter(),
_client = client ?? DefaultHttpClientAdapter();
final HttpClientAdapter _client;
final Hub _hub;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
// see https://develop.sentry.dev/sdk/performance/#header-sentry-trace
final currentSpan = _hub.getSpan();
final span = currentSpan?.startChild(
'http.client',
description: '${options.method} ${options.uri}',
);
ResponseBody? response;
try {
if (span != null) {
final traceHeader = span.toSentryTrace();
options.headers[traceHeader.name] = traceHeader.value;
}
// TODO: tracingOrigins support
response = await _client.fetch(options, requestStream, cancelFuture);
span?.status = SpanStatus.fromHttpStatusCode(response.statusCode ?? -1);
} catch (exception) {
span?.throwable = exception;
span?.status = const SpanStatus.internalError();
rethrow;
} finally {
await span?.finish();
}
return response;
}
@override
void close({bool force = false}) => _client.close(force: force);
}
class SentryHttpClient extends HttpClientAdapter {
SentryHttpClient({
HttpClientAdapter? client,
Hub? hub,
bool recordBreadcrumbs = true,
bool networkTracing = false,
}) {
_hub = hub ?? HubAdapter();
var innerClient = client ?? DefaultHttpClientAdapter();
if (networkTracing) {
innerClient = TracingClient(client: innerClient, hub: _hub);
}
// The ordering here matters.
// We don't want to include the breadcrumbs for the current request
// when capturing it as a failed request.
// However it still should be added for following events.
if (recordBreadcrumbs) {
innerClient = BreadcrumbClient(client: innerClient, hub: _hub);
}
_client = innerClient;
}
late HttpClientAdapter _client;
late Hub _hub;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) =>
_client.fetch(options, requestStream, cancelFuture);
@override
void close({bool force = false}) => _client.close(force: force);
} |
That works but Dio generally promotes the use of interceptors. |
Yeah, I agree. However, this approach reduces the amount of maintenance, because it's so similar to the already existing |
@kuhnroyal are you one of the new maintainers of |
Yea, I'll check it out tomorrow. |
Looks fine. Integrated it and will test tomorrow. |
Related: #539 |
dio is a popular HTTP library and I would like to request support for it, similar to the already existing support for the SentryHttpClient.
They have support for so-called interceptors, which should make it straight forward and also pretty similar to the
SentryHttpClient
.The text was updated successfully, but these errors were encountered: