Skip to content

Instantly share code, notes, and snippets.

@donpaul120
Created February 26, 2026 14:20
Show Gist options
  • Select an option

  • Save donpaul120/e708590843cc89d33e175cb7304d2a48 to your computer and use it in GitHub Desktop.

Select an option

Save donpaul120/e708590843cc89d33e175cb7304d2a48 to your computer and use it in GitHub Desktop.
EmbraceDioInterceptor that also respects sending the http status code for onError
class CustomEmbraceDioInterceptor extends Interceptor {
CustomEmbraceDioInterceptor({String Function(RequestOptions)? spanNameBuilder})
: _spanNameBuilder = spanNameBuilder ?? _defaultName;
static const String _contentLengthHeader = 'Content-Length';
final String Function(RequestOptions) _spanNameBuilder;
final _startTimes = HashMap<RequestOptions, int>();
static String _defaultName(RequestOptions o) => '${o.method} ${o.uri.path}';
@override
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
_startTimes[options] = DateTime.now().millisecondsSinceEpoch;
await _injectTraceparent(options);
handler.next(options);
}
@override
void onResponse(
Response<dynamic> response,
ResponseInterceptorHandler handler,
) {
try {
final request = response.requestOptions;
final startTimeMs = _startTimes.remove(request) ?? 0;
final endTimeMs = DateTime.now().millisecondsSinceEpoch;
Embrace.instance.recordCompletedSpan(
_spanNameBuilder(request),
startTimeMs,
endTimeMs,
attributes: {
'http.request.method': request.method,
'url.full': request.uri.toString(),
'http.response.status_code': '${response.statusCode ?? 0}',
'http.request.body.size': '${_bytesSent(request)}',
'http.response.body.size': '${_bytesReceived(response)}',
},
);
} catch (e) {
EmbracePlatform.instance
.logInternalError('Could not capture network request', e.toString());
} finally {
handler.next(response);
}
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
try {
final request = err.requestOptions;
final startTimeMs = _startTimes.remove(request) ?? 0;
final endTimeMs = DateTime.now().millisecondsSinceEpoch;
final statusCode = err.response?.statusCode;
Logger.getLogger().info("onError - Recording Span");
Embrace.instance.recordCompletedSpan(
_spanNameBuilder(request),
startTimeMs,
endTimeMs,
errorCode: statusCode != null ? ErrorCode.failure : ErrorCode.unknown,
attributes: {
'http.request.method': request.method,
'url.full': request.uri.toString(),
if (statusCode != null) ...{
'http.response.status_code': '$statusCode',
'http.request.body.size': '${_bytesSent(request)}',
'http.response.body.size': '${_bytesReceived(err.response)}',
} else
'error.message': err.message ?? '',
},
);
} catch (e) {
EmbracePlatform.instance
.logInternalError('Could not capture network error', e.toString());
} finally {
handler.next(err);
}
}
Future<void> _injectTraceparent(RequestOptions options) async {
if (options.headers.containsKey('traceparent')) return;
final traceparent =
await Embrace.instance.generateW3cTraceparent(null, null);
if (traceparent != null) {
options.headers['traceparent'] = traceparent;
}
}
int _bytesSent(RequestOptions request) {
final data = request.data;
// TODO: do better calculation
return data is String ? data.length : 0;
}
int _bytesReceived(Response<dynamic>? response) {
if (response == null) return 0;
final header = response.headers.value(_contentLengthHeader);
if (header != null) return int.tryParse(header) ?? 0;
final type = response.requestOptions.responseType;
if (response.data != null &&
(type == ResponseType.plain || type == ResponseType.json)) {
return response.data.toString().length;
}
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment