Skip to content

Commit 3d63d16

Browse files
authored
fix(newrelic): call getTransaction from the tracing agent (ardatan#5634)
* fix(newrelic): call getTransaction from the tracing agent * Go * Go * Add newrelic as an integration test * Go * Go * 🤦
1 parent ba36a4e commit 3d63d16

File tree

9 files changed

+1590
-1109
lines changed

9 files changed

+1590
-1109
lines changed

.changeset/mean-hornets-prove.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-mesh/plugin-newrelic': patch
3+
---
4+
5+
Fix getTransaction

declarations.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ declare module 'ioredis-mock' {
1212

1313
declare module 'newrelic' {
1414
const shim: any;
15+
function startWebTransaction(url: string, callback: () => Promise<void>): void;
1516
}
1617

1718
declare module 'newrelic/*' {
1819
export = shim;
1920
}
21+
22+
declare module '@newrelic/test-utilities' {
23+
export const TestAgent: any;
24+
}

jest.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ if (process.env.LEAK_TEST) {
1818

1919
testMatch.push(process.env.INTEGRATION_TEST ? '!**/packages/**' : '!**/examples/**');
2020

21+
testMatch.push(
22+
process.env.INTEGRATION_TEST && !process.env.LEAK_TEST
23+
? '**/packages/plugins/newrelic/tests/**'
24+
: '!**/packages/plugins/newrelic/tests/**',
25+
);
26+
2127
module.exports = {
2228
testEnvironment: 'node',
2329
rootDir: ROOT_DIR,

newrelic_agent.log

+1,400
Large diffs are not rendered by default.

packages/plugins/newrelic/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@
4646
"@graphql-mesh/string-interpolation": "^0.5.0"
4747
},
4848
"devDependencies": {
49+
"@newrelic/test-utilities": "6.5.5",
4950
"@types/newrelic": "9.14.0",
5051
"graphql-yoga": "4.0.3",
51-
"newrelic": "10.3.2"
52+
"newrelic": "9.15.0"
5253
},
5354
"publishConfig": {
5455
"access": "public",

packages/plugins/newrelic/src/index.ts

+7-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable promise/param-names */
12
import { Plugin } from 'graphql-yoga';
23
import newRelic from 'newrelic';
34
import NAMES from 'newrelic/lib/metrics/names.js';
@@ -13,8 +14,8 @@ const EnvelopAttributeName = 'Envelop_NewRelic_Plugin';
1314

1415
export default function useMeshNewrelic(
1516
options: MeshPluginOptions<YamlConfig.NewrelicConfig>,
17+
{ instrumentationApi = newRelic?.shim, agentApi = newRelic }: any = {},
1618
): MeshPlugin<any> & Plugin<any> {
17-
const instrumentationApi = newRelic?.shim;
1819
if (!instrumentationApi?.agent) {
1920
options.logger.debug(
2021
'Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.',
@@ -29,13 +30,13 @@ export default function useMeshNewrelic(
2930
const logger = instrumentationApi.logger.child({ component: EnvelopAttributeName });
3031

3132
const segmentByRequestContext = new WeakMap<any, any>();
32-
const transactionCallback = new WeakMap<Request, () => void>();
3333

3434
return {
3535
onPluginInit({ addPlugin }) {
3636
addPlugin(
3737
useNewRelic({
3838
...options,
39+
shim: instrumentationApi,
3940
extractOperationName: options.extractOperationName
4041
? context =>
4142
stringInterpolator.parse(options.extractOperationName, {
@@ -46,12 +47,11 @@ export default function useMeshNewrelic(
4647
}),
4748
);
4849
},
49-
onRequest({ request, url }) {
50-
const currentTransaction = instrumentationApi.getTransaction();
50+
onRequest({ url, requestHandler, setRequestHandler }) {
51+
const currentTransaction = instrumentationApi.agent.tracer.getTransaction();
5152
if (!currentTransaction) {
52-
instrumentationApi.startWebTransaction(
53-
url.pathname,
54-
() => new Promise<void>(resolve => transactionCallback.set(request, resolve)),
53+
setRequestHandler((...args) =>
54+
agentApi.startWebTransaction(url.pathname, () => requestHandler(...args)),
5555
);
5656
}
5757
},
@@ -157,11 +157,5 @@ export default function useMeshNewrelic(
157157
httpDetailSegment.end();
158158
};
159159
},
160-
onResponse({ request }) {
161-
const callback = transactionCallback.get(request);
162-
if (callback) {
163-
callback();
164-
}
165-
},
166160
};
167161
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
3+
import LocalforageCache from '@graphql-mesh/cache-localforage';
4+
import { createMeshHTTPHandler, MeshHTTPHandler } from '@graphql-mesh/http';
5+
import type { MeshInstance } from '@graphql-mesh/runtime';
6+
import { defaultImportFn, DefaultLogger, PubSub } from '@graphql-mesh/utils';
7+
import { TestAgent } from '@newrelic/test-utilities';
8+
import { getTestMesh } from '../../../testing/getTestMesh';
9+
import useMeshNewRelic from '../src';
10+
11+
describe('New Relic', () => {
12+
let mesh: MeshInstance;
13+
let helper: any;
14+
let httpHandler: MeshHTTPHandler;
15+
beforeAll(async () => {
16+
const logger = new DefaultLogger('New Relic Test');
17+
const cache = new LocalforageCache();
18+
const pubsub = new PubSub();
19+
const baseDir = __dirname;
20+
helper = TestAgent.makeInstrumented();
21+
mesh = await getTestMesh({
22+
logger,
23+
cache,
24+
pubsub,
25+
additionalEnvelopPlugins: [
26+
useMeshNewRelic(
27+
{
28+
logger,
29+
cache,
30+
pubsub,
31+
baseDir,
32+
importFn: defaultImportFn,
33+
},
34+
{
35+
instrumentationApi: helper.getShim(),
36+
agentApi: helper.getAgentApi(),
37+
},
38+
),
39+
],
40+
});
41+
httpHandler = createMeshHTTPHandler({
42+
baseDir,
43+
getBuiltMesh: async () => mesh,
44+
});
45+
});
46+
afterAll(async () => {
47+
mesh.destroy();
48+
return helper.unload();
49+
});
50+
it('should work', async () => {
51+
const response = await httpHandler.fetch('/graphql', {
52+
method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/json',
55+
Accept: 'application/json',
56+
},
57+
body: JSON.stringify({
58+
query: /* GraphQL */ `
59+
query Test {
60+
greetings
61+
}
62+
`,
63+
}),
64+
});
65+
const result = await response.json();
66+
expect(result).toEqual({
67+
data: {
68+
greetings: 'This is the `greetings` field of the root `Query` type',
69+
},
70+
});
71+
});
72+
});

packages/testing/getTestMesh.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { createSchema, createYoga, Repeater } from 'graphql-yoga';
55
import LocalforageCache from '@graphql-mesh/cache-localforage';
66
import GraphQLHandler from '@graphql-mesh/graphql';
77
import StitchingMerger from '@graphql-mesh/merger-stitching';
8-
import { getMesh } from '@graphql-mesh/runtime';
8+
import { getMesh, GetMeshOptions } from '@graphql-mesh/runtime';
99
import { InMemoryStoreStorageAdapter, MeshStore } from '@graphql-mesh/store';
1010
import { defaultImportFn, DefaultLogger, PubSub } from '@graphql-mesh/utils';
1111

12-
export function getTestMesh() {
12+
export function getTestMesh(extraOptions?: Partial<GetMeshOptions>) {
1313
const yoga = createYoga({
1414
logging: false,
1515
schema: createSchema({
@@ -64,6 +64,7 @@ export function getTestMesh() {
6464
importFn: defaultImportFn,
6565
}),
6666
},
67+
...(extraOptions?.sources || []),
6768
],
6869
cache,
6970
pubsub,
@@ -73,5 +74,6 @@ export function getTestMesh() {
7374
logger,
7475
store: store.child('Stitching'),
7576
}),
77+
...extraOptions,
7678
});
7779
}

0 commit comments

Comments
 (0)