From 473624c83ff30aec651324fbb1fce928a4152021 Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 13:12:38 +0100 Subject: [PATCH 1/7] feat: init graphql-otel usage --- packages/plugins/opentelemetry/package.json | 11 +- packages/plugins/opentelemetry/src/index.ts | 128 ++-------- .../test/use-open-telemetry.spec.ts | 115 +++++---- packages/plugins/opentelemetry/test/utils.ts | 92 ++++++++ pnpm-lock.yaml | 222 ++++++++++++++++-- 5 files changed, 381 insertions(+), 187 deletions(-) create mode 100644 packages/plugins/opentelemetry/test/utils.ts diff --git a/packages/plugins/opentelemetry/package.json b/packages/plugins/opentelemetry/package.json index 34c99bf06e..a9f19e9427 100644 --- a/packages/plugins/opentelemetry/package.json +++ b/packages/plugins/opentelemetry/package.json @@ -49,8 +49,15 @@ }, "dependencies": { "@envelop/on-resolve": "^2.0.6", - "@opentelemetry/api": "^1.0.0", - "@opentelemetry/sdk-trace-base": "^1.11.0", + "@opentelemetry/api": "^1.2.0", + "@opentelemetry/context-async-hooks": "^1.7.0", + "@opentelemetry/core": "^1.7.0", + "@opentelemetry/instrumentation": "^0.33.0", + "@opentelemetry/otlp-exporter-base": "^0.33.0", + "@opentelemetry/resources": "^1.7.0", + "@opentelemetry/sdk-trace-base": "^1.7.0", + "@opentelemetry/semantic-conventions": "^1.12.0", + "graphql-otel": "^0.0.9", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/plugins/opentelemetry/src/index.ts b/packages/plugins/opentelemetry/src/index.ts index bf0fad9b4e..732fa5b99e 100644 --- a/packages/plugins/opentelemetry/src/index.ts +++ b/packages/plugins/opentelemetry/src/index.ts @@ -1,13 +1,6 @@ -import { print } from 'graphql'; -import { isAsyncIterable, OnExecuteHookResult, Plugin } from '@envelop/core'; -import { useOnResolve } from '@envelop/on-resolve'; -import { SpanAttributes, SpanKind, TracerProvider } from '@opentelemetry/api'; +import { GraphQLOTELContext, traceDirective } from 'graphql-otel'; +import { Plugin } from '@envelop/core'; import * as opentelemetry from '@opentelemetry/api'; -import { - BasicTracerProvider, - ConsoleSpanExporter, - SimpleSpanProcessor, -} from '@opentelemetry/sdk-trace-base'; export enum AttributeName { EXECUTION_ERROR = 'graphql.execute.error', @@ -34,110 +27,31 @@ type PluginContext = { [tracingSpanSymbol]: opentelemetry.Span; }; -export const useOpenTelemetry = ( - options: TracingOptions, - tracingProvider?: TracerProvider, - spanKind: SpanKind = SpanKind.SERVER, - spanAdditionalAttributes: SpanAttributes = {}, - serviceName = 'graphql', -): Plugin => { - if (!tracingProvider) { - const basicTraceProvider = new BasicTracerProvider(); - basicTraceProvider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); - basicTraceProvider.register(); - tracingProvider = basicTraceProvider; - } - - const tracer = tracingProvider.getTracer(serviceName); +const graphqlMiddlewareAppliedTransformSymbol = Symbol('graphqlMiddleware.appliedTransform'); +export const useOpenTelemetry = (): Plugin => { return { - onPluginInit({ addPlugin }) { - if (options.resolvers) { - addPlugin( - useOnResolve(({ info, context, args }) => { - if (context && typeof context === 'object' && context[tracingSpanSymbol]) { - const ctx = opentelemetry.trace.setSpan( - opentelemetry.context.active(), - context[tracingSpanSymbol], - ); - const { fieldName, returnType, parentType } = info; - - const resolverSpan = tracer.startSpan( - `${parentType.name}.${fieldName}`, - { - attributes: { - [AttributeName.RESOLVER_FIELD_NAME]: fieldName, - [AttributeName.RESOLVER_TYPE_NAME]: parentType.toString(), - [AttributeName.RESOLVER_RESULT_TYPE]: returnType.toString(), - [AttributeName.RESOLVER_ARGS]: JSON.stringify(args || {}), - }, - }, - ctx, - ); - - return ({ result }) => { - if (result instanceof Error) { - resolverSpan.recordException({ - name: AttributeName.RESOLVER_EXCEPTION, - message: JSON.stringify(result), - }); - } else { - resolverSpan.end(); - } - }; - } - - return () => {}; - }), - ); - } - }, - onExecute({ args, extendContext }) { - const executionSpan = tracer.startSpan(`${args.operationName || 'Anonymous Operation'}`, { - kind: spanKind, - attributes: { - ...spanAdditionalAttributes, - [AttributeName.EXECUTION_OPERATION_NAME]: args.operationName ?? undefined, - [AttributeName.EXECUTION_OPERATION_DOCUMENT]: print(args.document), - ...(options.variables - ? { [AttributeName.EXECUTION_VARIABLES]: JSON.stringify(args.variableValues ?? {}) } - : {}), - }, + onContextBuilding({ extendContext }) { + extendContext({ + // @ts-ignore + GraphQLOTELContext: new GraphQLOTELContext(), }); + }, + onExecute({ args }) { + console.log(args.contextValue); + console.log('executing'); + }, + onSchemaChange({ schema, replaceSchema }) { + console.log('shjhskhjsghkjsghjgshjgshjksg'); + if (schema.extensions?.[graphqlMiddlewareAppliedTransformSymbol]) { + return; + } - const resultCbs: OnExecuteHookResult = { - onExecuteDone({ result }) { - if (isAsyncIterable(result)) { - executionSpan.end(); - // eslint-disable-next-line no-console - console.warn( - `Plugin "newrelic" encountered a AsyncIterator which is not supported yet, so tracing data is not available for the operation.`, - ); - return; - } - - if (result.data && options.result) { - executionSpan.setAttribute(AttributeName.EXECUTION_RESULT, JSON.stringify(result)); - } - - if (result.errors && result.errors.length > 0) { - executionSpan.recordException({ - name: AttributeName.EXECUTION_ERROR, - message: JSON.stringify(result.errors), - }); - } - - executionSpan.end(); - }, - }; + const directive = traceDirective('trace'); - if (options.resolvers) { - extendContext({ - [tracingSpanSymbol]: executionSpan, - }); - } + const transformedSchema = directive.transformer(schema); - return resultCbs; + replaceSchema(transformedSchema); }, }; }; diff --git a/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts b/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts index 58fb38344b..84800416f7 100644 --- a/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts +++ b/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts @@ -1,81 +1,74 @@ import { buildSchema } from 'graphql'; -import { assertSingleExecutionValue, createTestkit } from '@envelop/testing'; -import { - BasicTracerProvider, - InMemorySpanExporter, - SimpleSpanProcessor, -} from '@opentelemetry/sdk-trace-base'; +import { traceDirective } from 'graphql-otel'; +import { createTestkit } from '@envelop/testing'; +import { InMemorySpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { useOpenTelemetry } from '../src/index.js'; +import { buildSpanTree, cleanSpanTreeForSnapshot, otelSetup } from './utils'; -function createTraceProvider(exporter: InMemorySpanExporter) { - const provider = new BasicTracerProvider(); - const processor = new SimpleSpanProcessor(exporter); - provider.addSpanProcessor(processor); - provider.register(); - return provider; -} +const inMemorySpanExporter = otelSetup() as InMemorySpanExporter; describe('useOpenTelemetry', () => { + beforeEach(() => { + inMemorySpanExporter.reset(); + }); + + const trace = traceDirective('trace'); + const schema = buildSchema(/* GraphQL */ ` + ${trace.typeDefs} + + type User { + name: String @trace + posts: [Post] @trace + } + + type Post { + title: String + } + type Query { - ping: String + users: [User] @trace } `); + const query = /* GraphQL */ ` query { - ping + users { + name + posts { + title + } + } } `; - const useTestOpenTelemetry = (exporter?: InMemorySpanExporter, options?: any) => - useOpenTelemetry( - { - resolvers: false, - result: false, - variables: false, - ...(options ?? {}), - }, - exporter ? createTraceProvider(exporter) : undefined, - ); - - it('Should override execute function', async () => { - const onExecuteSpy = jest.fn(); - const testInstance = createTestkit( - [ - useTestOpenTelemetry(), - { - onExecute: onExecuteSpy, - }, - ], - schema, - ); - - const result = await testInstance.execute(query); - assertSingleExecutionValue(result); - expect(onExecuteSpy).toHaveBeenCalledTimes(1); - }); - - it('Should add execution span', async () => { - const exporter = new InMemorySpanExporter(); - const testInstance = createTestkit([useTestOpenTelemetry(exporter)], schema); + it('Should wrap the traced fields in a span and assert the tree', async () => { + const testInstance = createTestkit([useOpenTelemetry()], schema); await testInstance.execute(query); - const actual = exporter.getFinishedSpans(); - expect(actual.length).toBe(1); - expect(actual[0].name).toBe('Anonymous Operation'); - }); + const spans = inMemorySpanExporter.getFinishedSpans(); + const rootSpan = spans.find(span => !span.parentSpanId) as ReadableSpan; + const spanTree = buildSpanTree({ span: rootSpan, children: [] }, spans); - it('Should add resolver span if requested', async () => { - const exporter = new InMemorySpanExporter(); - const testInstance = createTestkit( - [useTestOpenTelemetry(exporter, { resolvers: true })], - schema, - ); + const cleanTree = cleanSpanTreeForSnapshot(spanTree); - await testInstance.execute(query); - const actual = exporter.getFinishedSpans(); - expect(actual.length).toBe(2); - expect(actual[0].name).toBe('Query.ping'); - expect(actual[1].name).toBe('Anonymous Operation'); + expect(JSON.stringify(cleanTree, null, 2)).toMatchInlineSnapshot(` + "{ + \\"span\\": { + \\"attributes\\": { + \\"query\\": \\"{\\\\n users {\\\\n name\\\\n posts {\\\\n title\\\\n }\\\\n }\\\\n}\\" + }, + \\"links\\": [], + \\"events\\": [], + \\"status\\": { + \\"code\\": 0 + }, + \\"name\\": \\"Query:users\\", + \\"kind\\": 0 + }, + \\"children\\": [] + }" + `); + console.log(spanTree); }); }); diff --git a/packages/plugins/opentelemetry/test/utils.ts b/packages/plugins/opentelemetry/test/utils.ts new file mode 100644 index 0000000000..feed7d9b61 --- /dev/null +++ b/packages/plugins/opentelemetry/test/utils.ts @@ -0,0 +1,92 @@ +import * as api from '@opentelemetry/api'; +import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; +import { Resource } from '@opentelemetry/resources'; +import { + BasicTracerProvider, + InMemorySpanExporter, + ReadableSpan, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; + +type Tree = { + span: ReadableSpan; + children?: Tree[]; +}; + +export function otelSetup(): InMemorySpanExporter { + const exporter = new InMemorySpanExporter(); + + const contextManager = new AsyncHooksContextManager().enable(); + + api.context.setGlobalContextManager(contextManager); + + const provider = new BasicTracerProvider({ + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'graphql-otel', + [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0', + }), + }); + + provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); + + provider.register(); + + return exporter; +} + +export type SpanTree = { + span: ReadableSpan; + children: SpanTree[]; +}; + +export function buildSpanTree(tree: SpanTree, spans: ReadableSpan[]): SpanTree { + const childrenSpans = spans.filter(span => span.parentSpanId === tree.span.spanContext().spanId); + + if (childrenSpans.length) { + tree.children = childrenSpans.map(span => buildSpanTree({ span, children: [] }, spans)); + } else { + tree.children = []; + } + + const simpleTree = JSON.stringify( + tree, + (key, value) => { + const removedKeys = [ + 'endTime', + 'startTime', + '_spanLimits', + 'instrumentationLibrary', + '_spanProcessor', + '_attributeValueLengthLimit', + '_duration', + ]; + + if (removedKeys.includes(key)) { + return undefined; + } else { + return value; + } + }, + 2, + ); + + return JSON.parse(simpleTree); +} + +export function cleanSpanTreeForSnapshot(tree: Tree) { + return JSON.parse(JSON.stringify(tree), (key, value) => { + if (key[0] === '_') return undefined; + if (key === 'parentSpanId') return ''; + if (key === 'itx_id') return ''; + if (key === 'endTime') return ''; + if (key === 'startTime') return ''; + if (key === 'db.type') return ''; + if (key === 'db.statement') return ''; + if (key === 'resource') return undefined; + if (key === 'spanId') return ''; + if (key === 'traceId') return ''; + + return value; + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6076cb0e8..3bd689dd6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1137,11 +1137,32 @@ importers: specifier: ^2.0.6 version: link:../on-resolve '@opentelemetry/api': - specifier: ^1.0.0 - version: 1.0.3 + specifier: ^1.2.0 + version: 1.4.1 + '@opentelemetry/context-async-hooks': + specifier: ^1.7.0 + version: 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/core': + specifier: ^1.7.0 + version: 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': + specifier: ^0.33.0 + version: 0.33.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': + specifier: ^0.33.0 + version: 0.33.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': + specifier: ^1.7.0 + version: 1.12.0(@opentelemetry/api@1.4.1) '@opentelemetry/sdk-trace-base': - specifier: ^1.11.0 - version: 1.11.0(@opentelemetry/api@1.0.3) + specifier: ^1.7.0 + version: 1.11.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': + specifier: ^1.12.0 + version: 1.12.0 + graphql-otel: + specifier: ^0.0.9 + version: 0.0.9 tslib: specifier: ^2.5.0 version: 2.5.0 @@ -5881,14 +5902,45 @@ packages: rimraf: 3.0.2 dev: true + /@opentelemetry/api-metrics@0.33.0: + resolution: + { + integrity: sha512-78evfPRRRnJA6uZ3xuBuS3VZlXTO/LRs+Ff1iv3O/7DgibCtq9k27T6Zlj8yRdJDFmcjcbQrvC0/CpDpWHaZYA==, + } + engines: { node: '>=14' } + dependencies: + '@opentelemetry/api': 1.4.1 + dev: false + /@opentelemetry/api@1.0.3: resolution: { integrity: sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ==, } engines: { node: '>=8.0.0' } + dev: true + + /@opentelemetry/api@1.4.1: + resolution: + { + integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==, + } + engines: { node: '>=8.0.0' } + dev: false - /@opentelemetry/core@1.11.0(@opentelemetry/api@1.0.3): + /@opentelemetry/context-async-hooks@1.12.0(@opentelemetry/api@1.4.1): + resolution: + { + integrity: sha512-PmwAanPNWCyS9JYFzhzVzHgviLhc0UHjOwdth+hp3HgQQ9XZZNE635P8JhAUHZmbghW9/qQFafRWOS4VN9VVnQ==, + } + engines: { node: '>=14' } + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.5.0' + dependencies: + '@opentelemetry/api': 1.4.1 + dev: false + + /@opentelemetry/core@1.11.0(@opentelemetry/api@1.4.1): resolution: { integrity: sha512-aP1wHSb+YfU0pM63UAkizYPuS4lZxzavHHw5KJfFNN2oWQ79HSm6JR3CzwFKHwKhSzHN8RE9fgP1IdVJ8zmo1w==, @@ -5897,11 +5949,68 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: - '@opentelemetry/api': 1.0.3 + '@opentelemetry/api': 1.4.1 '@opentelemetry/semantic-conventions': 1.11.0 dev: false - /@opentelemetry/resources@1.11.0(@opentelemetry/api@1.0.3): + /@opentelemetry/core@1.12.0(@opentelemetry/api@1.4.1): + resolution: + { + integrity: sha512-4DWYNb3dLs2mSCGl65jY3aEgbvPWSHVQV/dmDWiYeWUrMakZQFcymqZOSUNZO0uDrEJoxMu8O5tZktX6UKFwag==, + } + engines: { node: '>=14' } + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.5.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/semantic-conventions': 1.12.0 + dev: false + + /@opentelemetry/core@1.7.0(@opentelemetry/api@1.4.1): + resolution: + { + integrity: sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ==, + } + engines: { node: '>=14' } + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.3.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/semantic-conventions': 1.7.0 + dev: false + + /@opentelemetry/instrumentation@0.33.0(@opentelemetry/api@1.4.1): + resolution: + { + integrity: sha512-8joPjKJ6TznNt04JbnzZG+m1j/4wm1OIrX7DEw/V5lyZ9/2fahIqG72jeZ26VKOZnLOpVzUUnU/dweURqBzT3Q==, + } + engines: { node: '>=14' } + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-metrics': 0.33.0 + require-in-the-middle: 5.2.0 + semver: 7.3.8 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@opentelemetry/otlp-exporter-base@0.33.0(@opentelemetry/api@1.4.1): + resolution: + { + integrity: sha512-st+nsgv23BXSARFwugy6pheulDfOKjIFvzoYOUzPQDVhQtU8+l7dc50rIEybwXghb13o7mZs6Nb8KOvRk57qww==, + } + engines: { node: '>=14' } + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.7.0(@opentelemetry/api@1.4.1) + dev: false + + /@opentelemetry/resources@1.11.0(@opentelemetry/api@1.4.1): resolution: { integrity: sha512-y0z2YJTqk0ag+hGT4EXbxH/qPhDe8PfwltYb4tXIEsozgEFfut/bqW7H7pDvylmCjBRMG4NjtLp57V1Ev++brA==, @@ -5910,12 +6019,26 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: - '@opentelemetry/api': 1.0.3 - '@opentelemetry/core': 1.11.0(@opentelemetry/api@1.0.3) + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.11.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': 1.11.0 dev: false - /@opentelemetry/sdk-trace-base@1.11.0(@opentelemetry/api@1.0.3): + /@opentelemetry/resources@1.12.0(@opentelemetry/api@1.4.1): + resolution: + { + integrity: sha512-gunMKXG0hJrR0LXrqh7BVbziA/+iJBL3ZbXCXO64uY+SrExkwoyJkpiq9l5ismkGF/A20mDEV7tGwh+KyPw00Q==, + } + engines: { node: '>=14' } + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.5.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.12.0 + dev: false + + /@opentelemetry/sdk-trace-base@1.11.0(@opentelemetry/api@1.4.1): resolution: { integrity: sha512-DV8e5/Qo42V8FMBlQ0Y0Liv6Hl/Pp5bAZ73s7r1euX8w4bpRes1B7ACiA4yujADbWMJxBgSo4fGbi4yjmTMG2A==, @@ -5924,9 +6047,9 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: - '@opentelemetry/api': 1.0.3 - '@opentelemetry/core': 1.11.0(@opentelemetry/api@1.0.3) - '@opentelemetry/resources': 1.11.0(@opentelemetry/api@1.0.3) + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.11.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.11.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': 1.11.0 dev: false @@ -5938,6 +6061,22 @@ packages: engines: { node: '>=14' } dev: false + /@opentelemetry/semantic-conventions@1.12.0: + resolution: + { + integrity: sha512-hO+bdeGOlJwqowUBoZF5LyP3ORUFOP1G0GRv8N45W/cztXbT2ZEXaAzfokRS9Xc9FWmYrDj32mF6SzH6wuoIyA==, + } + engines: { node: '>=14' } + dev: false + + /@opentelemetry/semantic-conventions@1.7.0: + resolution: + { + integrity: sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA==, + } + engines: { node: '>=14' } + dev: false + /@panva/asn1.js@1.0.0: resolution: { @@ -13094,6 +13233,24 @@ packages: ramda: 0.28.0 dev: true + /graphql-otel@0.0.9: + resolution: + { + integrity: sha512-Lj4kShVInTcdtH3956r0qLyavJwxqe0iN5u/Rw/egvHRZSk3uxctW28PitCk8pf08lfsjM/AvDKrTRKDml8U+Q==, + } + engines: { node: '>=16.0.0' } + dependencies: + '@graphql-tools/utils': 8.13.1(graphql@16.6.0) + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.33.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.11.0(@opentelemetry/api@1.4.1) + graphql: 16.6.0 + safe-json-stringify: 1.2.0 + transitivePeerDependencies: + - supports-color + dev: false + /graphql-query-complexity@0.7.2(graphql@16.6.0): resolution: { @@ -13977,7 +14134,6 @@ packages: } dependencies: has: 1.0.3 - dev: true /is-date-object@1.0.4: resolution: @@ -17140,6 +17296,13 @@ packages: hasBin: true dev: true + /module-details-from-path@1.0.3: + resolution: + { + integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==, + } + dev: false + /morgan@1.6.1: resolution: { @@ -18126,7 +18289,6 @@ packages: { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, } - dev: true /path-to-regexp@0.1.7: resolution: @@ -19738,6 +19900,20 @@ packages: } engines: { node: '>=0.10.0' } + /require-in-the-middle@5.2.0: + resolution: + { + integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==, + } + engines: { node: '>=6' } + dependencies: + debug: 4.3.4 + module-details-from-path: 1.0.3 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + dev: false + /require-main-filename@2.0.0: resolution: { @@ -19815,7 +19991,6 @@ packages: is-core-module: 2.11.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true /response-time@2.3.2: resolution: @@ -19982,6 +20157,13 @@ packages: integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, } + /safe-json-stringify@1.2.0: + resolution: + { + integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==, + } + dev: false + /safe-regex-test@1.0.0: resolution: { @@ -20360,6 +20542,13 @@ packages: vscode-textmate: 8.0.0 dev: false + /shimmer@1.2.1: + resolution: + { + integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==, + } + dev: false + /side-channel@1.0.4: resolution: { @@ -21107,7 +21296,6 @@ packages: integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, } engines: { node: '>= 0.4' } - dev: true /svgo@2.8.0: resolution: From 25f32b61bde65f44b675007380c41432437772f0 Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 13:13:37 +0100 Subject: [PATCH 2/7] refactor: eslint comment --- packages/plugins/opentelemetry/src/index.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/plugins/opentelemetry/src/index.ts b/packages/plugins/opentelemetry/src/index.ts index 732fa5b99e..0ee1fa21de 100644 --- a/packages/plugins/opentelemetry/src/index.ts +++ b/packages/plugins/opentelemetry/src/index.ts @@ -33,16 +33,12 @@ export const useOpenTelemetry = (): Plugin => { return { onContextBuilding({ extendContext }) { extendContext({ - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - dafuck GraphQLOTELContext: new GraphQLOTELContext(), }); }, - onExecute({ args }) { - console.log(args.contextValue); - console.log('executing'); - }, onSchemaChange({ schema, replaceSchema }) { - console.log('shjhskhjsghkjsghjgshjgshjksg'); if (schema.extensions?.[graphqlMiddlewareAppliedTransformSymbol]) { return; } From 850e62a0f3f72108957667f4a1b1f7f83886971c Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 13:15:35 +0100 Subject: [PATCH 3/7] refactor: * --- packages/plugins/opentelemetry/src/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/plugins/opentelemetry/src/index.ts b/packages/plugins/opentelemetry/src/index.ts index 0ee1fa21de..af5b341414 100644 --- a/packages/plugins/opentelemetry/src/index.ts +++ b/packages/plugins/opentelemetry/src/index.ts @@ -17,12 +17,6 @@ export enum AttributeName { const tracingSpanSymbol = Symbol('OPEN_TELEMETRY_GRAPHQL'); -export type TracingOptions = { - resolvers: boolean; - variables: boolean; - result: boolean; -}; - type PluginContext = { [tracingSpanSymbol]: opentelemetry.Span; }; From 2f9c0ab3e1153a91cdcd7b6218ff95892e12b872 Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 13:17:38 +0100 Subject: [PATCH 4/7] pkg: move non prod to dev --- packages/plugins/opentelemetry/package.json | 12 +++--- pnpm-lock.yaml | 48 ++++++++------------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/packages/plugins/opentelemetry/package.json b/packages/plugins/opentelemetry/package.json index a9f19e9427..168a879f55 100644 --- a/packages/plugins/opentelemetry/package.json +++ b/packages/plugins/opentelemetry/package.json @@ -49,19 +49,19 @@ }, "dependencies": { "@envelop/on-resolve": "^2.0.6", + "@opentelemetry/core": "^1.7.0", + "graphql-otel": "^0.0.9", + "tslib": "^2.5.0" + }, + "devDependencies": { + "@envelop/core": "^3.0.6", "@opentelemetry/api": "^1.2.0", "@opentelemetry/context-async-hooks": "^1.7.0", - "@opentelemetry/core": "^1.7.0", "@opentelemetry/instrumentation": "^0.33.0", "@opentelemetry/otlp-exporter-base": "^0.33.0", "@opentelemetry/resources": "^1.7.0", "@opentelemetry/sdk-trace-base": "^1.7.0", "@opentelemetry/semantic-conventions": "^1.12.0", - "graphql-otel": "^0.0.9", - "tslib": "^2.5.0" - }, - "devDependencies": { - "@envelop/core": "^3.0.6", "graphql": "16.6.0", "typescript": "4.8.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bd689dd6c..dfee395ac9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1136,15 +1136,25 @@ importers: '@envelop/on-resolve': specifier: ^2.0.6 version: link:../on-resolve + '@opentelemetry/core': + specifier: ^1.7.0 + version: 1.12.0(@opentelemetry/api@1.4.1) + graphql-otel: + specifier: ^0.0.9 + version: 0.0.9 + tslib: + specifier: ^2.5.0 + version: 2.5.0 + devDependencies: + '@envelop/core': + specifier: ^3.0.6 + version: link:../../core '@opentelemetry/api': specifier: ^1.2.0 version: 1.4.1 '@opentelemetry/context-async-hooks': specifier: ^1.7.0 version: 1.12.0(@opentelemetry/api@1.4.1) - '@opentelemetry/core': - specifier: ^1.7.0 - version: 1.12.0(@opentelemetry/api@1.4.1) '@opentelemetry/instrumentation': specifier: ^0.33.0 version: 0.33.0(@opentelemetry/api@1.4.1) @@ -1160,16 +1170,6 @@ importers: '@opentelemetry/semantic-conventions': specifier: ^1.12.0 version: 1.12.0 - graphql-otel: - specifier: ^0.0.9 - version: 0.0.9 - tslib: - specifier: ^2.5.0 - version: 2.5.0 - devDependencies: - '@envelop/core': - specifier: ^3.0.6 - version: link:../../core graphql: specifier: 16.6.0 version: 16.6.0 @@ -5910,7 +5910,6 @@ packages: engines: { node: '>=14' } dependencies: '@opentelemetry/api': 1.4.1 - dev: false /@opentelemetry/api@1.0.3: resolution: @@ -5926,7 +5925,6 @@ packages: integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==, } engines: { node: '>=8.0.0' } - dev: false /@opentelemetry/context-async-hooks@1.12.0(@opentelemetry/api@1.4.1): resolution: @@ -5938,7 +5936,7 @@ packages: '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 - dev: false + dev: true /@opentelemetry/core@1.11.0(@opentelemetry/api@1.4.1): resolution: @@ -5951,7 +5949,6 @@ packages: dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/semantic-conventions': 1.11.0 - dev: false /@opentelemetry/core@1.12.0(@opentelemetry/api@1.4.1): resolution: @@ -5964,7 +5961,6 @@ packages: dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/semantic-conventions': 1.12.0 - dev: false /@opentelemetry/core@1.7.0(@opentelemetry/api@1.4.1): resolution: @@ -5977,7 +5973,7 @@ packages: dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/semantic-conventions': 1.7.0 - dev: false + dev: true /@opentelemetry/instrumentation@0.33.0(@opentelemetry/api@1.4.1): resolution: @@ -5995,7 +5991,6 @@ packages: shimmer: 1.2.1 transitivePeerDependencies: - supports-color - dev: false /@opentelemetry/otlp-exporter-base@0.33.0(@opentelemetry/api@1.4.1): resolution: @@ -6008,7 +6003,7 @@ packages: dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.7.0(@opentelemetry/api@1.4.1) - dev: false + dev: true /@opentelemetry/resources@1.11.0(@opentelemetry/api@1.4.1): resolution: @@ -6022,7 +6017,6 @@ packages: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.11.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': 1.11.0 - dev: false /@opentelemetry/resources@1.12.0(@opentelemetry/api@1.4.1): resolution: @@ -6036,7 +6030,7 @@ packages: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': 1.12.0 - dev: false + dev: true /@opentelemetry/sdk-trace-base@1.11.0(@opentelemetry/api@1.4.1): resolution: @@ -6051,7 +6045,6 @@ packages: '@opentelemetry/core': 1.11.0(@opentelemetry/api@1.4.1) '@opentelemetry/resources': 1.11.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': 1.11.0 - dev: false /@opentelemetry/semantic-conventions@1.11.0: resolution: @@ -6059,7 +6052,6 @@ packages: integrity: sha512-fG4D0AktoHyHwGhFGv+PzKrZjxbKJfckJauTJdq2A+ej5cTazmNYjJVAODXXkYyrsI10muMl+B1iO2q1R6Lp+w==, } engines: { node: '>=14' } - dev: false /@opentelemetry/semantic-conventions@1.12.0: resolution: @@ -6067,7 +6059,6 @@ packages: integrity: sha512-hO+bdeGOlJwqowUBoZF5LyP3ORUFOP1G0GRv8N45W/cztXbT2ZEXaAzfokRS9Xc9FWmYrDj32mF6SzH6wuoIyA==, } engines: { node: '>=14' } - dev: false /@opentelemetry/semantic-conventions@1.7.0: resolution: @@ -6075,7 +6066,7 @@ packages: integrity: sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA==, } engines: { node: '>=14' } - dev: false + dev: true /@panva/asn1.js@1.0.0: resolution: @@ -17301,7 +17292,6 @@ packages: { integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==, } - dev: false /morgan@1.6.1: resolution: @@ -19912,7 +19902,6 @@ packages: resolve: 1.22.1 transitivePeerDependencies: - supports-color - dev: false /require-main-filename@2.0.0: resolution: @@ -20547,7 +20536,6 @@ packages: { integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==, } - dev: false /side-channel@1.0.4: resolution: From c8ac879a44f762760179f6eacfe9bc32a264d917 Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 13:17:51 +0100 Subject: [PATCH 5/7] refactor: unused code --- packages/plugins/opentelemetry/src/index.ts | 13 ------------- .../opentelemetry/test/use-open-telemetry.spec.ts | 1 - 2 files changed, 14 deletions(-) diff --git a/packages/plugins/opentelemetry/src/index.ts b/packages/plugins/opentelemetry/src/index.ts index af5b341414..1b845b3ace 100644 --- a/packages/plugins/opentelemetry/src/index.ts +++ b/packages/plugins/opentelemetry/src/index.ts @@ -2,19 +2,6 @@ import { GraphQLOTELContext, traceDirective } from 'graphql-otel'; import { Plugin } from '@envelop/core'; import * as opentelemetry from '@opentelemetry/api'; -export enum AttributeName { - EXECUTION_ERROR = 'graphql.execute.error', - EXECUTION_RESULT = 'graphql.execute.result', - RESOLVER_EXCEPTION = 'graphql.resolver.exception', - RESOLVER_FIELD_NAME = 'graphql.resolver.fieldName', - RESOLVER_TYPE_NAME = 'graphql.resolver.typeName', - RESOLVER_RESULT_TYPE = 'graphql.resolver.resultType', - RESOLVER_ARGS = 'graphql.resolver.args', - EXECUTION_OPERATION_NAME = 'graphql.execute.operationName', - EXECUTION_OPERATION_DOCUMENT = 'graphql.execute.document', - EXECUTION_VARIABLES = 'graphql.execute.variables', -} - const tracingSpanSymbol = Symbol('OPEN_TELEMETRY_GRAPHQL'); type PluginContext = { diff --git a/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts b/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts index 84800416f7..39f4e65e08 100644 --- a/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts +++ b/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts @@ -69,6 +69,5 @@ describe('useOpenTelemetry', () => { \\"children\\": [] }" `); - console.log(spanTree); }); }); From 4104d652bc2798f4ed029a0e36278e23ffe7a0dd Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 13:22:31 +0100 Subject: [PATCH 6/7] docs: * --- packages/plugins/opentelemetry/README.md | 71 +----------------------- 1 file changed, 1 insertion(+), 70 deletions(-) diff --git a/packages/plugins/opentelemetry/README.md b/packages/plugins/opentelemetry/README.md index 24ba30a9d1..1333ed77b7 100644 --- a/packages/plugins/opentelemetry/README.md +++ b/packages/plugins/opentelemetry/README.md @@ -1,70 +1 @@ -## `@envelop/opentelemetry` - -This plugins integrates [Open Telemetry](https://opentelemetry.io/) tracing with your GraphQL -execution. It also collects GraphQL execution errors and reports it as Exceptions. - -You can use this plugin with any kind of Open Telemetry -[tracer](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#tracer), -and integrate it to any tracing/metric platform that supports this standard. - -## Getting Started - -``` -yarn add @envelop/opentelemetry -``` - -## Usage Example - -By default, this plugin prints the collected telemetry to the console: - -```ts -import { execute, parse, specifiedRules, subscribe, validate } from 'graphql' -import { envelop, useEngine } from '@envelop/core' -import { useOpenTelemetry } from '@envelop/opentelemetry' - -const getEnveloped = envelop({ - plugins: [ - useEngine({ parse, validate, specifiedRules, execute, subscribe }), - // ... other plugins ... - useOpenTelemetry({ - resolvers: true, // Tracks resolvers calls, and tracks resolvers thrown errors - variables: true, // Includes the operation variables values as part of the metadata collected - result: true // Includes execution result object as part of the metadata collected - }) - ] -}) -``` - -If you wish to use custom tracer/exporter, create it and pass it. This example integrates Jaeger -tracer: - -```ts -import { execute, parse, specifiedRules, subscribe, validate } from 'graphql' -import { envelop, useEngine } from '@envelop/core' -import { useOpenTelemetry } from '@envelop/opentelemetry' -import { JaegerExporter } from '@opentelemetry/exporter-jaeger' -import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base' - -const exporter = new JaegerExporter({ - serviceName: 'my-service-name' -}) - -const provider = new BasicTracerProvider() -provider.addSpanProcessor(new SimpleSpanProcessor(exporter)) -provider.register() - -const getEnveloped = envelop({ - plugins: [ - useEngine({ parse, validate, specifiedRules, execute, subscribe }), - // ... other plugins ... - useOpenTelemetry( - { - resolvers: true, // Tracks resolvers calls, and tracks resolvers thrown errors - variables: true, // Includes the operation variables values as part of the metadata collected - result: true // Includes execution result object as part of the metadata collected - }, - provider - ) - ] -}) -``` +TODO From 0d8cebe8eac1c0256bb0d19db948d5c6dc6228ce Mon Sep 17 00:00:00 2001 From: danstarns Date: Tue, 9 May 2023 21:48:32 +0100 Subject: [PATCH 7/7] feat: nested traces --- packages/plugins/opentelemetry/package.json | 1 + .../test/use-open-telemetry.spec.ts | 60 +++++++++++++++++-- pnpm-lock.yaml | 3 + 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/packages/plugins/opentelemetry/package.json b/packages/plugins/opentelemetry/package.json index 168a879f55..251b70a773 100644 --- a/packages/plugins/opentelemetry/package.json +++ b/packages/plugins/opentelemetry/package.json @@ -55,6 +55,7 @@ }, "devDependencies": { "@envelop/core": "^3.0.6", + "@graphql-tools/schema": "8.5.1", "@opentelemetry/api": "^1.2.0", "@opentelemetry/context-async-hooks": "^1.7.0", "@opentelemetry/instrumentation": "^0.33.0", diff --git a/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts b/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts index 39f4e65e08..247c4c3696 100644 --- a/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts +++ b/packages/plugins/opentelemetry/test/use-open-telemetry.spec.ts @@ -1,6 +1,6 @@ -import { buildSchema } from 'graphql'; import { traceDirective } from 'graphql-otel'; import { createTestkit } from '@envelop/testing'; +import { makeExecutableSchema } from '@graphql-tools/schema'; import { InMemorySpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { useOpenTelemetry } from '../src/index.js'; import { buildSpanTree, cleanSpanTreeForSnapshot, otelSetup } from './utils'; @@ -14,22 +14,37 @@ describe('useOpenTelemetry', () => { const trace = traceDirective('trace'); - const schema = buildSchema(/* GraphQL */ ` + const typeDefs = /* GraphQL */ ` ${trace.typeDefs} type User { - name: String @trace + name: String posts: [Post] @trace } type Post { title: String + comments: [Comment] @trace + } + + type Comment { + text: String } type Query { users: [User] @trace } - `); + `; + + const resolvers = { + Query: { + users: () => [ + { name: 'foobar', posts: [{ title: 'foobar', comments: [{ text: 'foobar' }] }] }, + ], + }, + }; + + const schema = makeExecutableSchema({ typeDefs, resolvers }); const query = /* GraphQL */ ` query { @@ -37,6 +52,9 @@ describe('useOpenTelemetry', () => { name posts { title + comments { + text + } } } } @@ -56,7 +74,7 @@ describe('useOpenTelemetry', () => { "{ \\"span\\": { \\"attributes\\": { - \\"query\\": \\"{\\\\n users {\\\\n name\\\\n posts {\\\\n title\\\\n }\\\\n }\\\\n}\\" + \\"query\\": \\"{\\\\n users {\\\\n name\\\\n posts {\\\\n title\\\\n comments {\\\\n text\\\\n }\\\\n }\\\\n }\\\\n}\\" }, \\"links\\": [], \\"events\\": [], @@ -66,7 +84,37 @@ describe('useOpenTelemetry', () => { \\"name\\": \\"Query:users\\", \\"kind\\": 0 }, - \\"children\\": [] + \\"children\\": [ + { + \\"span\\": { + \\"attributes\\": {}, + \\"links\\": [], + \\"events\\": [], + \\"status\\": { + \\"code\\": 0 + }, + \\"name\\": \\"User:posts\\", + \\"parentSpanId\\": \\"\\", + \\"kind\\": 0 + }, + \\"children\\": [ + { + \\"span\\": { + \\"attributes\\": {}, + \\"links\\": [], + \\"events\\": [], + \\"status\\": { + \\"code\\": 0 + }, + \\"name\\": \\"Post:comments\\", + \\"parentSpanId\\": \\"\\", + \\"kind\\": 0 + }, + \\"children\\": [] + } + ] + } + ] }" `); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfee395ac9..b3f9e94475 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1149,6 +1149,9 @@ importers: '@envelop/core': specifier: ^3.0.6 version: link:../../core + '@graphql-tools/schema': + specifier: 8.5.1 + version: 8.5.1(graphql@16.6.0) '@opentelemetry/api': specifier: ^1.2.0 version: 1.4.1