Skip to content

Commit d4c2ab8

Browse files
committed
Revert to observable wrapping and class API
1 parent 5aabb97 commit d4c2ab8

File tree

5 files changed

+213
-146
lines changed

5 files changed

+213
-146
lines changed

README.md

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export interface ApolloLinkSentryOptions {
4848
*/
4949
shouldHandleOperation: undefined | ((operation: Operation) => boolean);
5050

51+
/**
52+
* The uri of the GraphQL endpoint.
53+
*
54+
* Used to add context information, e.g. to breadcrumbs.
55+
*/
56+
uri: undefined | string;
57+
5158
/**
5259
* Set the Sentry transaction name to the GraphQL operation name.
5360
*
@@ -78,57 +85,57 @@ export interface ApolloLinkSentryOptions {
7885
}
7986

8087
export type AttachBreadcrumbsOptions = {
81-
/**
82-
* Include the full query string?
83-
*/
84-
includeQuery: false | true;
85-
86-
/**
87-
* Include the variable values?
88-
*
89-
* Be careful not to leak sensitive information or send too much data.
90-
*/
91-
includeVariables: false | true;
92-
93-
/**
94-
* Include the fetched result (data, errors, extensions)?
95-
*
96-
* Be careful not to leak sensitive information or send too much data.
97-
*/
98-
includeResult: false | true;
99-
100-
/**
101-
* Include the response error?
102-
*
103-
* Be careful not to leak sensitive information or send too much data.
104-
*/
105-
includeError: false | true;
106-
107-
/**
108-
* Include the contents of the Apollo Client cache?
109-
*
110-
* This is mostly useful for debugging purposes and not recommended for production environments,
111-
* see "Be careful what you include", unless carefully combined with `beforeBreadcrumb`.
112-
*/
113-
includeCache: false | true;
114-
115-
/**
116-
* Include arbitrary data from the `ApolloContext`?
117-
*
118-
* Accepts a list of keys in dot notation, e.g. `foo.bar`. Can be useful to include extra
119-
* information such as headers.
120-
*/
121-
includeContext: false | NonEmptyArray<string>;
122-
123-
/**
124-
* Modify the breadcrumb right before it is sent.
125-
*
126-
* Can be used to add additional data from the operation or clean up included data.
127-
* Very useful in combination with options like `includeVariables` and `includeContextKeys`.
128-
*/
129-
transform:
130-
| undefined
131-
| ((breadcrumb: Breadcrumb, operation: Operation) => Breadcrumb);
88+
/**
89+
* Include the full query string?
90+
*/
91+
includeQuery: false | true;
92+
93+
/**
94+
* Include the variable values?
95+
*
96+
* Be careful not to leak sensitive information or send too much data.
97+
*/
98+
includeVariables: false | true;
99+
100+
/**
101+
* Include the fetched result (data, errors, extensions)?
102+
*
103+
* Be careful not to leak sensitive information or send too much data.
104+
*/
105+
includeResult: false | true;
106+
107+
/**
108+
* Include the response error?
109+
*
110+
* Be careful not to leak sensitive information or send too much data.
111+
*/
112+
includeError: false | true;
113+
114+
/**
115+
* Include the contents of the Apollo Client cache?
116+
*
117+
* This is mostly useful for debugging purposes and not recommended for production environments,
118+
* see "Be careful what you include", unless carefully combined with `beforeBreadcrumb`.
119+
*/
120+
includeCache: false | true;
121+
122+
/**
123+
* Include arbitrary data from the `ApolloContext`?
124+
*
125+
* Accepts a list of keys in dot notation, e.g. `foo.bar`. Can be useful to include extra
126+
* information such as headers.
127+
*/
128+
includeContext: false | NonEmptyArray<string>;
129+
130+
/**
131+
* Modify the breadcrumb right before it is sent.
132+
*
133+
* Can be used to add additional data from the operation or clean up included data.
134+
* Very useful in combination with options like `includeVariables` and `includeContextKeys`.
135+
*/
136+
transform:
137+
| undefined
138+
| ((breadcrumb: GraphQLBreadcrumb, operation: Operation) => Breadcrumb);
132139
};
133140
```
134141

src/SentryLink.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {
2+
ApolloLink,
3+
FetchResult,
4+
NextLink,
5+
Operation,
6+
} from '@apollo/client/core';
7+
import { Severity } from '@sentry/types';
8+
import Observable from 'zen-observable';
9+
10+
import { GraphQLBreadcrumb, makeBreadcrumb } from './breadcrumb';
11+
import { ApolloLinkSentryOptions, Options, withDefaults } from './options';
12+
import {
13+
attachBreadcrumbToSentry,
14+
setFingerprint,
15+
setTransaction,
16+
} from './sentry';
17+
18+
export class SentryLink extends ApolloLink {
19+
private readonly options: ApolloLinkSentryOptions;
20+
21+
constructor(options: Options = {}) {
22+
super();
23+
this.options = withDefaults(options);
24+
}
25+
26+
request(
27+
operation: Operation,
28+
forward: NextLink,
29+
): Observable<FetchResult> | null {
30+
if (typeof this.options.shouldHandleOperation === 'function') {
31+
if (!this.options.shouldHandleOperation(operation)) {
32+
return forward(operation);
33+
}
34+
}
35+
36+
if (this.options.setTransaction) {
37+
setTransaction(operation);
38+
}
39+
40+
if (this.options.setFingerprint) {
41+
setFingerprint(operation);
42+
}
43+
44+
const breadcrumb = this.options.attachBreadcrumbs
45+
? makeBreadcrumb(operation, this.options)
46+
: undefined;
47+
48+
// While this could be done more simplistically by simply subscribing,
49+
// wrapping the observer in our own observer ensures we get the results
50+
// before they are passed along to other observers. This guarantees we
51+
// get to run our instrumentation before others observers potentially
52+
// throw and thus flush the results to Sentry.
53+
return new Observable<FetchResult>((originalObserver) => {
54+
const subscription = forward(operation).subscribe({
55+
next: (result) => {
56+
if (this.options.attachBreadcrumbs) {
57+
// We must have a breadcrumb if attachBreadcrumbs was set
58+
(breadcrumb as GraphQLBreadcrumb).level = severityForResult(result);
59+
60+
if (this.options.attachBreadcrumbs.includeResult) {
61+
// We must have a breadcrumb if attachBreadcrumbs was set
62+
(breadcrumb as GraphQLBreadcrumb).data.fetchResult = result;
63+
}
64+
}
65+
66+
originalObserver.next(result);
67+
},
68+
complete: () => {
69+
if (this.options.attachBreadcrumbs) {
70+
attachBreadcrumbToSentry(
71+
operation,
72+
// We must have a breadcrumb if attachBreadcrumbs was set
73+
breadcrumb as GraphQLBreadcrumb,
74+
this.options,
75+
);
76+
}
77+
78+
originalObserver.complete();
79+
},
80+
error: (error) => {
81+
if (this.options.attachBreadcrumbs) {
82+
// We must have a breadcrumb if attachBreadcrumbs was set
83+
(breadcrumb as GraphQLBreadcrumb).level = Severity.Error;
84+
85+
if (this.options.attachBreadcrumbs.includeError) {
86+
// We must have a breadcrumb if attachBreadcrumbs was set
87+
(breadcrumb as GraphQLBreadcrumb).data.error = error;
88+
}
89+
90+
attachBreadcrumbToSentry(
91+
operation,
92+
// We must have a breadcrumb if attachBreadcrumbs was set
93+
breadcrumb as GraphQLBreadcrumb,
94+
this.options,
95+
);
96+
}
97+
98+
originalObserver.error(error);
99+
},
100+
});
101+
102+
return () => {
103+
subscription.unsubscribe();
104+
};
105+
});
106+
}
107+
}
108+
109+
function severityForResult(result: FetchResult): Severity {
110+
return result.errors && result.errors.length > 0
111+
? Severity.Error
112+
: Severity.Info;
113+
}

src/apolloSentryLink.ts

Lines changed: 0 additions & 82 deletions
This file was deleted.

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { makeSentryLink } from './apolloSentryLink';
1+
export { SentryLink } from './SentryLink';
22
export { Options } from './options';
33
export { GraphQLBreadcrumb } from './breadcrumb';
44
export {

0 commit comments

Comments
 (0)