Skip to content

Commit

Permalink
feat: add helpers to exclude GraphQL fetch breadcrumbs (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
spawnia authored Feb 4, 2021
1 parent c66c22f commit a8fa792
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 15 deletions.
52 changes: 37 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,43 @@ In case you find that there's a piece of data you're missing, feel free to open
### Be careful what you include
Please note that Sentry sets some limits to how big events can be. For instance, **events greater than 200KiB are immediately dropped (pre decompression)**. More information on that [here](https://docs.sentry.io/accounts/quotas/#attributes-limits). Be especially careful with the `includeCache` option, as caches can become quite large.

Furthermore, much of the data you are sending to Sentry can include (sensitive) personal information. This might lead you to violating the terms of the GDPR. Use Sentry's `beforeBreadrcrumb` function to filter out all sensitive data.
Furthermore, much of the data you are sending to Sentry can include (sensitive) personal information. This might lead you to violating the terms of the GDPR. Use Sentry's `beforeBreadcrumb` function to filter out all sensitive data.

## Exclude redundant `fetch` breadcrumbs
By default, Sentry attaches all fetch events as breadcrumbs. Since this package tracks GraphQL requests as breadcrumbs,
they would show up duplicated in Sentry.

1. Disable the default integration for fetch requests. Note that this is only recommended if you **only** use GraphQL requests in your application. The default integration can be disabled like this:
```js
Sentry.init({
...,
defaultIntegrations: [
new Sentry.BrowserTracing({ traceFetch: false }),
],
});
```

2. Use the `beforeBreadcrumb` option of Sentry to filter out the duplicates.
The helpers in this package recognize every breadcrumb of category `fetch` where the URL contains `/graphql` as a GraphQL request.
```js
import { excludeGraphQLFetch } from 'apollo-link-sentry';

Sentry.init({
...,
beforeBreadcrumb: excludeGraphQLFetch,
})
```

If you have a custom wrapper, use the higher order function:

```js
import { withoutGraphQLFetch } from 'apollo-link-sentry';

Sentry.init({
...,
beforeBreadcrumb: withoutGraphQLFetch((breadcrumb, hint) => { ... }),
})
```

## FAQ
- **I don't see any events appearing in my Sentry stream**
Expand All @@ -145,26 +181,12 @@ Furthermore, much of the data you are sending to Sentry can include (sensitive)
}}
</Mutation>
```
- **GraphQL operations are also logged as fetch breadcrumbs**
- Sentry by default attaches all fetch events as breadcrumbs. This means that there are two ways to ensure GraphQL operations appear but once:
1. Disable the default integration for fetch requests. Note that this is only recommended if you **only** use GraphQL requests in your application. The default integration can be disabled like this:
```js
Sentry.init({
dsn: '',
defaultIntegrations: [
new Sentry.Integrations.Breadcrumbs({ fetch: false }),
],
});
```
2. Otherwise, it will be possible to use the `beforeBreadcrumb` option of Sentry to filter out the duplicates. This feature is not yet implemented in this package, but it is on the roadmap (see below).

## Caveats
- This package has not been tested for subscriptions
- We also need to test for different links, i.e. `apollo-link-rest`

## Roadmap / notes
- Provide wrapper for Sentry's beforeBreadcrumb to filter out fetch requests
- Caveat: people using `unfetch`?
- Write best practice scenario:
- setting `includeError` true
- catch errors manually
Expand Down
28 changes: 28 additions & 0 deletions src/excludeGraphQLFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { BrowserOptions } from '@sentry/browser';

type BeforeBreadcrumbCallback = NonNullable<BrowserOptions['beforeBreadcrumb']>;

export const excludeGraphQLFetch: BeforeBreadcrumbCallback = (breadcrumb) => {
if (breadcrumb.category === 'fetch') {
const url: string = breadcrumb.data?.url ?? '';

if (url.includes('/graphql')) {
return null;
}
}

return breadcrumb;
};

export const withoutGraphQLFetch = (
beforeBreadcrumb: BeforeBreadcrumbCallback
): BeforeBreadcrumbCallback => {
return (breadcrumb, hint) => {
const withoutFetch = excludeGraphQLFetch(breadcrumb, hint);
if (withoutFetch === null) {
return null;
}

return beforeBreadcrumb(withoutFetch, hint);
};
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { Operation } from './Operation';
export * from './excludeGraphQLFetch';
export * from './SentryLink';
56 changes: 56 additions & 0 deletions tests/excludeGraphQLFetch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { excludeGraphQLFetch, withoutGraphQLFetch } from '../src';

describe('excludeGraphQLFetch', () => {
it('should remove fetch operations on GraphQL endpoints', () => {
expect(
excludeGraphQLFetch({
category: 'fetch',
data: { url: 'https://example.com/graphql' },
})
).toBeNull();
});

it('should leave non-GraphQL fetches', () => {
const breadcrumb = {
category: 'fetch',
data: { url: 'https://example.com' },
};
expect(excludeGraphQLFetch(breadcrumb)).toEqual(breadcrumb);
});

it('should leave non-fetch breadcrumbs', () => {
const breadcrumb = { category: 'not-fetch' };
expect(excludeGraphQLFetch(breadcrumb)).toEqual(breadcrumb);
});
});

describe('withoutGraphQLFetch', () => {
it('should wrap custom callback and short circuit when filtering out fetches', () => {
const callback = jest.fn();

const wrapped = withoutGraphQLFetch(callback);

expect(
wrapped({
category: 'fetch',
data: { url: 'https://example.com/graphql' },
})
).toBeNull();
expect(callback).not.toHaveBeenCalled();
});

it('should pass non-fetches and the hint along to callback', () => {
const initial = { category: 'not-fetch' };
const hint = { foo: 'bar' };

const callback = jest.fn();
const altered = { category: 'altered' };
callback.mockReturnValue(altered);

const wrapped = withoutGraphQLFetch(callback);

expect(wrapped(initial, hint)).toEqual(altered);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(initial, hint);
});
});

0 comments on commit a8fa792

Please sign in to comment.