@envelop/newrelic 5.0.4 → 5.0.5-alpha-20230104113641-ef3d8f5b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,13 +6,13 @@ Take advantage of Distributed tracing to monitor performance and errors whilst u
6
6
  Below are some screenshots to show how tracking looks like in New Relic, in these examples all plugin options were set to `true`.
7
7
 
8
8
  Error tracking, operation and resolver views:
9
- ![newrelic_error_operation_screenshot](./error_operation.png)
10
- ![newrelic_error_resolver_screenshot](./error_resolver.png)
9
+ ![newrelic_error_operation_screenshot](https://raw.githubusercontent.com/n1ru4l/envelop/519f9c34435914fca63d64c9135812825d793440/packages/plugins/newrelic/error_operation.png)
10
+ ![newrelic_error_resolver_screenshot](https://raw.githubusercontent.com/n1ru4l/envelop/0af1f27e55c197e1d2ae1f451cb1117b36b2a8a9/packages/plugins/newrelic/error_resolver.png)
11
11
 
12
12
  Successful operation tracking, operation plus root-field and sub-field resolvers:
13
- ![newrelic_successful_operation_screenshot](./success_operation.png)
14
- ![newrelic_successful_root-field_resolver_screenshot](./success_rootfield_resolver.png)
15
- ![newrelic_successful_sub-field_resolver_screenshot](./success_subfield_resolver.png)
13
+ ![newrelic_successful_operation_screenshot](https://raw.githubusercontent.com/n1ru4l/envelop/0af1f27e55c197e1d2ae1f451cb1117b36b2a8a9/packages/plugins/newrelic/success_operation.png)
14
+ ![newrelic_successful_root-field_resolver_screenshot](https://raw.githubusercontent.com/n1ru4l/envelop/0af1f27e55c197e1d2ae1f451cb1117b36b2a8a9/packages/plugins/newrelic/success_rootfield_resolver.png)
15
+ ![newrelic_successful_sub-field_resolver_screenshot](https://raw.githubusercontent.com/n1ru4l/envelop/0af1f27e55c197e1d2ae1f451cb1117b36b2a8a9/packages/plugins/newrelic/success_subfield_resolver.png)
16
16
 
17
17
  ## Getting Started
18
18
 
package/cjs/index.js CHANGED
@@ -1,32 +1,11 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
3
  exports.useNewRelic = void 0;
4
+ const tslib_1 = require("tslib");
27
5
  const core_1 = require("@envelop/core");
28
6
  const on_resolve_1 = require("@envelop/on-resolve");
29
7
  const graphql_1 = require("graphql");
8
+ const newrelic_1 = tslib_1.__importDefault(require("newrelic"));
30
9
  var AttributeName;
31
10
  (function (AttributeName) {
32
11
  AttributeName["COMPONENT_NAME"] = "Envelop_NewRelic_Plugin";
@@ -58,29 +37,23 @@ const useNewRelic = (rawOptions) => {
58
37
  };
59
38
  options.isExecuteVariablesRegex = options.includeExecuteVariables instanceof RegExp;
60
39
  options.isResolverArgsRegex = options.includeResolverArgs instanceof RegExp;
61
- const instrumentationApi$ = Promise.resolve().then(() => __importStar(require('newrelic'))).then(m => m.default || m)
62
- .then(({ shim }) => {
63
- if (!shim?.agent) {
64
- throw new Error('Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.');
65
- }
66
- shim.agent.metrics
67
- .getOrCreateMetric(`Supportability/ExternalModules/${AttributeName.COMPONENT_NAME}`)
68
- .incrementCallCount();
69
- return shim;
70
- });
71
- const logger$ = instrumentationApi$.then(({ logger }) => {
72
- const childLogger = logger.child({ component: AttributeName.COMPONENT_NAME });
73
- childLogger.info(`${AttributeName.COMPONENT_NAME} registered`);
74
- return childLogger;
75
- });
40
+ const instrumentationApi = newrelic_1.default?.shim;
41
+ if (!instrumentationApi?.agent) {
42
+ console.warn('Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.');
43
+ return {};
44
+ }
45
+ instrumentationApi.agent.metrics
46
+ .getOrCreateMetric(`Supportability/ExternalModules/${AttributeName.COMPONENT_NAME}`)
47
+ .incrementCallCount();
48
+ const logger = instrumentationApi.logger.child({ component: AttributeName.COMPONENT_NAME });
49
+ logger.info(`${AttributeName.COMPONENT_NAME} registered`);
50
+ const documentSourceMap = new WeakMap();
76
51
  return {
77
52
  onPluginInit({ addPlugin }) {
78
53
  if (options.trackResolvers) {
79
- addPlugin((0, on_resolve_1.useOnResolve)(async ({ args: resolversArgs, info }) => {
80
- const instrumentationApi = await instrumentationApi$;
54
+ addPlugin((0, on_resolve_1.useOnResolve)(({ args: resolversArgs, info }) => {
81
55
  const transactionNameState = instrumentationApi.agent.tracer.getTransaction().nameState;
82
56
  const delimiter = transactionNameState.delimiter;
83
- const logger = await logger$;
84
57
  const { returnType, path, parentType } = info;
85
58
  const formattedPath = flattenPath(path, delimiter);
86
59
  const currentSegment = instrumentationApi.getActiveSegment();
@@ -113,51 +86,71 @@ const useNewRelic = (rawOptions) => {
113
86
  }));
114
87
  }
115
88
  },
116
- async onExecute({ args }) {
117
- const instrumentationApi = await instrumentationApi$;
118
- const transactionNameState = instrumentationApi.agent.tracer.getTransaction().nameState;
119
- const spanContext = instrumentationApi.agent.tracer.getSpanContext();
120
- const delimiter = transactionNameState.delimiter;
121
- const rootOperation = args.document.definitions.find(
122
- // @ts-expect-error TODO: not sure how we will make it dev friendly
123
- definitionNode => definitionNode.kind === graphql_1.Kind.OPERATION_DEFINITION);
89
+ onParse({ params: { source } }) {
90
+ return ({ result }) => {
91
+ if (options.includeOperationDocument) {
92
+ const key = source instanceof graphql_1.Source ? source.body : source;
93
+ if (!result || result instanceof Error)
94
+ return;
95
+ documentSourceMap.set(result, key);
96
+ }
97
+ };
98
+ },
99
+ onExecute({ args }) {
100
+ const rootOperation = (0, graphql_1.getOperationAST)(args.document, args.operationName);
101
+ if (!rootOperation) {
102
+ logger.trace('No root operation found. Not recording transaction.');
103
+ return;
104
+ }
124
105
  const operationType = rootOperation.operation;
125
- const document = (0, graphql_1.print)(args.document);
126
106
  const operationName = options.extractOperationName?.(args.contextValue) ||
127
107
  args.operationName ||
128
108
  rootOperation.name?.value ||
129
109
  AttributeName.ANONYMOUS_OPERATION;
130
- let rootFields = null;
131
- if (options.rootFieldsNaming) {
132
- const fieldNodes = rootOperation.selectionSet.selections.filter(selectionNode => selectionNode.kind === graphql_1.Kind.FIELD);
133
- rootFields = fieldNodes.map(fieldNode => fieldNode.name.value);
110
+ const transaction = instrumentationApi.agent.tracer.getTransaction();
111
+ const transactionNameState = transaction?.nameState;
112
+ if (transactionNameState) {
113
+ const delimiter = transactionNameState.delimiter || '/';
114
+ let rootFields = null;
115
+ if (options.rootFieldsNaming) {
116
+ const fieldNodes = rootOperation.selectionSet.selections.filter(selectionNode => selectionNode.kind === graphql_1.Kind.FIELD);
117
+ rootFields = fieldNodes.map(fieldNode => fieldNode.name.value);
118
+ }
119
+ const operationType = rootOperation.operation;
120
+ const operationName = options.extractOperationName?.(args.contextValue) ||
121
+ args.operationName ||
122
+ rootOperation.name?.value ||
123
+ AttributeName.ANONYMOUS_OPERATION;
124
+ transactionNameState.setName(transactionNameState.prefix, transactionNameState.verb, delimiter, operationType + delimiter + operationName + (rootFields ? delimiter + rootFields.join('&') : ''));
134
125
  }
135
- transactionNameState.setName(transactionNameState.prefix, transactionNameState.verb, delimiter, operationType + delimiter + operationName + (rootFields ? delimiter + rootFields.join('&') : ''));
136
- spanContext.addCustomAttribute(AttributeName.EXECUTION_OPERATION_NAME, operationName);
137
- spanContext.addCustomAttribute(AttributeName.EXECUTION_OPERATION_TYPE, operationType);
126
+ const spanContext = instrumentationApi.agent.tracer.getSpanContext();
127
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_OPERATION_NAME, operationName);
128
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_OPERATION_TYPE, operationType);
138
129
  options.includeOperationDocument &&
139
- spanContext.addCustomAttribute(AttributeName.EXECUTION_OPERATION_DOCUMENT, document);
130
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_OPERATION_DOCUMENT, documentSourceMap.get(args.document) ?? (0, graphql_1.print)(args.document));
140
131
  if (options.includeExecuteVariables) {
141
132
  const rawVariables = args.variableValues || {};
142
133
  const executeVariablesToTrack = options.isExecuteVariablesRegex
143
134
  ? filterPropertiesByRegex(rawVariables, options.includeExecuteVariables)
144
135
  : rawVariables;
145
- spanContext.addCustomAttribute(AttributeName.EXECUTION_VARIABLES, JSON.stringify(executeVariablesToTrack));
136
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_VARIABLES, JSON.stringify(executeVariablesToTrack));
146
137
  }
147
138
  const operationSegment = instrumentationApi.getActiveSegment();
148
139
  return {
149
140
  onExecuteDone({ result }) {
150
141
  const sendResult = (singularResult) => {
151
142
  if (singularResult.data && options.includeRawResult) {
152
- spanContext.addCustomAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(singularResult));
143
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(singularResult));
153
144
  }
154
145
  if (singularResult.errors && singularResult.errors.length > 0) {
155
146
  const agent = instrumentationApi.agent;
156
147
  const transaction = instrumentationApi.tracer.getTransaction();
157
- for (const error of singularResult.errors) {
158
- if (options.skipError?.(error))
159
- continue;
160
- agent.errors.add(transaction, JSON.stringify(error));
148
+ if (transaction) {
149
+ for (const error of singularResult.errors) {
150
+ if (options.skipError?.(error))
151
+ continue;
152
+ agent.errors.add(transaction, JSON.stringify(error));
153
+ }
161
154
  }
162
155
  }
163
156
  };
@@ -167,12 +160,12 @@ const useNewRelic = (rawOptions) => {
167
160
  sendResult(singularResult);
168
161
  },
169
162
  onEnd: () => {
170
- operationSegment.end();
163
+ operationSegment?.end();
171
164
  },
172
165
  };
173
166
  }
174
167
  sendResult(result);
175
- operationSegment.end();
168
+ operationSegment?.end();
176
169
  return {};
177
170
  },
178
171
  };
package/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isAsyncIterable } from '@envelop/core';
2
2
  import { useOnResolve } from '@envelop/on-resolve';
3
- import { print, Kind } from 'graphql';
3
+ import { print, Kind, Source, getOperationAST } from 'graphql';
4
+ import newRelic from 'newrelic';
4
5
  var AttributeName;
5
6
  (function (AttributeName) {
6
7
  AttributeName["COMPONENT_NAME"] = "Envelop_NewRelic_Plugin";
@@ -32,30 +33,23 @@ export const useNewRelic = (rawOptions) => {
32
33
  };
33
34
  options.isExecuteVariablesRegex = options.includeExecuteVariables instanceof RegExp;
34
35
  options.isResolverArgsRegex = options.includeResolverArgs instanceof RegExp;
35
- const instrumentationApi$ = import('newrelic')
36
- .then(m => m.default || m)
37
- .then(({ shim }) => {
38
- if (!shim?.agent) {
39
- throw new Error('Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.');
40
- }
41
- shim.agent.metrics
42
- .getOrCreateMetric(`Supportability/ExternalModules/${AttributeName.COMPONENT_NAME}`)
43
- .incrementCallCount();
44
- return shim;
45
- });
46
- const logger$ = instrumentationApi$.then(({ logger }) => {
47
- const childLogger = logger.child({ component: AttributeName.COMPONENT_NAME });
48
- childLogger.info(`${AttributeName.COMPONENT_NAME} registered`);
49
- return childLogger;
50
- });
36
+ const instrumentationApi = newRelic?.shim;
37
+ if (!instrumentationApi?.agent) {
38
+ console.warn('Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.');
39
+ return {};
40
+ }
41
+ instrumentationApi.agent.metrics
42
+ .getOrCreateMetric(`Supportability/ExternalModules/${AttributeName.COMPONENT_NAME}`)
43
+ .incrementCallCount();
44
+ const logger = instrumentationApi.logger.child({ component: AttributeName.COMPONENT_NAME });
45
+ logger.info(`${AttributeName.COMPONENT_NAME} registered`);
46
+ const documentSourceMap = new WeakMap();
51
47
  return {
52
48
  onPluginInit({ addPlugin }) {
53
49
  if (options.trackResolvers) {
54
- addPlugin(useOnResolve(async ({ args: resolversArgs, info }) => {
55
- const instrumentationApi = await instrumentationApi$;
50
+ addPlugin(useOnResolve(({ args: resolversArgs, info }) => {
56
51
  const transactionNameState = instrumentationApi.agent.tracer.getTransaction().nameState;
57
52
  const delimiter = transactionNameState.delimiter;
58
- const logger = await logger$;
59
53
  const { returnType, path, parentType } = info;
60
54
  const formattedPath = flattenPath(path, delimiter);
61
55
  const currentSegment = instrumentationApi.getActiveSegment();
@@ -88,51 +82,71 @@ export const useNewRelic = (rawOptions) => {
88
82
  }));
89
83
  }
90
84
  },
91
- async onExecute({ args }) {
92
- const instrumentationApi = await instrumentationApi$;
93
- const transactionNameState = instrumentationApi.agent.tracer.getTransaction().nameState;
94
- const spanContext = instrumentationApi.agent.tracer.getSpanContext();
95
- const delimiter = transactionNameState.delimiter;
96
- const rootOperation = args.document.definitions.find(
97
- // @ts-expect-error TODO: not sure how we will make it dev friendly
98
- definitionNode => definitionNode.kind === Kind.OPERATION_DEFINITION);
85
+ onParse({ params: { source } }) {
86
+ return ({ result }) => {
87
+ if (options.includeOperationDocument) {
88
+ const key = source instanceof Source ? source.body : source;
89
+ if (!result || result instanceof Error)
90
+ return;
91
+ documentSourceMap.set(result, key);
92
+ }
93
+ };
94
+ },
95
+ onExecute({ args }) {
96
+ const rootOperation = getOperationAST(args.document, args.operationName);
97
+ if (!rootOperation) {
98
+ logger.trace('No root operation found. Not recording transaction.');
99
+ return;
100
+ }
99
101
  const operationType = rootOperation.operation;
100
- const document = print(args.document);
101
102
  const operationName = options.extractOperationName?.(args.contextValue) ||
102
103
  args.operationName ||
103
104
  rootOperation.name?.value ||
104
105
  AttributeName.ANONYMOUS_OPERATION;
105
- let rootFields = null;
106
- if (options.rootFieldsNaming) {
107
- const fieldNodes = rootOperation.selectionSet.selections.filter(selectionNode => selectionNode.kind === Kind.FIELD);
108
- rootFields = fieldNodes.map(fieldNode => fieldNode.name.value);
106
+ const transaction = instrumentationApi.agent.tracer.getTransaction();
107
+ const transactionNameState = transaction?.nameState;
108
+ if (transactionNameState) {
109
+ const delimiter = transactionNameState.delimiter || '/';
110
+ let rootFields = null;
111
+ if (options.rootFieldsNaming) {
112
+ const fieldNodes = rootOperation.selectionSet.selections.filter(selectionNode => selectionNode.kind === Kind.FIELD);
113
+ rootFields = fieldNodes.map(fieldNode => fieldNode.name.value);
114
+ }
115
+ const operationType = rootOperation.operation;
116
+ const operationName = options.extractOperationName?.(args.contextValue) ||
117
+ args.operationName ||
118
+ rootOperation.name?.value ||
119
+ AttributeName.ANONYMOUS_OPERATION;
120
+ transactionNameState.setName(transactionNameState.prefix, transactionNameState.verb, delimiter, operationType + delimiter + operationName + (rootFields ? delimiter + rootFields.join('&') : ''));
109
121
  }
110
- transactionNameState.setName(transactionNameState.prefix, transactionNameState.verb, delimiter, operationType + delimiter + operationName + (rootFields ? delimiter + rootFields.join('&') : ''));
111
- spanContext.addCustomAttribute(AttributeName.EXECUTION_OPERATION_NAME, operationName);
112
- spanContext.addCustomAttribute(AttributeName.EXECUTION_OPERATION_TYPE, operationType);
122
+ const spanContext = instrumentationApi.agent.tracer.getSpanContext();
123
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_OPERATION_NAME, operationName);
124
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_OPERATION_TYPE, operationType);
113
125
  options.includeOperationDocument &&
114
- spanContext.addCustomAttribute(AttributeName.EXECUTION_OPERATION_DOCUMENT, document);
126
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_OPERATION_DOCUMENT, documentSourceMap.get(args.document) ?? print(args.document));
115
127
  if (options.includeExecuteVariables) {
116
128
  const rawVariables = args.variableValues || {};
117
129
  const executeVariablesToTrack = options.isExecuteVariablesRegex
118
130
  ? filterPropertiesByRegex(rawVariables, options.includeExecuteVariables)
119
131
  : rawVariables;
120
- spanContext.addCustomAttribute(AttributeName.EXECUTION_VARIABLES, JSON.stringify(executeVariablesToTrack));
132
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_VARIABLES, JSON.stringify(executeVariablesToTrack));
121
133
  }
122
134
  const operationSegment = instrumentationApi.getActiveSegment();
123
135
  return {
124
136
  onExecuteDone({ result }) {
125
137
  const sendResult = (singularResult) => {
126
138
  if (singularResult.data && options.includeRawResult) {
127
- spanContext.addCustomAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(singularResult));
139
+ spanContext?.addCustomAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(singularResult));
128
140
  }
129
141
  if (singularResult.errors && singularResult.errors.length > 0) {
130
142
  const agent = instrumentationApi.agent;
131
143
  const transaction = instrumentationApi.tracer.getTransaction();
132
- for (const error of singularResult.errors) {
133
- if (options.skipError?.(error))
134
- continue;
135
- agent.errors.add(transaction, JSON.stringify(error));
144
+ if (transaction) {
145
+ for (const error of singularResult.errors) {
146
+ if (options.skipError?.(error))
147
+ continue;
148
+ agent.errors.add(transaction, JSON.stringify(error));
149
+ }
136
150
  }
137
151
  }
138
152
  };
@@ -142,12 +156,12 @@ export const useNewRelic = (rawOptions) => {
142
156
  sendResult(singularResult);
143
157
  },
144
158
  onEnd: () => {
145
- operationSegment.end();
159
+ operationSegment?.end();
146
160
  },
147
161
  };
148
162
  }
149
163
  sendResult(result);
150
- operationSegment.end();
164
+ operationSegment?.end();
151
165
  return {};
152
166
  },
153
167
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envelop/newrelic",
3
- "version": "5.0.4",
3
+ "version": "5.0.5-alpha-20230104113641-ef3d8f5b",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "@envelop/core": "^3.0.4",