diff --git a/package.json b/package.json index 5e8b7e0..5da47e9 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@apollo/client": "^3.2.3", - "@sentry/browser": "^5.26.0", + "@sentry/browser": "^6.0.4", "@sentry/minimal": "^5.26.0", "@sentry/types": "^5.26.0", "@types/jest": "^26.0.14", @@ -55,8 +55,8 @@ "sentry-testkit": "^3.2.1", "standard-version": "^9.0.0", "ts-jest": "^26.4.1", - "tslib": "^2.0.3", "tsc-watch": "~4.2.9", + "tslib": "^2.0.3", "typescript": "^4.0.3", "zen-observable": "^0.8.15" } diff --git a/src/SentryLink.ts b/src/SentryLink.ts index ea2fa89..c498a8f 100644 --- a/src/SentryLink.ts +++ b/src/SentryLink.ts @@ -1,16 +1,20 @@ -import { Scope, Severity } from '@sentry/types'; +import { Scope, Severity, Span } from '@sentry/types'; import deepMerge from 'deepmerge'; import Observable from 'zen-observable'; import { FetchResult } from '@apollo/client/link/core/types'; import { - ApolloLink, NextLink, Operation as ApolloOperation, + ApolloLink, + NextLink, + Operation as ApolloOperation, } from '@apollo/client/link/core'; import { addBreadcrumb, configureScope } from '@sentry/minimal'; +import { getCurrentHub } from '@sentry/browser'; import { OperationBreadcrumb } from './OperationBreadcrumb'; import { Operation } from './Operation'; +import { SubscriptionObserver } from 'zen-observable/esm'; export interface Options { setTransaction?: boolean; @@ -24,7 +28,7 @@ export interface Options { includeResponse?: boolean; includeError?: boolean; includeContextKeys?: string[]; - } + }; filter?: (operation: Operation) => boolean; beforeBreadcrumb?: (breadcrumb: OperationBreadcrumb) => OperationBreadcrumb; @@ -50,7 +54,6 @@ export class SentryLink extends ApolloLink { /** * Create a new ApolloLinkSentry - * @param {Options} options */ constructor(options: Options = {}) { super(); @@ -60,14 +63,22 @@ export class SentryLink extends ApolloLink { /** * This is where the GraphQL operation is received * A breadcrumb will be created for the operation, and error/response data will be handled - * @param {ApolloOperation} op - * @param {NextLink} forward - * @returns {Observable | null} */ - request = (op: ApolloOperation, forward: NextLink): Observable | null => { + request = ( + op: ApolloOperation, + forward: NextLink + ): Observable | null => { // Obtain necessary data from the operation const operation = new Operation(op); + let span: Span | undefined; + const transaction = getCurrentHub().getScope()?.getTransaction(); + if (transaction !== undefined) { + span = transaction.startChild({ + op: operation.name, + }); + } + // Create a new breadcrumb for this specific operation const breadcrumb = new OperationBreadcrumb(); this.fillBreadcrumb(breadcrumb, operation); @@ -75,9 +86,10 @@ export class SentryLink extends ApolloLink { // Start observing the operation for results return new Observable((observer) => { const subscription = forward(op).subscribe({ - next: (result: FetchResult) => this.handleResult(result, breadcrumb, observer), - complete: () => this.handleComplete(breadcrumb, observer), - error: (error: any) => this.handleError(breadcrumb, error, observer), + next: (result: FetchResult) => + this.handleResult(result, breadcrumb, observer), + error: (error) => this.handleError(breadcrumb, error, observer), + complete: () => this.handleComplete(breadcrumb, observer, span), }); // Close the subscription @@ -90,19 +102,18 @@ export class SentryLink extends ApolloLink { /** * Fill the breadcrumb with information, respecting the provided options * The breadcrumb is not yet attached to Sentry after this method - * @param {OperationBreadcrumb} breadcrumb - * @param {Operation} operation */ - fillBreadcrumb = (breadcrumb: OperationBreadcrumb, operation: Operation): void => { + fillBreadcrumb = ( + breadcrumb: OperationBreadcrumb, + operation: Operation + ): void => { // Apply the filter option if (typeof this.options.filter === 'function') { const stop = breadcrumb.filter(this.options.filter(operation)); if (stop) return; } - breadcrumb - .setMessage(operation.name) - .setCategory(operation.type); + breadcrumb.setMessage(operation.name).setCategory(operation.type); // TODO: Maybe move this to a different place? It isn't a breadcrumb if (this.options.setTransaction) { @@ -127,18 +138,21 @@ export class SentryLink extends ApolloLink { } if (this.options?.breadcrumb?.includeContextKeys?.length) { - breadcrumb.setContext(operation.getContextKeys(this.options.breadcrumb.includeContextKeys)); + breadcrumb.setContext( + operation.getContextKeys(this.options.breadcrumb.includeContextKeys) + ); } }; /** * Handle the operation's response * The breadcrumb is not yet attached to Sentry after this method - * @param {FetchResult} result - * @param {OperationBreadcrumb} breadcrumb - * @param observer */ - handleResult = (result: FetchResult, breadcrumb: OperationBreadcrumb, observer: any): void => { + handleResult = ( + result: FetchResult, + breadcrumb: OperationBreadcrumb, + observer: SubscriptionObserver + ): void => { if (this.options.breadcrumb?.includeResponse) { breadcrumb.setResponse(result); } @@ -150,14 +164,13 @@ export class SentryLink extends ApolloLink { * Changes the level and type of the breadcrumb to `error` * Furthermore, if the includeError option is truthy, the error data will be attached * Then, the error will be attached to Sentry - * @param {OperationBreadcrumb} breadcrumb - * @param error - * @param observer */ - handleError = (breadcrumb: OperationBreadcrumb, error: any, observer: any): void => { - breadcrumb - .setLevel(Severity.Error) - .setType('error'); + handleError = ( + breadcrumb: OperationBreadcrumb, + error: unknown, + observer: SubscriptionObserver + ): void => { + breadcrumb.setLevel(Severity.Error).setType('error'); if (this.options.breadcrumb?.includeError) { breadcrumb.setError(error); @@ -170,17 +183,20 @@ export class SentryLink extends ApolloLink { /** * Since no error occurred, it is time to attach the breadcrumb to Sentry - * @param {OperationBreadcrumb} breadcrumb - * @param observer */ - handleComplete = (breadcrumb: OperationBreadcrumb, observer: any): void => { + handleComplete = ( + breadcrumb: OperationBreadcrumb, + observer: SubscriptionObserver, + span?: Span + ): void => { this.attachBreadcrumbToSentry(breadcrumb); + + span?.finish(); observer.complete(); }; /** * Set the Sentry transaction - * @param {Operation} operation */ setTransaction = (operation: Operation): void => { configureScope((scope: Scope) => { @@ -193,16 +209,12 @@ export class SentryLink extends ApolloLink { */ setFingerprint = (): void => { configureScope((scope: Scope) => { - scope.setFingerprint([ - '{{default}}', - '{{transaction}}', - ]); + scope.setFingerprint(['{{default}}', '{{transaction}}']); }); }; /** * Attach the breadcrumb to the Sentry event - * @param {OperationBreadcrumb} breadcrumb */ attachBreadcrumbToSentry = (breadcrumb: OperationBreadcrumb): void => { // Apply options @@ -210,7 +222,9 @@ export class SentryLink extends ApolloLink { if (breadcrumb.filtered) return; if (breadcrumb.flushed) { - console.warn('[apollo-link-sentry] SentryLink.attachBreadcrumbToSentry() was called on an already flushed breadcrumb'); + console.warn( + '[apollo-link-sentry] SentryLink.attachBreadcrumbToSentry() was called on an already flushed breadcrumb' + ); return; } diff --git a/yarn.lock b/yarn.lock index bb03d28..cd37ed9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -489,25 +489,25 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@sentry/browser@^5.26.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" - integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== - dependencies: - "@sentry/core" "5.30.0" - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" +"@sentry/browser@^6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.0.4.tgz#f31c0a9e7b22638cff9da70aa96c7934a18a2059" + integrity sha512-DrlH53IPNZmW6XWT0Za7vGtIyKpm45An662xvXavI8LQQH0qhPU9mb7NcvecwDfs6jXEV2w5Y8rKjuu/J4QxAA== + dependencies: + "@sentry/core" "6.0.4" + "@sentry/types" "6.0.4" + "@sentry/utils" "6.0.4" tslib "^1.9.3" -"@sentry/core@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" - integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg== +"@sentry/core@6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.0.4.tgz#b8d41528309531335407efd9785206aa020b2271" + integrity sha512-5+Xnk3jb0nkKYvgBV/kKWUqrNsNeM38r98ZRqfHrl69WoSrv+ynTsj8gn0tZO+VvhxUDRLOYvDha+QZgkYZt/w== dependencies: - "@sentry/hub" "5.30.0" - "@sentry/minimal" "5.30.0" - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" + "@sentry/hub" "6.0.4" + "@sentry/minimal" "6.0.4" + "@sentry/types" "6.0.4" + "@sentry/utils" "6.0.4" tslib "^1.9.3" "@sentry/hub@5.29.2": @@ -519,22 +519,22 @@ "@sentry/utils" "5.29.2" tslib "^1.9.3" -"@sentry/hub@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" - integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ== +"@sentry/hub@6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.0.4.tgz#b13eac8fe4d4822dc4f997a415e9cfe8cc52fbd9" + integrity sha512-gutuxH8M3CdElSbwqNq9G29MiNuGsPESB22w4k4wx+pc632bi6w0v53+BLjGO6wh2EMfHVWptgAYmojEk5yKQg== dependencies: - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" + "@sentry/types" "6.0.4" + "@sentry/utils" "6.0.4" tslib "^1.9.3" -"@sentry/minimal@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b" - integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw== +"@sentry/minimal@6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.0.4.tgz#5a08ac6b0899fa5db409b1a5b888740f769cb6a5" + integrity sha512-COL0tjznrCaatOPH2eGgT1Y9vUUKJw+W0srCn5V1dHgRu3t00rGFXrcyOXQmHfEWmBaagt9lXEJCFaN7yMucVQ== dependencies: - "@sentry/hub" "5.30.0" - "@sentry/types" "5.30.0" + "@sentry/hub" "6.0.4" + "@sentry/types" "6.0.4" tslib "^1.9.3" "@sentry/minimal@^5.26.0": @@ -551,7 +551,12 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.29.2.tgz#ac87383df1222c2d9b9f8f9ed7a6b86ea41a098a" integrity sha512-dM9wgt8wy4WRty75QkqQgrw9FV9F+BOMfmc0iaX13Qos7i6Qs2Q0dxtJ83SoR4YGtW8URaHzlDtWlGs5egBiMA== -"@sentry/types@5.30.0", "@sentry/types@^5.26.0": +"@sentry/types@6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.0.4.tgz#762949dc4ba25f4071c405f67ddc28c84b6dc08f" + integrity sha512-VqmnhJPpPmsu4gMzSZw8UHgYlP1QSikMZ5X6E3q6zwmbWu+2oniQHD6xGB6PXv6uTo5zg2NseQEiWnEjJRUYWw== + +"@sentry/types@^5.26.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== @@ -564,12 +569,12 @@ "@sentry/types" "5.29.2" tslib "^1.9.3" -"@sentry/utils@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" - integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww== +"@sentry/utils@6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.0.4.tgz#92ed5c114c633b8b59ae6fae70bd8bbfc88e302f" + integrity sha512-UOAz5p5IIntmIcmX04Cjk7l7+EwnuBn2S/rhNN92I1vDCaL010OmUZOHGHJExoXBE75zVh/LDssAPQTKXo0F+g== dependencies: - "@sentry/types" "5.30.0" + "@sentry/types" "6.0.4" tslib "^1.9.3" "@sinonjs/commons@^1.7.0":