Skip to content

Commit 8920277

Browse files
authored
Merge pull request #18806 from mozilla/improve-gql-breadcrumbs
task(settings): Improve error handling for gql errors and network errors
2 parents a351bc7 + 92804ce commit 8920277

3 files changed

Lines changed: 92 additions & 12 deletions

File tree

packages/fxa-settings/src/lib/gql.test.ts

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Operation, NextLink, ServerError } from '@apollo/client/core';
77
import { GraphQLError } from 'graphql';
88
import { cache } from './cache';
99
import { GET_LOCAL_SIGNED_IN_STATUS } from '../components/App/gql';
10-
import sentryMetrics from 'fxa-shared/sentry/browser';
10+
import * as Sentry from '@sentry/browser';
1111

1212
describe('errorHandler', () => {
1313
beforeAll(() => {
@@ -26,8 +26,10 @@ describe('errorHandler', () => {
2626
originalError: { error: 'Unauthorized' },
2727
}),
2828
],
29-
operation: null as any as Operation,
30-
forward: null as any as NextLink,
29+
operation: {
30+
operationName: 'foo',
31+
} as Operation,
32+
forward: jest.fn(),
3133
};
3234

3335
errorHandler(errorResponse);
@@ -38,21 +40,55 @@ describe('errorHandler', () => {
3840
});
3941
});
4042

43+
it('adds breadcrumb on graphql error', () => {
44+
const gqlError = new GraphQLError('Foo', null, null, null, null, null, {
45+
originalError: { error: 'Boom' },
46+
});
47+
const errorResponse: ErrorResponse = {
48+
graphQLErrors: [gqlError],
49+
operation: {
50+
operationName: 'Foo',
51+
} as Operation,
52+
forward: jest.fn(),
53+
};
54+
55+
const addBreadcrumbMock = jest.fn();
56+
jest.spyOn(Sentry, 'addBreadcrumb').mockImplementation(addBreadcrumbMock);
57+
58+
errorHandler(errorResponse);
59+
60+
expect(addBreadcrumbMock).toHaveBeenCalledWith({
61+
category: 'graphql.error',
62+
message: 'Foo',
63+
level: 'error',
64+
data: {
65+
operation: 'Foo',
66+
graphQLError: gqlError,
67+
},
68+
});
69+
});
70+
4171
it('logs network errors to Sentry', () => {
4272
let networkError: any;
4373
networkError = new Error('Network error') as ServerError;
4474
const errorResponse: ErrorResponse = {
4575
networkError,
46-
operation: null as any as Operation,
47-
forward: null as any as NextLink,
76+
operation: {
77+
operationName: 'foo',
78+
} as any as Operation,
79+
forward: jest.fn() as NextLink,
4880
};
4981
const captureExceptionMock = jest.fn();
5082
jest
51-
.spyOn(sentryMetrics, 'captureException')
83+
.spyOn(Sentry, 'captureException')
5284
.mockImplementation(captureExceptionMock);
5385

5486
errorHandler(errorResponse);
5587

56-
expect(captureExceptionMock).toHaveBeenCalledWith(networkError);
88+
expect(captureExceptionMock).toHaveBeenCalledWith(networkError, {
89+
extra: {
90+
operation: 'foo',
91+
},
92+
});
5793
});
5894
});

packages/fxa-settings/src/lib/gql.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { BatchHttpLink } from '@apollo/client/link/batch-http';
1515
import { cache, sessionToken, typeDefs } from './cache';
1616
import { GraphQLError, GraphQLFormattedError } from 'graphql';
1717
import { GET_LOCAL_SIGNED_IN_STATUS } from '../components/App/gql';
18-
import sentryMetrics from 'fxa-shared/sentry/browser';
18+
import * as Sentry from '@sentry/browser';
1919

2020
/**
2121
* These operation names either require auth with a valid session token
@@ -77,7 +77,11 @@ const afterwareLink = new ApolloLink((operation, forward) => {
7777
});
7878
});
7979

80-
export const errorHandler: ErrorHandler = ({ graphQLErrors, networkError }) => {
80+
export const errorHandler: ErrorHandler = ({
81+
graphQLErrors,
82+
networkError,
83+
operation,
84+
}) => {
8185
if (graphQLErrors) {
8286
for (const error of graphQLErrors) {
8387
if (isUnauthorizedError(error)) {
@@ -99,16 +103,51 @@ export const errorHandler: ErrorHandler = ({ graphQLErrors, networkError }) => {
99103
data: { isSignedIn: false },
100104
});
101105
}
106+
} else {
107+
// Add error as bread crumb, so if there's a down stream exception, we can
108+
// see potential gql problems.
109+
Sentry.addBreadcrumb({
110+
category: 'graphql.error',
111+
message: error.message,
112+
level: 'error',
113+
data: {
114+
operation: operation.operationName,
115+
graphQLError: error,
116+
},
117+
});
102118
}
103119
}
104-
console.error('graphQLErrors', graphQLErrors);
105120
}
121+
106122
if (networkError) {
107-
sentryMetrics.captureException(networkError);
108-
console.error('networkError', networkError);
123+
Sentry.captureException(networkError, {
124+
extra: {
125+
operation: operation.operationName,
126+
},
127+
});
109128
}
110129
};
111130

131+
const sentryLink = new ApolloLink((operation, forward) => {
132+
const { operationName, variables } = operation;
133+
134+
return forward(operation).map((response) => {
135+
// Log the response
136+
Sentry.addBreadcrumb({
137+
category: 'graphql.response',
138+
message: operationName || 'Unnamed operation',
139+
level: 'info',
140+
data: {
141+
request: {
142+
variables,
143+
},
144+
},
145+
});
146+
147+
return response;
148+
});
149+
});
150+
112151
let apolloClientInstance: ApolloClient<NormalizedCacheObject>;
113152
export function createApolloClient(gqlServerUri: string) {
114153
if (apolloClientInstance) {
@@ -158,6 +197,7 @@ export function createApolloClient(gqlServerUri: string) {
158197
cache,
159198
link: from([
160199
errorLink,
200+
sentryLink,
161201
sessionTokenCheckLink,
162202
authLink,
163203
afterwareLink,

packages/fxa-settings/src/lib/nimbus/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export async function initializeNimbus(
5454
return (await resp.json()) as NimbusResult;
5555
}
5656
} catch (err) {
57+
// Important, if this fails it will just show up in Sentry as a
58+
// TypeError: NetworkError when attempting to fetch resource.
59+
// Look at the previous fetch bread crumb to understand what
60+
// request is actually failing.
5761
Sentry.captureException(err, {
5862
tags: {
5963
source: 'nimbus-experiments',

0 commit comments

Comments
 (0)