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

Add sentry_link for GraphQL #2338

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- Transfer ownership of `sentry_link` to Sentry. You can view the changelog for the previous versions [here](https://github.com/getsentry/sentry-dart/blob/main/sentry_link/CHANGELOG_OLD.md) ([#2338](https://github.com/getsentry/sentry-dart/pull/2338))
- No functional changes have been made. This version is identical to the previos one.
- Change license from Apache to MIT

### Enhancements

- Remove `sentry` frames if SDK falls back to current stack trace ([#2351](https://github.com/getsentry/sentry-dart/pull/2351))
Expand Down Expand Up @@ -2501,4 +2505,4 @@ Until then, the stable SDK offered by Sentry is at version [3.0.1](https://githu

## 0.0.1

- basic ability to send exception reports to Sentry.io
- basic ability to send exception reports to Sentry.io
10 changes: 10 additions & 0 deletions sentry_link/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Files and directories created by pub.
.dart_tool/
.packages

# Conventional directory for build outputs.
build/

# Omit committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
1 change: 1 addition & 0 deletions sentry_link/CHANGELOG.md
66 changes: 66 additions & 0 deletions sentry_link/CHANGELOG_OLD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## 0.5.2

- Metadata updates

## 0.5.1

- Fix various pub score issues

## 0.5.0

- Require `gql_exec: ">=0.4.4 <2.0.0"`
- Remove newly unused code
- Export a couple extension methods to help converting requests and responses to their Sentry equivalents

## 0.4.0

- Require Dart 3
- Update to Sentry v8.0.0

## 0.3.0

- Proper support for GraphQL in Sentry. Sentry added proper support for GraphQL errors in with [#33723](https://github.com/getsentry/sentry/issues/33723) and this library now sends it as per spec.

## 0.2.1

- fix readme

## 0.2.0

This version contains breaking changes

- Require Sentry v7
- Instead of multiple `Link`s, there's now just a single one. See the readme for usage instructions
- Add exception extractors for unwrapping of nested `LinkException`
- Add a filter to remove duplicated http breadcrumbs. See readme for usage instructions

## 0.1.3

- Added filter for http breadcrumbs.

## 0.1.2

- Add ability to add breadcrumbs for GraphQL operations

## 0.1.2

- Update `gql` dependencies
- Add inner exceptions for event processor

## 0.1.0

- Add `SentryTracingLink` which creates performance traces
- Add `SentryResponseParser`, `SentryRequestSerializer` and `sentryResponseDecoder` which create spans for (de)serialization operations

## 0.0.3

- Fix an invalid usage of Sentry's context
- Add event processor for nested LinkExceptions

## 0.0.2

- Update dependencies and add some docs

## 0.0.1

- Initial version.
21 changes: 21 additions & 0 deletions sentry_link/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Jonas Uekötter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
251 changes: 251 additions & 0 deletions sentry_link/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# Sentry Link (GraphQL)

[![pub package](https://img.shields.io/pub/v/sentry_link.svg)](https://pub.dev/packages/sentry_link) [![likes](https://img.shields.io/pub/likes/sentry_link)](https://pub.dev/packages/sentry_link/score) [![popularity](https://img.shields.io/pub/popularity/sentry_link)](https://pub.dev/packages/sentry_link/score) [![pub points](https://img.shields.io/pub/points/sentry_link)](https://pub.dev/packages/sentry_link/score)

## Compatibility list

This integration is compatible with the following packages. It's also compatible with other packages which are build on [`gql`](https://pub.dev/publishers/gql-dart.dev/packages) suite of packages.

| package | stats |
|---------|-------|
| [`gql_link`](https://pub.dev/packages/gql_link) | <a href="https://pub.dev/packages/graphql/score"><img src="https://img.shields.io/pub/likes/gql_link" alt="likes"></a> <a href="https://pub.dev/packages/gql_link/score"><img src="https://img.shields.io/pub/popularity/gql_link" alt="popularity"></a> <a href="https://pub.dev/packages/gql_link/score"><img src="https://img.shields.io/pub/points/gql_link" alt="pub points"></a> |
| [`graphql`](https://pub.dev/packages/graphql) | <a href="https://pub.dev/packages/graphql/score"><img src="https://img.shields.io/pub/likes/graphql" alt="likes"></a> <a href="https://pub.dev/packages/graphql/score"><img src="https://img.shields.io/pub/popularity/graphql" alt="popularity"></a> <a href="https://pub.dev/packages/graphql/score"><img src="https://img.shields.io/pub/points/graphql" alt="pub points"></a> |
| [`ferry`](https://pub.dev/packages/ferry) | <a href="https://pub.dev/packages/ferry/score"><img src="https://img.shields.io/pub/likes/ferry" alt="likes"></a> <a href="https://pub.dev/packages/ferry/score"><img src="https://img.shields.io/pub/popularity/ferry" alt="popularity"></a> <a href="https://pub.dev/packages/ferry/score"><img src="https://img.shields.io/pub/points/ferry" alt="pub points"></a> |
| [`artemis`](https://pub.dev/packages/artemis) | <a href="https://pub.dev/packages/artemis/score"><img src="https://img.shields.io/pub/likes/artemis" alt="likes"></a> <a href="https://pub.dev/packages/artemis/score"><img src="https://img.shields.io/pub/popularity/artemis" alt="popularity"></a> <a href="https://pub.dev/packages/artemis/score"><img src="https://img.shields.io/pub/points/artemis" alt="pub points"></a> |

## Usage

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

```dart
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 errors will be reported as seen in the example below:

Given the following query with an error

```graphql
query LoadPosts($id: ID!) {
post(id: $id) {
id
# This word is intentionally misspelled to trigger a GraphQL error
titl
body
}
}
```

it will be represented in Sentry as seen in the image

<img src="https://raw.githubusercontent.com/getsentry/sentry-dart/main/sentry_link/screenshot.png" />

## Improve exception reports for `LinkException`s

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

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

## Performance traces for serialization and parsing

The [`SentryResponseParser`](https://pub.dev/documentation/sentry_link/latest/sentry_link/SentryResponseParser-class.html) and [`SentryRequestSerializer`](https://pub.dev/documentation/sentry_link/latest/sentry_link/SentryRequestSerializer-class.html) classes can be used to trace the de/serialization process.
Both classes work with the [`HttpLink`](https://pub.dev/packages/gql_http_link) and the [`DioLink`](https://pub.dev/packages/gql_dio_link).
When using the `HttpLink`, you can additionally use the `sentryResponseDecoder` function as explained further down below.

### Example for `HttpLink`

This example uses the [`http`](https://docs.sentry.io/platforms/dart/configuration/integrations/http-integration/#performance-monitoring-for-http-requests) integration in addition to this gql integration.

```dart
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(),
),
]);
```

### Example for `DioLink`

This example uses the [`sentry_dio`](https://pub.dev/packages/sentry_dio) integration in addition to this gql integration.

```dart
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(),
),
]);
```

<details>
<summary>HttpLink</summary>

## Bonus `HttpLink` tracing

```dart
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>?;
}
```

</details>

## Filter redundant HTTP breadcrumbs

If you use the [`sentry_dio`](https://pub.dev/packages/sentry_dio) or [`http`](https://pub.dev/documentation/sentry/latest/sentry_io/SentryHttpClient-class.html) 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`](https://pub.dev/documentation/sentry/latest/sentry_io/SentryOptions/beforeBreadcrumb.html).
```dart
return Sentry.init(
(options) {
options.beforeBreadcrumb = graphQlFilter();
// or
options.beforeBreadcrumb = graphQlFilter((breadcrumb, hint) {
// custom filter
return breadcrumb;
});
},
);
```

## Additional `graphql` usage hints

<details>
<summary>

Additional hints for usage with [`graphql`](https://pub.dev/packages/graphql)

</summary>

```dart
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);
}
}
```

</details>

# 📣 About the original author

- [![Twitter Follow](https://img.shields.io/twitter/follow/ue_man?style=social)](https://twitter.com/ue_man)
- [![GitHub followers](https://img.shields.io/github/followers/ueman?style=social)](https://github.com/ueman)
13 changes: 13 additions & 0 deletions sentry_link/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
include: package:lints/recommended.yaml

linter:
rules:
- prefer_single_quotes
- depend_on_referenced_packages
- always_use_package_imports

analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
Loading
Loading