sentry_link 0.2.1 icon indicating copy to clipboard operation
sentry_link: ^0.2.1 copied to clipboard

Automatic capture of exceptions and GraphQL errors for the gql eco-system, like graphql and ferry

Sentry Link (GraphQL) #

pub package likes popularity pub points

Compatibility list #

This integration is compatible with the following packages. It's also compatible with other packages which are build on gql suite of packages.

package stats
gql_link likes popularity pub points
graphql likes popularity pub points
ferry likes popularity pub points
artemis likes popularity pub points

Usage #

Just add SentryGql.link() to your links. It will add error reporting and performance monitoring to your GraphQL operations.

final link = Link.from([
    AuthLink(getToken: () async => 'Bearer $personalAccessToken'),
    // SentryLink records exceptions
    SentryGql.link(
      shouldStartTransaction: true,
      graphQlErrorsMarkTransactionAsFailed: true,
    ),
    HttpLink('https://api.github.com/graphql'),
]);

A GraphQL error will be reported like the following screenshot:

Improve exception reports for LinkExceptions #

LinkExceptions and it subclasses can be arbitrary deeply nested. By adding an exception extractor for it, Sentry can create significantly improved exception reports.

Sentry.init((options) {
  options.addGqlExtractors();
});

Performance traces for serialization and parsing #

The SentryResponseParser and SentryRequestSerializer classes can be used to trace the de/serialization process. Both classes work with the HttpLink and the DioLink. When using the HttpLink, you can additionally use the sentryResponseDecoder function as explained further down below.

This example uses the http integration in addition to this gql integration.

import 'package:sentry/sentry.dart';
import 'package:sentry_link/sentry_link.dart';

final link = Link.from([
    AuthLink(getToken: () async => 'Bearer $personalAccessToken'),
    SentryGql.link(
      shouldStartTransaction: true,
      graphQlErrorsMarkTransactionAsFailed: true,
    ),
    HttpLink(
      'https://api.github.com/graphql',
      httpClient: SentryHttpClient(),
      serializer: SentryRequestSerializer(),
      parser: SentryResponseParser(),
    ),
  ]);

This example uses the sentry_dio integration in addition to this gql integration.

import 'package:sentry_link/sentry_link.dart';
import 'package:sentry_dio/sentry_dio.dart';

final link = Link.from([
    AuthLink(getToken: () async => 'Bearer $personalAccessToken'),
    SentryGql.link(
      shouldStartTransaction: true,
      graphQlErrorsMarkTransactionAsFailed: true,
    ),
    DioLink(
      'https://api.github.com/graphql',
      client: Dio()..addSentry(),
      serializer: SentryRequestSerializer(),
      parser: SentryResponseParser(),
    ),
  ]);
HttpLink
import 'dart:async';
import 'dart:convert';

import 'package:sentry/sentry.dart';
import 'package:http/http.dart' as http;

import 'package:sentry_link/sentry_link.dart';

final link = Link.from([
  AuthLink(getToken: () async => 'Bearer $personalAccessToken'),
  SentryGql.link(
    shouldStartTransaction: true,
    graphQlErrorsMarkTransactionAsFailed: true,
  ),
  HttpLink(
    'https://api.github.com/graphql',
    httpClient: SentryHttpClient(networkTracing: true),
    serializer: SentryRequestSerializer(),
    parser: SentryResponseParser(),
    httpResponseDecoder: sentryResponseDecoder,
  ),
]);

Map<String, dynamic>? sentryResponseDecoder(
  http.Response response, {
  Hub? hub,
}) {
  final currentHub = hub ?? HubAdapter();
  final span = currentHub.getSpan()?.startChild(
        'serialize.http.client',
        description: 'http response deserialization',
      );
  Map<String, dynamic>? result;
  try {
    result = _defaultHttpResponseDecoder(response);
    span?.status = const SpanStatus.ok();
  } catch (e) {
    span?.status = const SpanStatus.unknownError();
    span?.throwable = e;
    rethrow;
  } finally {
    unawaited(span?.finish());
  }
  return result;
}

Map<String, dynamic>? _defaultHttpResponseDecoder(http.Response httpResponse) {
  return json.decode(utf8.decode(httpResponse.bodyBytes))
      as Map<String, dynamic>?;
}

Filter redundant HTTP breadcrumbs #

If you use the sentry_dio or http you will have breadcrumbs attached for every HTTP request. In order to not have duplicated breadcrumbs from the HTTP integrations and this GraphQL integration, you should filter those breadcrumbs.

That can be achieved in two ways:

  1. Disable all HTTP breadcrumbs.
  2. Use beforeBreadcrumb.
return Sentry.init(
  (options) {
    options.beforeBreadcrumb = graphQlFilter();
    // or 
    options.beforeBreadcrumb = graphQlFilter((breadcrumb, hint) {
      // custom filter
      return breadcrumb;
    });
  },
);

Additional graphql usage hints #

Additional hints for usage with graphql

import 'package:sentry/sentry.dart';
import 'package:sentry_link/sentry_link.dart';
import 'package:graphql/graphql.dart';

Sentry.init((options) {
  options.addExceptionCauseExtractor(UnknownExceptionExtractor());
  options.addExceptionCauseExtractor(NetworkExceptionExtractor());
  options.addExceptionCauseExtractor(CacheMissExceptionExtractor());
  options.addExceptionCauseExtractor(OperationExceptionExtractor());
  options.addExceptionCauseExtractor(CacheMisconfigurationExceptionExtractor());
  options.addExceptionCauseExtractor(MismatchedDataStructureExceptionExtractor());
  options.addExceptionCauseExtractor(UnexpectedResponseStructureExceptionExtractor());
});

class UnknownExceptionExtractor
    extends LinkExceptionExtractor<UnknownException> {}

class NetworkExceptionExtractor
    extends LinkExceptionExtractor<NetworkException> {}

class CacheMissExceptionExtractor
    extends LinkExceptionExtractor<CacheMissException> {}

class CacheMisconfigurationExceptionExtractor
    extends LinkExceptionExtractor<CacheMisconfigurationException> {}

class MismatchedDataStructureExceptionExtractor
    extends LinkExceptionExtractor<MismatchedDataStructureException> {}

class UnexpectedResponseStructureExceptionExtractor
    extends LinkExceptionExtractor<UnexpectedResponseStructureException> {}

class OperationExceptionExtractor extends ExceptionCauseExtractor<T> {
  @override
  ExceptionCause? cause(T error) {
    return ExceptionCause(error.linkException, error.originalStackTrace);
  }
}

📣 About the author #

  • Twitter Follow
  • GitHub followers
4
likes
120
pub points
78%
popularity

Publisher

verified publisher iconuekoetter.dev

Automatic capture of exceptions and GraphQL errors for the gql eco-system, like graphql and ferry

Repository (GitHub)
View/report issues

Documentation

API reference

License

Icon for licenses.Apache-2.0 (LICENSE)

Dependencies

gql, gql_error_link, gql_exec, gql_link, sentry

More

Packages that depend on sentry_link