Skip to content

Commit 656f0de

Browse files
committed
[WIP] Wrap apollo resolvers.
1 parent ebb77e3 commit 656f0de

File tree

4 files changed

+137
-20
lines changed

4 files changed

+137
-20
lines changed

packages/tracing/src/hubextensions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ function _autoloadDatabaseIntegrations(): void {
237237
}
238238

239239
const packageToIntegrationMapping: Record<string, () => Integration> = {
240+
apollo() {
241+
const integration = dynamicRequire(module, './integrations/apollo') as {
242+
Apollo: IntegrationClass<Integration>;
243+
};
244+
return new integration.Apollo();
245+
},
240246
graphql() {
241247
const integration = dynamicRequire(module, './integrations/graphql') as {
242248
GraphQL: IntegrationClass<Integration>;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
/* eslint-disable no-console */
3+
/* eslint-disable no-debugger */
4+
import { Hub } from '@sentry/hub';
5+
import { EventProcessor, Integration } from '@sentry/types';
6+
import { fill, isThenable, loadModule, logger } from '@sentry/utils';
7+
8+
type ApolloResolverGroup = {
9+
[key: string]: () => any;
10+
};
11+
12+
type ApolloField = {
13+
[key: string]: ApolloResolverGroup;
14+
};
15+
16+
/** Tracing integration for Apollo package */
17+
export class Apollo implements Integration {
18+
/**
19+
* @inheritDoc
20+
*/
21+
public static id: string = 'Apollo';
22+
23+
/**
24+
* @inheritDoc
25+
*/
26+
public name: string = Apollo.id;
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
32+
const pkg = loadModule<{
33+
ApolloServerBase: {
34+
prototype: {
35+
constructSchema: () => unknown;
36+
};
37+
};
38+
}>(`apollo-server-core`);
39+
40+
debugger;
41+
if (!pkg) {
42+
logger.error(`Apollo Integration was unable to require apollo package.`);
43+
return;
44+
}
45+
46+
fill(pkg.ApolloServerBase.prototype, 'constructSchema', function(orig: () => void) {
47+
return function(this: { config: { resolvers: ApolloField[] } }) {
48+
this.config.resolvers = this.config.resolvers.map(field => {
49+
Object.keys(field).forEach(resolverGroupName => {
50+
Object.keys(field[resolverGroupName]).forEach(resolverName => {
51+
if (typeof field[resolverGroupName][resolverName] !== 'function') {
52+
return;
53+
}
54+
55+
patchResolver(field, resolverGroupName, resolverName, getCurrentHub);
56+
});
57+
});
58+
59+
return field;
60+
});
61+
62+
return orig.call(this);
63+
};
64+
});
65+
}
66+
}
67+
68+
/**
69+
*
70+
* @param field
71+
* @param resolverGroupName
72+
* @param resolverName
73+
*/
74+
function patchResolver(
75+
field: ApolloField,
76+
resolverGroupName: string,
77+
resolverName: string,
78+
getCurrentHub: () => Hub,
79+
): void {
80+
fill(field[resolverGroupName], resolverName, function(orig: () => unknown | Promise<unknown>) {
81+
return function(this: unknown, ...args: unknown[]) {
82+
const scope = getCurrentHub().getScope();
83+
const parentSpan = scope?.getSpan();
84+
const span = parentSpan?.startChild({
85+
description: `${resolverGroupName}.${resolverName}`,
86+
op: `apollo`,
87+
});
88+
89+
scope?.setSpan(span);
90+
91+
const rv = orig.call(this, ...args);
92+
93+
if (isThenable(rv)) {
94+
return (rv as Promise<unknown>).then((res: unknown) => {
95+
span?.finish();
96+
scope?.setSpan(parentSpan);
97+
return res;
98+
});
99+
}
100+
101+
span?.finish();
102+
scope?.setSpan(parentSpan);
103+
return rv;
104+
};
105+
});
106+
}

packages/tracing/src/integrations/graphql.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-debugger */
12
import { Hub } from '@sentry/hub';
23
import { EventProcessor, Integration } from '@sentry/types';
34
import { fill, isThenable, loadModule, logger } from '@sentry/utils';
@@ -27,30 +28,33 @@ export class GraphQL implements Integration {
2728
return;
2829
}
2930

30-
['execute', 'defaultFieldResolver', 'defaultTypeResolver', 'buildExecutionContext'].forEach(method => {
31-
fill(pkg, method, function(orig: () => void | Promise<unknown>) {
32-
return function(this: unknown, ...args: unknown[]) {
33-
const scope = getCurrentHub().getScope();
34-
const parentSpan = scope?.getSpan();
31+
fill(pkg, 'execute', function(orig: () => void | Promise<unknown>) {
32+
return function(this: unknown, ...args: unknown[]) {
33+
const hub = getCurrentHub();
34+
const scope = hub.getScope();
35+
const parentSpan = scope?.getSpan();
3536

36-
const span = parentSpan?.startChild({
37-
description: method,
38-
op: 'graphql',
39-
});
37+
const span = parentSpan?.startChild({
38+
description: 'execute',
39+
op: 'graphql',
40+
});
41+
42+
scope?.setSpan(span);
4043

41-
const rv = orig.call(this, ...args) as Promise<unknown>;
44+
const rv = orig.call(this, ...args) as Promise<unknown>;
4245

43-
if (isThenable(rv)) {
44-
return rv.then((res: unknown) => {
45-
span?.finish();
46-
return res;
47-
});
48-
} else {
46+
if (isThenable(rv)) {
47+
return rv.then((res: unknown) => {
4948
span?.finish();
50-
return rv;
51-
}
52-
};
53-
});
49+
scope?.setSpan(parentSpan);
50+
return res;
51+
});
52+
}
53+
54+
span?.finish();
55+
scope?.setSpan(parentSpan);
56+
return rv;
57+
};
5458
});
5559
}
5660
}

packages/tracing/src/integrations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { Postgres } from './node/postgres';
33
export { Mysql } from './node/mysql';
44
export { Mongo } from './node/mongo';
55
export { GraphQL } from './graphql';
6+
export { Apollo } from './apollo';
67

78
// TODO(v7): Remove this export
89
// Please see `src/index.ts` for more details.

0 commit comments

Comments
 (0)