@envelop/sentry 4.0.0-alpha-4ba70ab.0 → 4.0.0-alpha-d0d0776.0

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
@@ -32,6 +32,8 @@ yarn add @sentry/node @sentry/tracing @envelop/sentry
32
32
  ```ts
33
33
  import { envelop } from '@envelop/core';
34
34
  import { useSentry } from '@envelop/sentry';
35
+ // do this only once in you entry file.
36
+ import "@sentry/tracing";
35
37
 
36
38
  const getEnveloped = envelop({
37
39
  plugins: [
@@ -62,3 +64,4 @@ const getEnveloped = envelop({
62
64
  - `operationName` - Produces a "op" (operation) of created Span.
63
65
  - `skip` (default: none) - Produces a "op" (operation) of created Span.
64
66
  - `skipError` (default: ignored `EnvelopError`) - Indicates whether or not to skip Sentry exception reporting for a given error. By default, this plugin skips all `EnvelopError` errors and does not report it to Sentry.
67
+ - `eventIdKey` (default: `'sentryEventId'`) - The key in the error's extensions field used to expose the generated Sentry event id. Set to `null` to disable.
package/index.d.ts CHANGED
@@ -34,6 +34,11 @@ export declare type SentryPluginOptions = {
34
34
  * @default false
35
35
  */
36
36
  includeExecuteVariables?: boolean;
37
+ /**
38
+ * The key of the event id in the error's extension. `null` to disable.
39
+ * @default sentryEventId
40
+ */
41
+ eventIdKey?: string | null;
37
42
  /**
38
43
  * Adds custom tags to every Transaction.
39
44
  */
package/index.js CHANGED
@@ -6,15 +6,33 @@ const core = require('@envelop/core');
6
6
  const Sentry = require('@sentry/node');
7
7
  const graphql = require('graphql');
8
8
 
9
- /* eslint-disable @typescript-eslint/no-empty-function */
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ const n = Object.create(null);
12
+ if (e) {
13
+ for (const k in e) {
14
+ if (k !== 'default') {
15
+ const d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ }
22
+ }
23
+ n["default"] = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ const Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
28
+
10
29
  function defaultSkipError(error) {
11
30
  return error instanceof core.EnvelopError;
12
31
  }
13
32
  const sentryTracingSymbol = Symbol('sentryTracing');
14
33
  const useSentry = (options = {}) => {
15
34
  function pick(key, defaultValue) {
16
- var _a;
17
- return (_a = options[key]) !== null && _a !== void 0 ? _a : defaultValue;
35
+ return options[key] ?? defaultValue;
18
36
  }
19
37
  const startTransaction = pick('startTransaction', true);
20
38
  const trackResolvers = pick('trackResolvers', true);
@@ -24,6 +42,16 @@ const useSentry = (options = {}) => {
24
42
  const renameTransaction = pick('renameTransaction', false);
25
43
  const skipOperation = pick('skip', () => false);
26
44
  const skipError = pick('skipError', defaultSkipError);
45
+ function addEventId(err, eventId) {
46
+ if (options.eventIdKey === null) {
47
+ return err;
48
+ }
49
+ const eventIdKey = options.eventIdKey ?? 'sentryEventId';
50
+ return new graphql.GraphQLError(err.message, err.nodes, err.source, err.positions, err.path, undefined, {
51
+ ...err.extensions,
52
+ [eventIdKey]: eventId,
53
+ });
54
+ }
27
55
  const onResolverCalled = trackResolvers
28
56
  ? ({ args: resolversArgs, info, context }) => {
29
57
  const { rootSpan, opName, operationType } = context[sentryTracingSymbol];
@@ -47,8 +75,11 @@ const useSentry = (options = {}) => {
47
75
  childSpan.setData('result', result);
48
76
  }
49
77
  if (result instanceof Error && !skipError(result)) {
50
- const errorPath = graphql.responsePathAsArray(info.path).join(' > ');
51
- Sentry.captureException(result, {
78
+ // Map index values in list to $index for better grouping of events.
79
+ const errorPath = graphql.responsePathAsArray(info.path)
80
+ .map(v => (typeof v === 'number' ? '$index' : v))
81
+ .join(' > ');
82
+ Sentry__namespace.captureException(result, {
52
83
  fingerprint: ['graphql', errorPath, opName, operationType],
53
84
  });
54
85
  }
@@ -61,14 +92,13 @@ const useSentry = (options = {}) => {
61
92
  return {
62
93
  onResolverCalled,
63
94
  onExecute({ args, extendContext }) {
64
- var _a;
65
95
  if (skipOperation(args)) {
66
96
  return;
67
97
  }
68
98
  const rootOperation = args.document.definitions.find(o => o.kind === graphql.Kind.OPERATION_DEFINITION);
69
99
  const operationType = rootOperation.operation;
70
100
  const document = graphql.print(args.document);
71
- const opName = args.operationName || ((_a = rootOperation.name) === null || _a === void 0 ? void 0 : _a.value) || 'Anonymous Operation';
101
+ const opName = args.operationName || rootOperation.name?.value || 'Anonymous Operation';
72
102
  const addedTags = (options.appendTags && options.appendTags(args)) || {};
73
103
  const transactionName = options.transactionName ? options.transactionName(args) : opName;
74
104
  const op = options.operationName ? options.operationName(args) : 'execute';
@@ -79,21 +109,22 @@ const useSentry = (options = {}) => {
79
109
  };
80
110
  let rootSpan;
81
111
  if (startTransaction) {
82
- rootSpan = Sentry.startTransaction({
112
+ rootSpan = Sentry__namespace.startTransaction({
83
113
  name: transactionName,
84
114
  op,
85
115
  tags,
86
116
  });
87
117
  }
88
118
  else {
89
- const scope = Sentry.getCurrentHub().getScope();
90
- const parentSpan = scope === null || scope === void 0 ? void 0 : scope.getSpan();
91
- const span = parentSpan === null || parentSpan === void 0 ? void 0 : parentSpan.startChild({
119
+ const scope = Sentry__namespace.getCurrentHub().getScope();
120
+ const parentSpan = scope?.getSpan();
121
+ const span = parentSpan?.startChild({
92
122
  description: transactionName,
93
123
  op,
94
124
  tags,
95
125
  });
96
126
  if (!span) {
127
+ // eslint-disable-next-line no-console
97
128
  console.warn([
98
129
  `Flag "startTransaction" is enabled but Sentry failed to find a transaction.`,
99
130
  `Try to create a transaction before GraphQL execution phase is started.`,
@@ -107,7 +138,7 @@ const useSentry = (options = {}) => {
107
138
  }
108
139
  rootSpan.setData('document', document);
109
140
  if (options.configureScope) {
110
- Sentry.configureScope(scope => options.configureScope(args, scope));
141
+ Sentry__namespace.configureScope(scope => options.configureScope(args, scope));
111
142
  }
112
143
  if (onResolverCalled) {
113
144
  const sentryContext = {
@@ -118,19 +149,13 @@ const useSentry = (options = {}) => {
118
149
  extendContext({ [sentryTracingSymbol]: sentryContext });
119
150
  }
120
151
  return {
121
- onExecuteDone({ result }) {
122
- if (core.isAsyncIterable(result)) {
123
- rootSpan.finish();
124
- // eslint-disable-next-line no-console
125
- console.warn(`Plugin "sentry" encountered a AsyncIterator which is not supported yet, so tracing data is not available for the operation.`);
126
- return;
127
- }
128
- if (includeRawResult) {
129
- rootSpan.setData('result', result);
130
- }
131
- if (result.errors && result.errors.length > 0) {
132
- for (const err of result.errors) {
133
- Sentry.withScope(scope => {
152
+ onExecuteDone(payload) {
153
+ const handleResult = ({ result, setResult }) => {
154
+ if (includeRawResult) {
155
+ rootSpan.setData('result', result);
156
+ }
157
+ if (result.errors && result.errors.length > 0) {
158
+ Sentry__namespace.withScope(scope => {
134
159
  scope.setTransactionName(opName);
135
160
  scope.setTag('operation', operationType);
136
161
  scope.setTag('operationName', opName);
@@ -142,28 +167,38 @@ const useSentry = (options = {}) => {
142
167
  if (includeExecuteVariables) {
143
168
  scope.setExtra('variables', args.variableValues);
144
169
  }
145
- const errorPath = (err.path || []).join(' > ');
146
- if (errorPath) {
147
- scope.addBreadcrumb({
148
- category: 'execution-path',
149
- message: errorPath,
150
- level: Sentry.Severity.Debug,
151
- });
152
- }
153
- Sentry.captureException(err, {
154
- fingerprint: ['graphql', errorPath, opName, operationType],
155
- contexts: {
156
- GraphQL: {
157
- operationName: opName,
158
- operationType: operationType,
159
- variables: args.variableValues,
170
+ const errors = result.errors?.map(err => {
171
+ const errorPath = (err.path ?? []).join(' > ');
172
+ if (errorPath) {
173
+ scope.addBreadcrumb({
174
+ category: 'execution-path',
175
+ message: errorPath,
176
+ level: 'debug',
177
+ });
178
+ }
179
+ // Map index values in list to $index for better grouping of events.
180
+ const errorPathWithIndex = (err.path ?? []).map(v => (typeof v === 'number' ? '$index' : v)).join(' > ');
181
+ const eventId = Sentry__namespace.captureException(err, {
182
+ fingerprint: ['graphql', errorPathWithIndex, opName, operationType],
183
+ contexts: {
184
+ GraphQL: {
185
+ operationName: opName,
186
+ operationType,
187
+ variables: args.variableValues,
188
+ },
160
189
  },
161
- },
190
+ });
191
+ return addEventId(err, eventId);
192
+ });
193
+ setResult({
194
+ ...result,
195
+ errors,
162
196
  });
163
197
  });
164
198
  }
165
- }
166
- rootSpan.finish();
199
+ rootSpan.finish();
200
+ };
201
+ return core.handleStreamOrSingleExecutionResult(payload, handleResult);
167
202
  },
168
203
  };
169
204
  },
package/index.mjs CHANGED
@@ -1,18 +1,16 @@
1
- import { EnvelopError, isAsyncIterable } from '@envelop/core';
2
- import { startTransaction, getCurrentHub, configureScope, withScope, Severity, captureException } from '@sentry/node';
3
- import { Kind, print, responsePathAsArray } from 'graphql';
1
+ import { EnvelopError, handleStreamOrSingleExecutionResult } from '@envelop/core';
2
+ import * as Sentry from '@sentry/node';
3
+ import { responsePathAsArray, Kind, print, GraphQLError } from 'graphql';
4
4
 
5
- /* eslint-disable @typescript-eslint/no-empty-function */
6
5
  function defaultSkipError(error) {
7
6
  return error instanceof EnvelopError;
8
7
  }
9
8
  const sentryTracingSymbol = Symbol('sentryTracing');
10
9
  const useSentry = (options = {}) => {
11
10
  function pick(key, defaultValue) {
12
- var _a;
13
- return (_a = options[key]) !== null && _a !== void 0 ? _a : defaultValue;
11
+ return options[key] ?? defaultValue;
14
12
  }
15
- const startTransaction$1 = pick('startTransaction', true);
13
+ const startTransaction = pick('startTransaction', true);
16
14
  const trackResolvers = pick('trackResolvers', true);
17
15
  const includeResolverArgs = pick('includeResolverArgs', false);
18
16
  const includeRawResult = pick('includeRawResult', false);
@@ -20,6 +18,16 @@ const useSentry = (options = {}) => {
20
18
  const renameTransaction = pick('renameTransaction', false);
21
19
  const skipOperation = pick('skip', () => false);
22
20
  const skipError = pick('skipError', defaultSkipError);
21
+ function addEventId(err, eventId) {
22
+ if (options.eventIdKey === null) {
23
+ return err;
24
+ }
25
+ const eventIdKey = options.eventIdKey ?? 'sentryEventId';
26
+ return new GraphQLError(err.message, err.nodes, err.source, err.positions, err.path, undefined, {
27
+ ...err.extensions,
28
+ [eventIdKey]: eventId,
29
+ });
30
+ }
23
31
  const onResolverCalled = trackResolvers
24
32
  ? ({ args: resolversArgs, info, context }) => {
25
33
  const { rootSpan, opName, operationType } = context[sentryTracingSymbol];
@@ -43,8 +51,11 @@ const useSentry = (options = {}) => {
43
51
  childSpan.setData('result', result);
44
52
  }
45
53
  if (result instanceof Error && !skipError(result)) {
46
- const errorPath = responsePathAsArray(info.path).join(' > ');
47
- captureException(result, {
54
+ // Map index values in list to $index for better grouping of events.
55
+ const errorPath = responsePathAsArray(info.path)
56
+ .map(v => (typeof v === 'number' ? '$index' : v))
57
+ .join(' > ');
58
+ Sentry.captureException(result, {
48
59
  fingerprint: ['graphql', errorPath, opName, operationType],
49
60
  });
50
61
  }
@@ -57,14 +68,13 @@ const useSentry = (options = {}) => {
57
68
  return {
58
69
  onResolverCalled,
59
70
  onExecute({ args, extendContext }) {
60
- var _a;
61
71
  if (skipOperation(args)) {
62
72
  return;
63
73
  }
64
74
  const rootOperation = args.document.definitions.find(o => o.kind === Kind.OPERATION_DEFINITION);
65
75
  const operationType = rootOperation.operation;
66
76
  const document = print(args.document);
67
- const opName = args.operationName || ((_a = rootOperation.name) === null || _a === void 0 ? void 0 : _a.value) || 'Anonymous Operation';
77
+ const opName = args.operationName || rootOperation.name?.value || 'Anonymous Operation';
68
78
  const addedTags = (options.appendTags && options.appendTags(args)) || {};
69
79
  const transactionName = options.transactionName ? options.transactionName(args) : opName;
70
80
  const op = options.operationName ? options.operationName(args) : 'execute';
@@ -74,22 +84,23 @@ const useSentry = (options = {}) => {
74
84
  ...(addedTags || {}),
75
85
  };
76
86
  let rootSpan;
77
- if (startTransaction$1) {
78
- rootSpan = startTransaction({
87
+ if (startTransaction) {
88
+ rootSpan = Sentry.startTransaction({
79
89
  name: transactionName,
80
90
  op,
81
91
  tags,
82
92
  });
83
93
  }
84
94
  else {
85
- const scope = getCurrentHub().getScope();
86
- const parentSpan = scope === null || scope === void 0 ? void 0 : scope.getSpan();
87
- const span = parentSpan === null || parentSpan === void 0 ? void 0 : parentSpan.startChild({
95
+ const scope = Sentry.getCurrentHub().getScope();
96
+ const parentSpan = scope?.getSpan();
97
+ const span = parentSpan?.startChild({
88
98
  description: transactionName,
89
99
  op,
90
100
  tags,
91
101
  });
92
102
  if (!span) {
103
+ // eslint-disable-next-line no-console
93
104
  console.warn([
94
105
  `Flag "startTransaction" is enabled but Sentry failed to find a transaction.`,
95
106
  `Try to create a transaction before GraphQL execution phase is started.`,
@@ -103,7 +114,7 @@ const useSentry = (options = {}) => {
103
114
  }
104
115
  rootSpan.setData('document', document);
105
116
  if (options.configureScope) {
106
- configureScope(scope => options.configureScope(args, scope));
117
+ Sentry.configureScope(scope => options.configureScope(args, scope));
107
118
  }
108
119
  if (onResolverCalled) {
109
120
  const sentryContext = {
@@ -114,19 +125,13 @@ const useSentry = (options = {}) => {
114
125
  extendContext({ [sentryTracingSymbol]: sentryContext });
115
126
  }
116
127
  return {
117
- onExecuteDone({ result }) {
118
- if (isAsyncIterable(result)) {
119
- rootSpan.finish();
120
- // eslint-disable-next-line no-console
121
- console.warn(`Plugin "sentry" encountered a AsyncIterator which is not supported yet, so tracing data is not available for the operation.`);
122
- return;
123
- }
124
- if (includeRawResult) {
125
- rootSpan.setData('result', result);
126
- }
127
- if (result.errors && result.errors.length > 0) {
128
- for (const err of result.errors) {
129
- withScope(scope => {
128
+ onExecuteDone(payload) {
129
+ const handleResult = ({ result, setResult }) => {
130
+ if (includeRawResult) {
131
+ rootSpan.setData('result', result);
132
+ }
133
+ if (result.errors && result.errors.length > 0) {
134
+ Sentry.withScope(scope => {
130
135
  scope.setTransactionName(opName);
131
136
  scope.setTag('operation', operationType);
132
137
  scope.setTag('operationName', opName);
@@ -138,28 +143,38 @@ const useSentry = (options = {}) => {
138
143
  if (includeExecuteVariables) {
139
144
  scope.setExtra('variables', args.variableValues);
140
145
  }
141
- const errorPath = (err.path || []).join(' > ');
142
- if (errorPath) {
143
- scope.addBreadcrumb({
144
- category: 'execution-path',
145
- message: errorPath,
146
- level: Severity.Debug,
147
- });
148
- }
149
- captureException(err, {
150
- fingerprint: ['graphql', errorPath, opName, operationType],
151
- contexts: {
152
- GraphQL: {
153
- operationName: opName,
154
- operationType: operationType,
155
- variables: args.variableValues,
146
+ const errors = result.errors?.map(err => {
147
+ const errorPath = (err.path ?? []).join(' > ');
148
+ if (errorPath) {
149
+ scope.addBreadcrumb({
150
+ category: 'execution-path',
151
+ message: errorPath,
152
+ level: 'debug',
153
+ });
154
+ }
155
+ // Map index values in list to $index for better grouping of events.
156
+ const errorPathWithIndex = (err.path ?? []).map(v => (typeof v === 'number' ? '$index' : v)).join(' > ');
157
+ const eventId = Sentry.captureException(err, {
158
+ fingerprint: ['graphql', errorPathWithIndex, opName, operationType],
159
+ contexts: {
160
+ GraphQL: {
161
+ operationName: opName,
162
+ operationType,
163
+ variables: args.variableValues,
164
+ },
156
165
  },
157
- },
166
+ });
167
+ return addEventId(err, eventId);
168
+ });
169
+ setResult({
170
+ ...result,
171
+ errors,
158
172
  });
159
173
  });
160
174
  }
161
- }
162
- rootSpan.finish();
175
+ rootSpan.finish();
176
+ };
177
+ return handleStreamOrSingleExecutionResult(payload, handleResult);
163
178
  },
164
179
  };
165
180
  },
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@envelop/sentry",
3
- "version": "4.0.0-alpha-4ba70ab.0",
3
+ "version": "4.0.0-alpha-d0d0776.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "@envelop/core": "2.1.0-alpha-4ba70ab.0",
7
- "@sentry/node": "^6",
6
+ "@envelop/core": "3.0.0-alpha-d0d0776.0",
7
+ "@sentry/node": "^6 || ^7",
8
8
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/dotansimha/envelop.git",
12
+ "url": "https://github.com/n1ru4l/envelop.git",
13
13
  "directory": "packages/plugins/sentry"
14
14
  },
15
15
  "author": "Dotan Simha <dotansimha@gmail.com>",
@@ -22,12 +22,24 @@
22
22
  },
23
23
  "exports": {
24
24
  ".": {
25
- "require": "./index.js",
26
- "import": "./index.mjs"
25
+ "require": {
26
+ "default": "./index.js",
27
+ "types": "./index.d.ts"
28
+ },
29
+ "import": {
30
+ "default": "./index.mjs",
31
+ "types": "./index.d.ts"
32
+ }
27
33
  },
28
34
  "./*": {
29
- "require": "./*.js",
30
- "import": "./*.mjs"
35
+ "require": {
36
+ "default": "./*.js",
37
+ "types": "./*.d.ts"
38
+ },
39
+ "import": {
40
+ "default": "./*.mjs",
41
+ "types": "./*.d.ts"
42
+ }
31
43
  },
32
44
  "./package.json": "./package.json"
33
45
  }