@envelop/opentelemetry 6.1.0 → 6.2.0-alpha-20240118112454-9c258182

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.
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasInlineArgument = void 0;
4
+ const graphql_1 = require("graphql");
5
+ function hasInlineArgument(doc) {
6
+ let seen = false;
7
+ const leave = () => {
8
+ seen = true;
9
+ return graphql_1.BREAK;
10
+ };
11
+ (0, graphql_1.visit)(doc, {
12
+ StringValue: {
13
+ leave,
14
+ },
15
+ BooleanValue: {
16
+ leave,
17
+ },
18
+ FloatValue: {
19
+ leave,
20
+ },
21
+ EnumValue: {
22
+ leave,
23
+ },
24
+ IntValue: {
25
+ leave,
26
+ },
27
+ });
28
+ return seen;
29
+ }
30
+ exports.hasInlineArgument = hasInlineArgument;
package/cjs/index.js CHANGED
@@ -8,6 +8,7 @@ const on_resolve_1 = require("@envelop/on-resolve");
8
8
  const api_1 = require("@opentelemetry/api");
9
9
  const opentelemetry = tslib_1.__importStar(require("@opentelemetry/api"));
10
10
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
11
+ const has_inline_argument_js_1 = require("./has-inline-argument.js");
11
12
  var AttributeName;
12
13
  (function (AttributeName) {
13
14
  AttributeName["EXECUTION_ERROR"] = "graphql.execute.error";
@@ -18,6 +19,7 @@ var AttributeName;
18
19
  AttributeName["RESOLVER_RESULT_TYPE"] = "graphql.resolver.resultType";
19
20
  AttributeName["RESOLVER_ARGS"] = "graphql.resolver.args";
20
21
  AttributeName["EXECUTION_OPERATION_NAME"] = "graphql.execute.operationName";
22
+ AttributeName["EXECUTION_OPERATION_TYPE"] = "graphql.execute.operationType";
21
23
  AttributeName["EXECUTION_OPERATION_DOCUMENT"] = "graphql.execute.document";
22
24
  AttributeName["EXECUTION_VARIABLES"] = "graphql.execute.variables";
23
25
  })(AttributeName || (exports.AttributeName = AttributeName = {}));
@@ -30,12 +32,14 @@ const useOpenTelemetry = (options, tracingProvider, spanKind = api_1.SpanKind.SE
30
32
  tracingProvider = basicTraceProvider;
31
33
  }
32
34
  const tracer = tracingProvider.getTracer(serviceName);
35
+ const spanByContext = new WeakMap();
33
36
  return {
34
37
  onPluginInit({ addPlugin }) {
35
38
  if (options.resolvers) {
36
39
  addPlugin((0, on_resolve_1.useOnResolve)(({ info, context, args }) => {
37
- if (context && typeof context === 'object' && context[tracingSpanSymbol]) {
38
- const ctx = opentelemetry.trace.setSpan(opentelemetry.context.active(), context[tracingSpanSymbol]);
40
+ const parentSpan = spanByContext.get(context);
41
+ if (parentSpan) {
42
+ const ctx = opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan);
39
43
  const { fieldName, returnType, parentType } = info;
40
44
  const resolverSpan = tracer.startSpan(`${parentType.name}.${fieldName}`, {
41
45
  attributes: {
@@ -61,45 +65,149 @@ const useOpenTelemetry = (options, tracingProvider, spanKind = api_1.SpanKind.SE
61
65
  }));
62
66
  }
63
67
  },
64
- onExecute({ args, extendContext }) {
65
- const executionSpan = tracer.startSpan(`${args.operationName || 'Anonymous Operation'}`, {
68
+ onExecute({ args }) {
69
+ const operationAst = (0, graphql_1.getOperationAST)(args.document, args.operationName);
70
+ if (!operationAst) {
71
+ return;
72
+ }
73
+ const operationType = operationAst.operation;
74
+ let isDocumentLoggable;
75
+ if (options.document == null || options.document === true) {
76
+ if (options.variables) {
77
+ isDocumentLoggable = true;
78
+ }
79
+ else if (!(0, has_inline_argument_js_1.hasInlineArgument)(args.document)) {
80
+ isDocumentLoggable = true;
81
+ }
82
+ else {
83
+ isDocumentLoggable = false;
84
+ }
85
+ }
86
+ else {
87
+ isDocumentLoggable = false;
88
+ }
89
+ const operationName = operationAst.name?.value || 'anonymous';
90
+ const executionSpan = tracer.startSpan(`${operationType}.${operationName}`, {
66
91
  kind: spanKind,
67
92
  attributes: {
68
93
  ...spanAdditionalAttributes,
69
- [AttributeName.EXECUTION_OPERATION_NAME]: args.operationName ?? undefined,
70
- [AttributeName.EXECUTION_OPERATION_DOCUMENT]: (0, core_1.getDocumentString)(args.document, graphql_1.print),
94
+ [AttributeName.EXECUTION_OPERATION_NAME]: operationName,
95
+ [AttributeName.EXECUTION_OPERATION_TYPE]: operationType,
96
+ [AttributeName.EXECUTION_OPERATION_DOCUMENT]: isDocumentLoggable
97
+ ? (0, core_1.getDocumentString)(args.document, graphql_1.print)
98
+ : undefined,
71
99
  ...(options.variables
72
100
  ? { [AttributeName.EXECUTION_VARIABLES]: JSON.stringify(args.variableValues ?? {}) }
73
101
  : {}),
74
102
  },
75
103
  });
104
+ const otelContext = opentelemetry.trace.setSpan(opentelemetry.context.active(), executionSpan);
76
105
  const resultCbs = {
77
- onExecuteDone({ result }) {
78
- if ((0, core_1.isAsyncIterable)(result)) {
106
+ onExecuteDone({ result, setResult }) {
107
+ if (!(0, core_1.isAsyncIterable)(result)) {
108
+ if (result.data && options.result) {
109
+ executionSpan.setAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(result));
110
+ }
111
+ if (options.traceIdInResult) {
112
+ setResult(addTraceIdToResult(otelContext, result, options.traceIdInResult));
113
+ }
114
+ markError(executionSpan, result);
79
115
  executionSpan.end();
80
- // eslint-disable-next-line no-console
81
- console.warn(`Plugin "opentelemetry" encountered an AsyncIterator which is not supported yet, so tracing data is not available for the operation.`);
82
- return;
83
- }
84
- if (result.data && options.result) {
85
- executionSpan.setAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(result));
86
116
  }
87
- if (result.errors && result.errors.length > 0) {
88
- executionSpan.recordException({
89
- name: AttributeName.EXECUTION_ERROR,
90
- message: JSON.stringify(result.errors),
91
- });
92
- }
93
- executionSpan.end();
117
+ return {
118
+ // handles async iterator
119
+ onNext: ({ result, setResult }) => {
120
+ if (options.traceIdInResult) {
121
+ setResult(addTraceIdToResult(otelContext, result, options.traceIdInResult));
122
+ }
123
+ markError(executionSpan, result);
124
+ },
125
+ onEnd: () => {
126
+ executionSpan.end();
127
+ },
128
+ };
94
129
  },
95
130
  };
96
131
  if (options.resolvers) {
97
- extendContext({
98
- [tracingSpanSymbol]: executionSpan,
99
- });
132
+ spanByContext.set(args.contextValue, executionSpan);
133
+ }
134
+ return resultCbs;
135
+ },
136
+ onSubscribe({ args }) {
137
+ const operationAst = (0, graphql_1.getOperationAST)(args.document, args.operationName);
138
+ if (!operationAst) {
139
+ return;
140
+ }
141
+ const operationType = 'subscription';
142
+ let isDocumentLoggable;
143
+ if (options.variables) {
144
+ isDocumentLoggable = true;
145
+ }
146
+ else if (!(0, has_inline_argument_js_1.hasInlineArgument)(args.document)) {
147
+ isDocumentLoggable = true;
148
+ }
149
+ else {
150
+ isDocumentLoggable = false;
151
+ }
152
+ const operationName = operationAst.name?.value || 'anonymous';
153
+ const subscriptionSpan = tracer.startSpan(`${operationType}.${operationName}`, {
154
+ kind: spanKind,
155
+ attributes: {
156
+ ...spanAdditionalAttributes,
157
+ [AttributeName.EXECUTION_OPERATION_NAME]: operationName,
158
+ [AttributeName.EXECUTION_OPERATION_TYPE]: operationType,
159
+ [AttributeName.EXECUTION_OPERATION_DOCUMENT]: isDocumentLoggable
160
+ ? (0, core_1.getDocumentString)(args.document, graphql_1.print)
161
+ : undefined,
162
+ ...(options.variables
163
+ ? { [AttributeName.EXECUTION_VARIABLES]: JSON.stringify(args.variableValues ?? {}) }
164
+ : {}),
165
+ },
166
+ });
167
+ const otelContext = opentelemetry.trace.setSpan(opentelemetry.context.active(), subscriptionSpan);
168
+ const resultCbs = {
169
+ onSubscribeError: ({ error }) => {
170
+ if (error)
171
+ subscriptionSpan.setStatus({ code: api_1.SpanStatusCode.ERROR });
172
+ },
173
+ onSubscribeResult() {
174
+ return {
175
+ // handles async iterator
176
+ onNext: ({ result, setResult }) => {
177
+ if (options.traceIdInResult) {
178
+ setResult(addTraceIdToResult(otelContext, result, options.traceIdInResult));
179
+ }
180
+ markError(subscriptionSpan, result);
181
+ },
182
+ onEnd: () => {
183
+ subscriptionSpan.end();
184
+ },
185
+ };
186
+ },
187
+ };
188
+ if (options.resolvers) {
189
+ spanByContext.set(args.contextValue, subscriptionSpan);
100
190
  }
101
191
  return resultCbs;
102
192
  },
103
193
  };
104
194
  };
105
195
  exports.useOpenTelemetry = useOpenTelemetry;
196
+ function addTraceIdToResult(ctx, result, traceIdProp) {
197
+ return {
198
+ ...result,
199
+ extensions: {
200
+ ...result.extensions,
201
+ [traceIdProp]: opentelemetry.trace.getSpan(ctx)?.spanContext().traceId,
202
+ },
203
+ };
204
+ }
205
+ function markError(executionSpan, result) {
206
+ if (result.errors && result.errors.length > 0) {
207
+ executionSpan.setStatus({ code: opentelemetry.SpanStatusCode.ERROR });
208
+ executionSpan.recordException({
209
+ name: AttributeName.EXECUTION_ERROR,
210
+ message: JSON.stringify(result.errors),
211
+ });
212
+ }
213
+ }
@@ -0,0 +1,26 @@
1
+ import { BREAK, visit } from 'graphql';
2
+ export function hasInlineArgument(doc) {
3
+ let seen = false;
4
+ const leave = () => {
5
+ seen = true;
6
+ return BREAK;
7
+ };
8
+ visit(doc, {
9
+ StringValue: {
10
+ leave,
11
+ },
12
+ BooleanValue: {
13
+ leave,
14
+ },
15
+ FloatValue: {
16
+ leave,
17
+ },
18
+ EnumValue: {
19
+ leave,
20
+ },
21
+ IntValue: {
22
+ leave,
23
+ },
24
+ });
25
+ return seen;
26
+ }
package/esm/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import { print } from 'graphql';
2
- import { getDocumentString, isAsyncIterable } from '@envelop/core';
1
+ import { getOperationAST, print } from 'graphql';
2
+ import { getDocumentString, isAsyncIterable, } from '@envelop/core';
3
3
  import { useOnResolve } from '@envelop/on-resolve';
4
- import { SpanKind } from '@opentelemetry/api';
4
+ import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
5
5
  import * as opentelemetry from '@opentelemetry/api';
6
6
  import { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor, } from '@opentelemetry/sdk-trace-base';
7
+ import { hasInlineArgument } from './has-inline-argument.js';
7
8
  export var AttributeName;
8
9
  (function (AttributeName) {
9
10
  AttributeName["EXECUTION_ERROR"] = "graphql.execute.error";
@@ -14,6 +15,7 @@ export var AttributeName;
14
15
  AttributeName["RESOLVER_RESULT_TYPE"] = "graphql.resolver.resultType";
15
16
  AttributeName["RESOLVER_ARGS"] = "graphql.resolver.args";
16
17
  AttributeName["EXECUTION_OPERATION_NAME"] = "graphql.execute.operationName";
18
+ AttributeName["EXECUTION_OPERATION_TYPE"] = "graphql.execute.operationType";
17
19
  AttributeName["EXECUTION_OPERATION_DOCUMENT"] = "graphql.execute.document";
18
20
  AttributeName["EXECUTION_VARIABLES"] = "graphql.execute.variables";
19
21
  })(AttributeName || (AttributeName = {}));
@@ -26,12 +28,14 @@ export const useOpenTelemetry = (options, tracingProvider, spanKind = SpanKind.S
26
28
  tracingProvider = basicTraceProvider;
27
29
  }
28
30
  const tracer = tracingProvider.getTracer(serviceName);
31
+ const spanByContext = new WeakMap();
29
32
  return {
30
33
  onPluginInit({ addPlugin }) {
31
34
  if (options.resolvers) {
32
35
  addPlugin(useOnResolve(({ info, context, args }) => {
33
- if (context && typeof context === 'object' && context[tracingSpanSymbol]) {
34
- const ctx = opentelemetry.trace.setSpan(opentelemetry.context.active(), context[tracingSpanSymbol]);
36
+ const parentSpan = spanByContext.get(context);
37
+ if (parentSpan) {
38
+ const ctx = opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan);
35
39
  const { fieldName, returnType, parentType } = info;
36
40
  const resolverSpan = tracer.startSpan(`${parentType.name}.${fieldName}`, {
37
41
  attributes: {
@@ -57,44 +61,148 @@ export const useOpenTelemetry = (options, tracingProvider, spanKind = SpanKind.S
57
61
  }));
58
62
  }
59
63
  },
60
- onExecute({ args, extendContext }) {
61
- const executionSpan = tracer.startSpan(`${args.operationName || 'Anonymous Operation'}`, {
64
+ onExecute({ args }) {
65
+ const operationAst = getOperationAST(args.document, args.operationName);
66
+ if (!operationAst) {
67
+ return;
68
+ }
69
+ const operationType = operationAst.operation;
70
+ let isDocumentLoggable;
71
+ if (options.document == null || options.document === true) {
72
+ if (options.variables) {
73
+ isDocumentLoggable = true;
74
+ }
75
+ else if (!hasInlineArgument(args.document)) {
76
+ isDocumentLoggable = true;
77
+ }
78
+ else {
79
+ isDocumentLoggable = false;
80
+ }
81
+ }
82
+ else {
83
+ isDocumentLoggable = false;
84
+ }
85
+ const operationName = operationAst.name?.value || 'anonymous';
86
+ const executionSpan = tracer.startSpan(`${operationType}.${operationName}`, {
62
87
  kind: spanKind,
63
88
  attributes: {
64
89
  ...spanAdditionalAttributes,
65
- [AttributeName.EXECUTION_OPERATION_NAME]: args.operationName ?? undefined,
66
- [AttributeName.EXECUTION_OPERATION_DOCUMENT]: getDocumentString(args.document, print),
90
+ [AttributeName.EXECUTION_OPERATION_NAME]: operationName,
91
+ [AttributeName.EXECUTION_OPERATION_TYPE]: operationType,
92
+ [AttributeName.EXECUTION_OPERATION_DOCUMENT]: isDocumentLoggable
93
+ ? getDocumentString(args.document, print)
94
+ : undefined,
67
95
  ...(options.variables
68
96
  ? { [AttributeName.EXECUTION_VARIABLES]: JSON.stringify(args.variableValues ?? {}) }
69
97
  : {}),
70
98
  },
71
99
  });
100
+ const otelContext = opentelemetry.trace.setSpan(opentelemetry.context.active(), executionSpan);
72
101
  const resultCbs = {
73
- onExecuteDone({ result }) {
74
- if (isAsyncIterable(result)) {
102
+ onExecuteDone({ result, setResult }) {
103
+ if (!isAsyncIterable(result)) {
104
+ if (result.data && options.result) {
105
+ executionSpan.setAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(result));
106
+ }
107
+ if (options.traceIdInResult) {
108
+ setResult(addTraceIdToResult(otelContext, result, options.traceIdInResult));
109
+ }
110
+ markError(executionSpan, result);
75
111
  executionSpan.end();
76
- // eslint-disable-next-line no-console
77
- console.warn(`Plugin "opentelemetry" encountered an AsyncIterator which is not supported yet, so tracing data is not available for the operation.`);
78
- return;
79
- }
80
- if (result.data && options.result) {
81
- executionSpan.setAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(result));
82
112
  }
83
- if (result.errors && result.errors.length > 0) {
84
- executionSpan.recordException({
85
- name: AttributeName.EXECUTION_ERROR,
86
- message: JSON.stringify(result.errors),
87
- });
88
- }
89
- executionSpan.end();
113
+ return {
114
+ // handles async iterator
115
+ onNext: ({ result, setResult }) => {
116
+ if (options.traceIdInResult) {
117
+ setResult(addTraceIdToResult(otelContext, result, options.traceIdInResult));
118
+ }
119
+ markError(executionSpan, result);
120
+ },
121
+ onEnd: () => {
122
+ executionSpan.end();
123
+ },
124
+ };
90
125
  },
91
126
  };
92
127
  if (options.resolvers) {
93
- extendContext({
94
- [tracingSpanSymbol]: executionSpan,
95
- });
128
+ spanByContext.set(args.contextValue, executionSpan);
129
+ }
130
+ return resultCbs;
131
+ },
132
+ onSubscribe({ args }) {
133
+ const operationAst = getOperationAST(args.document, args.operationName);
134
+ if (!operationAst) {
135
+ return;
136
+ }
137
+ const operationType = 'subscription';
138
+ let isDocumentLoggable;
139
+ if (options.variables) {
140
+ isDocumentLoggable = true;
141
+ }
142
+ else if (!hasInlineArgument(args.document)) {
143
+ isDocumentLoggable = true;
144
+ }
145
+ else {
146
+ isDocumentLoggable = false;
147
+ }
148
+ const operationName = operationAst.name?.value || 'anonymous';
149
+ const subscriptionSpan = tracer.startSpan(`${operationType}.${operationName}`, {
150
+ kind: spanKind,
151
+ attributes: {
152
+ ...spanAdditionalAttributes,
153
+ [AttributeName.EXECUTION_OPERATION_NAME]: operationName,
154
+ [AttributeName.EXECUTION_OPERATION_TYPE]: operationType,
155
+ [AttributeName.EXECUTION_OPERATION_DOCUMENT]: isDocumentLoggable
156
+ ? getDocumentString(args.document, print)
157
+ : undefined,
158
+ ...(options.variables
159
+ ? { [AttributeName.EXECUTION_VARIABLES]: JSON.stringify(args.variableValues ?? {}) }
160
+ : {}),
161
+ },
162
+ });
163
+ const otelContext = opentelemetry.trace.setSpan(opentelemetry.context.active(), subscriptionSpan);
164
+ const resultCbs = {
165
+ onSubscribeError: ({ error }) => {
166
+ if (error)
167
+ subscriptionSpan.setStatus({ code: SpanStatusCode.ERROR });
168
+ },
169
+ onSubscribeResult() {
170
+ return {
171
+ // handles async iterator
172
+ onNext: ({ result, setResult }) => {
173
+ if (options.traceIdInResult) {
174
+ setResult(addTraceIdToResult(otelContext, result, options.traceIdInResult));
175
+ }
176
+ markError(subscriptionSpan, result);
177
+ },
178
+ onEnd: () => {
179
+ subscriptionSpan.end();
180
+ },
181
+ };
182
+ },
183
+ };
184
+ if (options.resolvers) {
185
+ spanByContext.set(args.contextValue, subscriptionSpan);
96
186
  }
97
187
  return resultCbs;
98
188
  },
99
189
  };
100
190
  };
191
+ function addTraceIdToResult(ctx, result, traceIdProp) {
192
+ return {
193
+ ...result,
194
+ extensions: {
195
+ ...result.extensions,
196
+ [traceIdProp]: opentelemetry.trace.getSpan(ctx)?.spanContext().traceId,
197
+ },
198
+ };
199
+ }
200
+ function markError(executionSpan, result) {
201
+ if (result.errors && result.errors.length > 0) {
202
+ executionSpan.setStatus({ code: opentelemetry.SpanStatusCode.ERROR });
203
+ executionSpan.recordException({
204
+ name: AttributeName.EXECUTION_ERROR,
205
+ message: JSON.stringify(result.errors),
206
+ });
207
+ }
208
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envelop/opentelemetry",
3
- "version": "6.1.0",
3
+ "version": "6.2.0-alpha-20240118112454-9c258182",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "@envelop/core": "^5.0.0",
@@ -0,0 +1,2 @@
1
+ import { DocumentNode } from 'graphql';
2
+ export declare function hasInlineArgument(doc: DocumentNode): boolean;
@@ -0,0 +1,2 @@
1
+ import { DocumentNode } from 'graphql';
2
+ export declare function hasInlineArgument(doc: DocumentNode): boolean;
@@ -10,14 +10,17 @@ export declare enum AttributeName {
10
10
  RESOLVER_RESULT_TYPE = "graphql.resolver.resultType",
11
11
  RESOLVER_ARGS = "graphql.resolver.args",
12
12
  EXECUTION_OPERATION_NAME = "graphql.execute.operationName",
13
+ EXECUTION_OPERATION_TYPE = "graphql.execute.operationType",
13
14
  EXECUTION_OPERATION_DOCUMENT = "graphql.execute.document",
14
15
  EXECUTION_VARIABLES = "graphql.execute.variables"
15
16
  }
16
17
  declare const tracingSpanSymbol: unique symbol;
17
18
  export type TracingOptions = {
18
- resolvers: boolean;
19
- variables: boolean;
20
- result: boolean;
19
+ document?: boolean;
20
+ resolvers?: boolean;
21
+ variables?: boolean;
22
+ result?: boolean;
23
+ traceIdInResult?: string;
21
24
  };
22
25
  type PluginContext = {
23
26
  [tracingSpanSymbol]: opentelemetry.Span;
@@ -10,14 +10,17 @@ export declare enum AttributeName {
10
10
  RESOLVER_RESULT_TYPE = "graphql.resolver.resultType",
11
11
  RESOLVER_ARGS = "graphql.resolver.args",
12
12
  EXECUTION_OPERATION_NAME = "graphql.execute.operationName",
13
+ EXECUTION_OPERATION_TYPE = "graphql.execute.operationType",
13
14
  EXECUTION_OPERATION_DOCUMENT = "graphql.execute.document",
14
15
  EXECUTION_VARIABLES = "graphql.execute.variables"
15
16
  }
16
17
  declare const tracingSpanSymbol: unique symbol;
17
18
  export type TracingOptions = {
18
- resolvers: boolean;
19
- variables: boolean;
20
- result: boolean;
19
+ document?: boolean;
20
+ resolvers?: boolean;
21
+ variables?: boolean;
22
+ result?: boolean;
23
+ traceIdInResult?: string;
21
24
  };
22
25
  type PluginContext = {
23
26
  [tracingSpanSymbol]: opentelemetry.Span;