@envelop/sentry 4.0.0-alpha-36a1267.0 → 4.0.0-alpha-5b6bfa8.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
@@ -30,22 +30,24 @@ yarn add @sentry/node @sentry/tracing @envelop/sentry
30
30
  ## Usage Example
31
31
 
32
32
  ```ts
33
- import { envelop } from '@envelop/core';
34
- import { useSentry } from '@envelop/sentry';
33
+ import { envelop } from '@envelop/core'
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: [
38
40
  // ... other plugins ...
39
41
  useSentry({
40
- includeRawResult: false // set to `true` in order to include the execution result in the metadata collected
42
+ includeRawResult: false, // set to `true` in order to include the execution result in the metadata collected
41
43
  includeResolverArgs: false, // set to `true` in order to include the args passed to resolvers
42
44
  includeExecuteVariables: false, // set to `true` in order to include the operation variables values
43
- appendTags: (args) => { return { ... }} // if you wish to add custom "tags" to the Sentry transaction created per operation
44
- configureScope: (args, scope) => { return { ... }} // if you wish to modify the Sentry scope
45
- skip: (executionArgs) => { return { ... }} // if you wish to modify the skip specific operations
46
- }),
47
- ],
48
- });
45
+ appendTags: args => {}, // if you wish to add custom "tags" to the Sentry transaction created per operation
46
+ configureScope: (args, scope) => {}, // if you wish to modify the Sentry scope
47
+ skip: executionArgs => {} // if you wish to modify the skip specific operations
48
+ })
49
+ ]
50
+ })
49
51
  ```
50
52
 
51
53
  ### Configuration
@@ -59,6 +61,8 @@ const getEnveloped = envelop({
59
61
  - `appendTags` - See example above. Allow you to manipulate the tags reports on the Sentry transaction.
60
62
  - `configureScope` - See example above. Allow you to manipulate the tags reports on the Sentry transaction.
61
63
  - `transactionName` (default: operation name) - Produces a name of Transaction (only when "renameTransaction" or "startTransaction" are enabled) and description of created Span.
64
+ - `traceparentData` (default: `{}`) - Adds tracing data to be sent to Sentry - this includes traceId, parentId and more.
62
65
  - `operationName` - Produces a "op" (operation) of created Span.
63
66
  - `skip` (default: none) - Produces a "op" (operation) of created Span.
64
67
  - `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.
68
+ - `eventIdKey` (default: `'sentryEventId'`) - The key in the error's extensions field used to expose the generated Sentry event id. Set to `null` to disable.
@@ -1,15 +1,14 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- const core = require('@envelop/core');
6
- const Sentry = require('@sentry/node');
7
- const graphql = require('graphql');
8
-
9
- /* eslint-disable @typescript-eslint/no-empty-function */
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSentry = exports.defaultSkipError = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const core_1 = require("@envelop/core");
6
+ const Sentry = tslib_1.__importStar(require("@sentry/node"));
7
+ const graphql_1 = require("@graphql-tools/graphql");
10
8
  function defaultSkipError(error) {
11
- return error instanceof core.EnvelopError;
9
+ return error instanceof core_1.EnvelopError;
12
10
  }
11
+ exports.defaultSkipError = defaultSkipError;
13
12
  const sentryTracingSymbol = Symbol('sentryTracing');
14
13
  const useSentry = (options = {}) => {
15
14
  function pick(key, defaultValue) {
@@ -24,6 +23,17 @@ const useSentry = (options = {}) => {
24
23
  const renameTransaction = pick('renameTransaction', false);
25
24
  const skipOperation = pick('skip', () => false);
26
25
  const skipError = pick('skipError', defaultSkipError);
26
+ function addEventId(err, eventId) {
27
+ var _a;
28
+ if (options.eventIdKey === null) {
29
+ return err;
30
+ }
31
+ const eventIdKey = (_a = options.eventIdKey) !== null && _a !== void 0 ? _a : 'sentryEventId';
32
+ return new graphql_1.GraphQLError(err.message, err.nodes, err.source, err.positions, err.path, undefined, {
33
+ ...err.extensions,
34
+ [eventIdKey]: eventId,
35
+ });
36
+ }
27
37
  const onResolverCalled = trackResolvers
28
38
  ? ({ args: resolversArgs, info, context }) => {
29
39
  const { rootSpan, opName, operationType } = context[sentryTracingSymbol];
@@ -47,7 +57,10 @@ const useSentry = (options = {}) => {
47
57
  childSpan.setData('result', result);
48
58
  }
49
59
  if (result instanceof Error && !skipError(result)) {
50
- const errorPath = graphql.responsePathAsArray(info.path).join(' > ');
60
+ // Map index values in list to $index for better grouping of events.
61
+ const errorPath = (0, graphql_1.responsePathAsArray)(info.path)
62
+ .map(v => (typeof v === 'number' ? '$index' : v))
63
+ .join(' > ');
51
64
  Sentry.captureException(result, {
52
65
  fingerprint: ['graphql', errorPath, opName, operationType],
53
66
  });
@@ -65,17 +78,18 @@ const useSentry = (options = {}) => {
65
78
  if (skipOperation(args)) {
66
79
  return;
67
80
  }
68
- const rootOperation = args.document.definitions.find(o => o.kind === graphql.Kind.OPERATION_DEFINITION);
81
+ const rootOperation = args.document.definitions.find(o => o.kind === graphql_1.Kind.OPERATION_DEFINITION);
69
82
  const operationType = rootOperation.operation;
70
- const document = graphql.print(args.document);
83
+ const document = (0, graphql_1.print)(args.document);
71
84
  const opName = args.operationName || ((_a = rootOperation.name) === null || _a === void 0 ? void 0 : _a.value) || 'Anonymous Operation';
72
85
  const addedTags = (options.appendTags && options.appendTags(args)) || {};
86
+ const traceparentData = (options.traceparentData && options.traceparentData(args)) || {};
73
87
  const transactionName = options.transactionName ? options.transactionName(args) : opName;
74
88
  const op = options.operationName ? options.operationName(args) : 'execute';
75
89
  const tags = {
76
90
  operationName: opName,
77
91
  operation: operationType,
78
- ...(addedTags || {}),
92
+ ...addedTags,
79
93
  };
80
94
  let rootSpan;
81
95
  if (startTransaction) {
@@ -83,7 +97,15 @@ const useSentry = (options = {}) => {
83
97
  name: transactionName,
84
98
  op,
85
99
  tags,
100
+ ...traceparentData,
86
101
  });
102
+ if (!rootSpan) {
103
+ const error = [
104
+ `Could not create the root Sentry transaction for the GraphQL operation "${transactionName}".`,
105
+ `It's very likely that this is because you have not included the Sentry tracing SDK in your app's runtime before handling the request.`,
106
+ ];
107
+ throw new Error(error.join('\n'));
108
+ }
87
109
  }
88
110
  else {
89
111
  const scope = Sentry.getCurrentHub().getScope();
@@ -94,6 +116,7 @@ const useSentry = (options = {}) => {
94
116
  tags,
95
117
  });
96
118
  if (!span) {
119
+ // eslint-disable-next-line no-console
97
120
  console.warn([
98
121
  `Flag "startTransaction" is enabled but Sentry failed to find a transaction.`,
99
122
  `Try to create a transaction before GraphQL execution phase is started.`,
@@ -118,19 +141,14 @@ const useSentry = (options = {}) => {
118
141
  extendContext({ [sentryTracingSymbol]: sentryContext });
119
142
  }
120
143
  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) {
144
+ onExecuteDone(payload) {
145
+ const handleResult = ({ result, setResult }) => {
146
+ if (includeRawResult) {
147
+ rootSpan.setData('result', result);
148
+ }
149
+ if (result.errors && result.errors.length > 0) {
133
150
  Sentry.withScope(scope => {
151
+ var _a;
134
152
  scope.setTransactionName(opName);
135
153
  scope.setTag('operation', operationType);
136
154
  scope.setTag('operationName', opName);
@@ -142,33 +160,44 @@ const useSentry = (options = {}) => {
142
160
  if (includeExecuteVariables) {
143
161
  scope.setExtra('variables', args.variableValues);
144
162
  }
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,
163
+ const errors = (_a = result.errors) === null || _a === void 0 ? void 0 : _a.map(err => {
164
+ var _a, _b;
165
+ const errorPath = ((_a = err.path) !== null && _a !== void 0 ? _a : []).join(' > ');
166
+ if (errorPath) {
167
+ scope.addBreadcrumb({
168
+ category: 'execution-path',
169
+ message: errorPath,
170
+ level: 'debug',
171
+ });
172
+ }
173
+ // Map index values in list to $index for better grouping of events.
174
+ const errorPathWithIndex = ((_b = err.path) !== null && _b !== void 0 ? _b : [])
175
+ .map(v => (typeof v === 'number' ? '$index' : v))
176
+ .join(' > ');
177
+ const eventId = Sentry.captureException(err, {
178
+ fingerprint: ['graphql', errorPathWithIndex, opName, operationType],
179
+ contexts: {
180
+ GraphQL: {
181
+ operationName: opName,
182
+ operationType,
183
+ variables: args.variableValues,
184
+ },
160
185
  },
161
- },
186
+ });
187
+ return addEventId(err, eventId);
188
+ });
189
+ setResult({
190
+ ...result,
191
+ errors,
162
192
  });
163
193
  });
164
194
  }
165
- }
166
- rootSpan.finish();
195
+ rootSpan.finish();
196
+ };
197
+ return (0, core_1.handleStreamOrSingleExecutionResult)(payload, handleResult);
167
198
  },
168
199
  };
169
200
  },
170
201
  };
171
202
  };
172
-
173
- exports.defaultSkipError = defaultSkipError;
174
203
  exports.useSentry = useSentry;
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -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';
4
-
5
- /* eslint-disable @typescript-eslint/no-empty-function */
6
- function defaultSkipError(error) {
1
+ import { EnvelopError, handleStreamOrSingleExecutionResult, } from '@envelop/core';
2
+ import * as Sentry from '@sentry/node';
3
+ import { GraphQLError, Kind, print, responsePathAsArray, } from '@graphql-tools/graphql';
4
+ export function defaultSkipError(error) {
7
5
  return error instanceof EnvelopError;
8
6
  }
9
7
  const sentryTracingSymbol = Symbol('sentryTracing');
10
- const useSentry = (options = {}) => {
8
+ export const useSentry = (options = {}) => {
11
9
  function pick(key, defaultValue) {
12
10
  var _a;
13
11
  return (_a = options[key]) !== null && _a !== void 0 ? _a : 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,17 @@ 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
+ var _a;
23
+ if (options.eventIdKey === null) {
24
+ return err;
25
+ }
26
+ const eventIdKey = (_a = options.eventIdKey) !== null && _a !== void 0 ? _a : 'sentryEventId';
27
+ return new GraphQLError(err.message, err.nodes, err.source, err.positions, err.path, undefined, {
28
+ ...err.extensions,
29
+ [eventIdKey]: eventId,
30
+ });
31
+ }
23
32
  const onResolverCalled = trackResolvers
24
33
  ? ({ args: resolversArgs, info, context }) => {
25
34
  const { rootSpan, opName, operationType } = context[sentryTracingSymbol];
@@ -43,8 +52,11 @@ const useSentry = (options = {}) => {
43
52
  childSpan.setData('result', result);
44
53
  }
45
54
  if (result instanceof Error && !skipError(result)) {
46
- const errorPath = responsePathAsArray(info.path).join(' > ');
47
- captureException(result, {
55
+ // Map index values in list to $index for better grouping of events.
56
+ const errorPath = responsePathAsArray(info.path)
57
+ .map(v => (typeof v === 'number' ? '$index' : v))
58
+ .join(' > ');
59
+ Sentry.captureException(result, {
48
60
  fingerprint: ['graphql', errorPath, opName, operationType],
49
61
  });
50
62
  }
@@ -66,23 +78,32 @@ const useSentry = (options = {}) => {
66
78
  const document = print(args.document);
67
79
  const opName = args.operationName || ((_a = rootOperation.name) === null || _a === void 0 ? void 0 : _a.value) || 'Anonymous Operation';
68
80
  const addedTags = (options.appendTags && options.appendTags(args)) || {};
81
+ const traceparentData = (options.traceparentData && options.traceparentData(args)) || {};
69
82
  const transactionName = options.transactionName ? options.transactionName(args) : opName;
70
83
  const op = options.operationName ? options.operationName(args) : 'execute';
71
84
  const tags = {
72
85
  operationName: opName,
73
86
  operation: operationType,
74
- ...(addedTags || {}),
87
+ ...addedTags,
75
88
  };
76
89
  let rootSpan;
77
- if (startTransaction$1) {
78
- rootSpan = startTransaction({
90
+ if (startTransaction) {
91
+ rootSpan = Sentry.startTransaction({
79
92
  name: transactionName,
80
93
  op,
81
94
  tags,
95
+ ...traceparentData,
82
96
  });
97
+ if (!rootSpan) {
98
+ const error = [
99
+ `Could not create the root Sentry transaction for the GraphQL operation "${transactionName}".`,
100
+ `It's very likely that this is because you have not included the Sentry tracing SDK in your app's runtime before handling the request.`,
101
+ ];
102
+ throw new Error(error.join('\n'));
103
+ }
83
104
  }
84
105
  else {
85
- const scope = getCurrentHub().getScope();
106
+ const scope = Sentry.getCurrentHub().getScope();
86
107
  const parentSpan = scope === null || scope === void 0 ? void 0 : scope.getSpan();
87
108
  const span = parentSpan === null || parentSpan === void 0 ? void 0 : parentSpan.startChild({
88
109
  description: transactionName,
@@ -90,6 +111,7 @@ const useSentry = (options = {}) => {
90
111
  tags,
91
112
  });
92
113
  if (!span) {
114
+ // eslint-disable-next-line no-console
93
115
  console.warn([
94
116
  `Flag "startTransaction" is enabled but Sentry failed to find a transaction.`,
95
117
  `Try to create a transaction before GraphQL execution phase is started.`,
@@ -103,7 +125,7 @@ const useSentry = (options = {}) => {
103
125
  }
104
126
  rootSpan.setData('document', document);
105
127
  if (options.configureScope) {
106
- configureScope(scope => options.configureScope(args, scope));
128
+ Sentry.configureScope(scope => options.configureScope(args, scope));
107
129
  }
108
130
  if (onResolverCalled) {
109
131
  const sentryContext = {
@@ -114,19 +136,14 @@ const useSentry = (options = {}) => {
114
136
  extendContext({ [sentryTracingSymbol]: sentryContext });
115
137
  }
116
138
  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 => {
139
+ onExecuteDone(payload) {
140
+ const handleResult = ({ result, setResult }) => {
141
+ if (includeRawResult) {
142
+ rootSpan.setData('result', result);
143
+ }
144
+ if (result.errors && result.errors.length > 0) {
145
+ Sentry.withScope(scope => {
146
+ var _a;
130
147
  scope.setTransactionName(opName);
131
148
  scope.setTag('operation', operationType);
132
149
  scope.setTag('operationName', opName);
@@ -138,32 +155,43 @@ const useSentry = (options = {}) => {
138
155
  if (includeExecuteVariables) {
139
156
  scope.setExtra('variables', args.variableValues);
140
157
  }
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,
158
+ const errors = (_a = result.errors) === null || _a === void 0 ? void 0 : _a.map(err => {
159
+ var _a, _b;
160
+ const errorPath = ((_a = err.path) !== null && _a !== void 0 ? _a : []).join(' > ');
161
+ if (errorPath) {
162
+ scope.addBreadcrumb({
163
+ category: 'execution-path',
164
+ message: errorPath,
165
+ level: 'debug',
166
+ });
167
+ }
168
+ // Map index values in list to $index for better grouping of events.
169
+ const errorPathWithIndex = ((_b = err.path) !== null && _b !== void 0 ? _b : [])
170
+ .map(v => (typeof v === 'number' ? '$index' : v))
171
+ .join(' > ');
172
+ const eventId = Sentry.captureException(err, {
173
+ fingerprint: ['graphql', errorPathWithIndex, opName, operationType],
174
+ contexts: {
175
+ GraphQL: {
176
+ operationName: opName,
177
+ operationType,
178
+ variables: args.variableValues,
179
+ },
156
180
  },
157
- },
181
+ });
182
+ return addEventId(err, eventId);
183
+ });
184
+ setResult({
185
+ ...result,
186
+ errors,
158
187
  });
159
188
  });
160
189
  }
161
- }
162
- rootSpan.finish();
190
+ rootSpan.finish();
191
+ };
192
+ return handleStreamOrSingleExecutionResult(payload, handleResult);
163
193
  },
164
194
  };
165
195
  },
166
196
  };
167
197
  };
168
-
169
- export { defaultSkipError, useSentry };
package/package.json CHANGED
@@ -1,34 +1,56 @@
1
1
  {
2
2
  "name": "@envelop/sentry",
3
- "version": "4.0.0-alpha-36a1267.0",
3
+ "version": "4.0.0-alpha-5b6bfa8.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "@envelop/core": "2.1.0-alpha-36a1267.0",
7
- "@sentry/node": "^6",
8
- "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
6
+ "@envelop/core": "3.0.0-alpha-5b6bfa8.0",
7
+ "@sentry/node": "^6 || ^7",
8
+ "@graphql-tools/graphql": "0.1.0-alpha-33fb2950.0"
9
9
  },
10
+ "dependencies": {},
10
11
  "repository": {
11
12
  "type": "git",
12
- "url": "https://github.com/dotansimha/envelop.git",
13
+ "url": "https://github.com/n1ru4l/envelop.git",
13
14
  "directory": "packages/plugins/sentry"
14
15
  },
15
16
  "author": "Dotan Simha <dotansimha@gmail.com>",
16
17
  "license": "MIT",
17
- "main": "index.js",
18
- "module": "index.mjs",
19
- "typings": "index.d.ts",
18
+ "main": "cjs/index.js",
19
+ "module": "esm/index.js",
20
+ "typings": "typings/index.d.ts",
20
21
  "typescript": {
21
- "definition": "index.d.ts"
22
+ "definition": "typings/index.d.ts"
22
23
  },
24
+ "type": "module",
23
25
  "exports": {
24
26
  ".": {
25
- "require": "./index.js",
26
- "import": "./index.mjs"
27
+ "require": {
28
+ "types": "./typings/index.d.ts",
29
+ "default": "./cjs/index.js"
30
+ },
31
+ "import": {
32
+ "types": "./typings/index.d.ts",
33
+ "default": "./esm/index.js"
34
+ },
35
+ "default": {
36
+ "types": "./typings/index.d.ts",
37
+ "default": "./esm/index.js"
38
+ }
27
39
  },
28
40
  "./*": {
29
- "require": "./*.js",
30
- "import": "./*.mjs"
41
+ "require": {
42
+ "types": "./typings/*.d.ts",
43
+ "default": "./cjs/*.js"
44
+ },
45
+ "import": {
46
+ "types": "./typings/*.d.ts",
47
+ "default": "./esm/*.js"
48
+ },
49
+ "default": {
50
+ "types": "./typings/*.d.ts",
51
+ "default": "./esm/*.js"
52
+ }
31
53
  },
32
54
  "./package.json": "./package.json"
33
55
  }
34
- }
56
+ }
@@ -1,6 +1,7 @@
1
1
  import { Plugin } from '@envelop/core';
2
2
  import * as Sentry from '@sentry/node';
3
- import { ExecutionArgs } from 'graphql';
3
+ import type { TraceparentData } from '@sentry/types';
4
+ import { ExecutionArgs } from '@graphql-tools/graphql';
4
5
  export declare type SentryPluginOptions = {
5
6
  /**
6
7
  * Starts a new transaction for every GraphQL Operation.
@@ -34,6 +35,11 @@ export declare type SentryPluginOptions = {
34
35
  * @default false
35
36
  */
36
37
  includeExecuteVariables?: boolean;
38
+ /**
39
+ * The key of the event id in the error's extension. `null` to disable.
40
+ * @default sentryEventId
41
+ */
42
+ eventIdKey?: string | null;
37
43
  /**
38
44
  * Adds custom tags to every Transaction.
39
45
  */
@@ -48,6 +54,12 @@ export declare type SentryPluginOptions = {
48
54
  * @default operation's name or "Anonymous Operation" when missing)
49
55
  */
50
56
  transactionName?: (args: ExecutionArgs) => string;
57
+ /**
58
+ * Produces tracing data for Transaction
59
+ *
60
+ * @default is empty
61
+ */
62
+ traceparentData?: (args: ExecutionArgs) => TraceparentData | undefined;
51
63
  /**
52
64
  * Produces a "op" (operation) of created Span.
53
65
  *