UNPKG

@graphql-mesh/plugin-opentelemetry

Version:
1,018 lines (1,006 loc) • 35.5 kB
'use strict'; var api = require('@opentelemetry/api'); var gatewayRuntime = require('@graphql-hive/gateway-runtime'); var utils$1 = require('@graphql-mesh/utils'); var utils = require('@graphql-tools/utils'); var core = require('@opentelemetry/core'); var resources = require('@opentelemetry/resources'); var sdkTraceWeb = require('@opentelemetry/sdk-trace-web'); var promiseHelpers = require('@whatwg-node/promise-helpers'); var transportCommon = require('@graphql-mesh/transport-common'); var semanticConventions = require('@opentelemetry/semantic-conventions'); var graphql = require('graphql'); var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http'); var exporterZipkin = require('@opentelemetry/exporter-zipkin'); var sdkTraceBase = require('@opentelemetry/sdk-trace-base'); const SEMATTRS_GRAPHQL_DOCUMENT = "graphql.document"; const SEMATTRS_GRAPHQL_OPERATION_TYPE = "graphql.operation.type"; const SEMATTRS_GRAPHQL_OPERATION_NAME = "graphql.operation.name"; const SEMATTRS_GRAPHQL_ERROR_COUNT = "graphql.error.count"; const SEMATTRS_GATEWAY_UPSTREAM_SUBGRAPH_NAME = "gateway.upstream.subgraph.name"; class OtelContextStack { #root; #current; constructor(root) { this.#root = { ctx: root }; this.#current = this.#root; } get current() { return this.#current.ctx; } get root() { return this.#root.ctx; } push = (ctx) => { this.#current = { ctx, previous: this.#current }; }; pop = () => { this.#current = this.#current.previous ?? this.#root; }; toString() { let node = this.#current; const names = []; while (node != void 0) { names.push(api.trace.getSpan(node.ctx).name); node = node.previous; } return names.join(" -> "); } } function getContextManager(contextManager) { if (contextManager === false) { return promiseHelpers.fakePromise(void 0); } if (contextManager === true || contextManager == void 0) { const doNotBundleThisModule = "@opentelemetry"; return import(`${doNotBundleThisModule}/context-async-hooks`).then((module) => new module.AsyncLocalStorageContextManager()).catch((err) => { console.dir("module error", err); if (contextManager === true) { throw new Error( "[OTEL] 'node:async_hooks' module is not available: can't initialize context manager. Possible solutions:\n - disable context manager usage by providing `contextManager: false`\n - provide a custom context manager in the `contextManager` optionLearn more about OTEL configuration here: https://the-guild.dev/graphql/hive/docs/gateway/monitoring-tracing#opentelemetry-traces", { cause: err } ); } }); } return promiseHelpers.fakePromise(contextManager); } function withState(pluginFactory) { const states = {}; function getProp(scope, key) { return { get() { if (!states[scope]) states[scope] = /* @__PURE__ */ new WeakMap(); let value = states[scope].get(key); if (!value) states[scope].set(key, value = {}); return value; }, enumerable: true }; } function getState(payload) { let { executionRequest, context, request } = payload; const state = {}; const defineState = (scope, key) => Object.defineProperty(state, scope, getProp(scope, key)); if (executionRequest) { defineState("forSubgraphExecution", executionRequest); if (executionRequest.context?.params) context = executionRequest.context; } if (context) { defineState("forOperation", context); if (context.request) request = context.request; } if (request) { defineState("forRequest", request); } return state; } function addStateGetters(src) { const result = {}; for (const [hookName, hook] of Object.entries(src)) { if (typeof hook !== "function") { result[hookName] = hook; } else { result[hookName] = { [hook.name](payload, ...args) { return hook( { ...payload, get state() { return getState(payload); } }, ...args ); } }[hook.name]; } } return result; } const { instrumentation, ...hooks } = pluginFactory(getState); const pluginWithState = addStateGetters(hooks); pluginWithState.instrumentation = addStateGetters(instrumentation); return pluginWithState; } function getMostSpecificState(state = {}) { const { forOperation, forRequest, forSubgraphExecution } = state; return forSubgraphExecution ?? forOperation ?? forRequest; } const RETRY_SYMBOL = Symbol.for("@hive-gateway/runtime/upstreamRetry"); function isRetryExecutionRequest(executionRequest) { return !!executionRequest?.[RETRY_SYMBOL]; } function getRetryInfo(executionRequest) { return executionRequest[RETRY_SYMBOL]; } function createHttpSpan(input) { const { url, request, tracer } = input; const span = tracer.startSpan( `${request.method || "GET"} ${url.pathname}`, { attributes: { [semanticConventions.SEMATTRS_HTTP_METHOD]: request.method || "GET", [semanticConventions.SEMATTRS_HTTP_URL]: request.url, [semanticConventions.SEMATTRS_HTTP_ROUTE]: url.pathname, [semanticConventions.SEMATTRS_HTTP_SCHEME]: url.protocol, [semanticConventions.SEMATTRS_NET_HOST_NAME]: url.hostname || url.host || request.headers.get("host") || "localhost", [semanticConventions.SEMATTRS_HTTP_HOST]: url.host || request.headers.get("host") || void 0, [semanticConventions.SEMATTRS_HTTP_CLIENT_IP]: request.headers.get("x-forwarded-for")?.split(",")[0], [semanticConventions.SEMATTRS_HTTP_USER_AGENT]: request.headers.get("user-agent") || void 0 }, kind: api.SpanKind.SERVER }, input.ctx ); return { ctx: api.trace.setSpan(input.ctx, span) }; } function setResponseAttributes(ctx, response) { const span = api.trace.getSpan(ctx); if (span) { span.setAttribute(semanticConventions.SEMATTRS_HTTP_STATUS_CODE, response.status); span.setAttribute( "gateway.cache.response_cache", response.status === 304 && response.headers.get("ETag") ? "hit" : "miss" ); span.setStatus({ code: response.ok ? api.SpanStatusCode.OK : api.SpanStatusCode.ERROR, message: response.ok ? void 0 : response.statusText }); } } function createGraphQLSpan(input) { const span = input.tracer.startSpan( `graphql.operation`, { kind: api.SpanKind.INTERNAL }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function setParamsAttributes(input) { const { ctx, params } = input; const span = api.trace.getSpan(ctx); if (!span) { return; } span.setAttribute(SEMATTRS_GRAPHQL_DOCUMENT, params.query ?? "<undefined>"); span.setAttribute( SEMATTRS_GRAPHQL_OPERATION_NAME, params.operationName ?? "Anonymous" ); } function setExecutionAttributesOnOperationSpan(ctx, args) { const span = api.trace.getSpan(ctx); if (span) { const operation = utils.getOperationASTFromDocument( args.document, args.operationName || void 0 ); const operationName = operation.name?.value ?? "Anonymous"; const document = transportCommon.defaultPrintFn(args.document); span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_TYPE, operation.operation); span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, operationName); span.setAttribute(SEMATTRS_GRAPHQL_DOCUMENT, document); span.updateName(`graphql.operation ${operationName}`); } } function createGraphqlContextBuildingSpan(input) { const span = input.tracer.startSpan( "graphql.context", { kind: api.SpanKind.INTERNAL }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function createGraphQLParseSpan(input) { const span = input.tracer.startSpan( "graphql.parse", { kind: api.SpanKind.INTERNAL }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function setGraphQLParseAttributes(input) { const span = api.trace.getSpan(input.ctx); if (!span) { return; } span.setAttribute(SEMATTRS_GRAPHQL_DOCUMENT, input.query ?? "<empty>"); span.setAttribute( SEMATTRS_GRAPHQL_OPERATION_NAME, input.operationName ?? "Anonymous" ); if (input.result instanceof Error) { span.setAttribute(SEMATTRS_GRAPHQL_ERROR_COUNT, 1); } } function createGraphQLValidateSpan(input) { const span = input.tracer.startSpan( "graphql.validate", { attributes: { [SEMATTRS_GRAPHQL_DOCUMENT]: input.query, [SEMATTRS_GRAPHQL_OPERATION_NAME]: input.operationName }, kind: api.SpanKind.INTERNAL }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function setGraphQLValidateAttributes(input) { const { result, ctx } = input; const span = api.trace.getSpan(ctx); if (!span) { return; } if (result instanceof Error) { span.setStatus({ code: api.SpanStatusCode.ERROR, message: result.message }); } else if (Array.isArray(result) && result.length > 0) { span.setAttribute(SEMATTRS_GRAPHQL_ERROR_COUNT, result.length); span.setStatus({ code: api.SpanStatusCode.ERROR, message: result.map((e) => e.message).join(", ") }); for (const error in result) { span.recordException(error); } } } function createGraphQLExecuteSpan(input) { const span = input.tracer.startSpan( "graphql.execute", { kind: api.SpanKind.INTERNAL }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function setGraphQLExecutionAttributes(input) { const { ctx, args } = input; const span = api.trace.getSpan(ctx); if (!span) { return; } const operation = utils.getOperationASTFromDocument( args.document, args.operationName || void 0 ); const operationName = operation.name?.value ?? "Anonymous"; const document = transportCommon.defaultPrintFn(input.args.document); span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_TYPE, operation.operation); span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, operationName); span.setAttribute(SEMATTRS_GRAPHQL_DOCUMENT, document); } function setGraphQLExecutionResultAttributes(input) { const { ctx, result } = input; const span = api.trace.getSpan(ctx); if (!span) { return; } if (!utils.isAsyncIterable(result) && // FIXME: Handle async iterable too result.errors && result.errors.length > 0) { span.setAttribute(SEMATTRS_GRAPHQL_ERROR_COUNT, result.errors.length); span.setStatus({ code: api.SpanStatusCode.ERROR, message: result.errors.map((e) => e.message).join(", ") }); for (const error of result.errors) { span.recordException(error); } } } function startSubgraphExecuteFetchSpan(input) { const span = input.tracer.startSpan( `subgraph.execute (${input.subgraphName})`, { attributes: { [SEMATTRS_GRAPHQL_OPERATION_NAME]: input.executionRequest.operationName, [SEMATTRS_GRAPHQL_DOCUMENT]: transportCommon.defaultPrintFn( input.executionRequest.document ), [SEMATTRS_GRAPHQL_OPERATION_TYPE]: utils.getOperationASTFromDocument( input.executionRequest.document, input.executionRequest.operationName )?.operation, [SEMATTRS_GATEWAY_UPSTREAM_SUBGRAPH_NAME]: input.subgraphName }, kind: api.SpanKind.CLIENT }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function createUpstreamHttpFetchSpan(input) { const span = input.tracer.startSpan( "http.fetch", { attributes: {}, kind: api.SpanKind.CLIENT }, input.ctx ); return api.trace.setSpan(input.ctx, span); } function setUpstreamFetchAttributes(input) { const { ctx, url, options: fetchOptions } = input; const span = api.trace.getSpan(ctx); if (!span) { return; } const urlObj = new URL(input.url); span.setAttribute(semanticConventions.SEMATTRS_HTTP_METHOD, fetchOptions.method ?? "GET"); span.setAttribute(semanticConventions.SEMATTRS_HTTP_URL, url); span.setAttribute(semanticConventions.SEMATTRS_NET_HOST_NAME, urlObj.hostname); span.setAttribute(semanticConventions.SEMATTRS_HTTP_HOST, urlObj.host); span.setAttribute(semanticConventions.SEMATTRS_HTTP_ROUTE, urlObj.pathname); span.setAttribute(semanticConventions.SEMATTRS_HTTP_SCHEME, urlObj.protocol); if (input.executionRequest && isRetryExecutionRequest(input.executionRequest)) { const { attempt } = getRetryInfo(input.executionRequest); if (attempt > 0) { span.setAttribute("http.request.resend_count", attempt); } } } function setUpstreamFetchResponseAttributes(input) { const { ctx, response } = input; const span = api.trace.getSpan(ctx); if (!span) { return; } span.setAttribute(semanticConventions.SEMATTRS_HTTP_STATUS_CODE, response.status); span.setStatus({ code: response.ok ? api.SpanStatusCode.OK : api.SpanStatusCode.ERROR, message: response.ok ? void 0 : response.statusText }); } function recordCacheEvent(event, payload) { api.trace.getActiveSpan()?.addEvent("gateway.cache." + event, { "gateway.cache.key": payload.key, "gateway.cache.ttl": payload.ttl }); } function recordCacheError(action, error, payload) { api.trace.getActiveSpan()?.addEvent("gateway.cache.error", { "gateway.cache.key": payload.key, "gateway.cache.ttl": payload.ttl, "gateway.cache.action": action, [semanticConventions.SEMATTRS_EXCEPTION_TYPE]: "code" in error ? error.code : error.message, [semanticConventions.SEMATTRS_EXCEPTION_MESSAGE]: error.message, [semanticConventions.SEMATTRS_EXCEPTION_STACKTRACE]: error.stack }); } const responseCacheSymbol = Symbol.for("servedFromResponseCache"); function setExecutionResultAttributes(input) { const span = api.trace.getSpan(input.ctx); if (input.result && span) { span.setAttribute( "gateway.cache.response_cache", input.result[responseCacheSymbol] ? "hit" : "miss" ); } } function createSchemaLoadingSpan(inputs) { const span = inputs.tracer.startSpan( "gateway.schema", { attributes: { "gateway.schema.changed": false } }, api.ROOT_CONTEXT ); return api.trace.setSpan(api.ROOT_CONTEXT, span); } function setSchemaAttributes(inputs) { const span = api.trace.getActiveSpan(); if (!span) { return; } span.setAttribute("gateway.schema.changed", true); span.setAttribute("graphql.schema", graphql.printSchema(inputs.schema)); } function registerException(ctx, error) { const span = ctx && api.trace.getSpan(ctx); if (!span) { return; } const message = error?.message?.toString() ?? error?.toString(); span.setStatus({ code: api.SpanStatusCode.ERROR, message }); span.recordException(error); } async function tryContextManagerSetup(useContextManager) { if (await isContextManagerCompatibleWithAsync()) { return true; } const contextManager = await getContextManager(useContextManager); if (!contextManager) { return false; } if (!api.context.setGlobalContextManager(contextManager)) { if (useContextManager) { throw new Error( "[OTEL] A Context Manager is already registered, but is not compatible with async calls. Please use another context manager, such as `AsyncLocalStorageContextManager`." ); } } return true; } function isContextManagerCompatibleWithAsync() { const symbol = Symbol(); const root = api.context.active(); return api.context.with(root.setValue(symbol, true), async () => { return api.context.active().getValue(symbol) || false; }); } const getEnvVar = "process" in globalThis ? (name, defaultValue) => process.env[name] || defaultValue : (_name, defaultValue) => defaultValue; const initializationTime = "performance" in globalThis ? performance.now() : void 0; const HeadersTextMapGetter = { keys(carrier) { return [...carrier.keys()]; }, get(carrier, key) { return carrier.get(key) || void 0; } }; function useOpenTelemetry(options) { const inheritContext = options.inheritContext ?? true; const propagateContext = options.propagateContext ?? true; let useContextManager; let tracer; let spanProcessors; let provider; const yogaVersion = utils.createDeferred(); let initSpan; function isParentEnabled(state) { const parentState = getMostSpecificState(state); return !parentState || !!parentState.otel; } function getContext(state) { const specificState = getMostSpecificState(state)?.otel; if (initSpan && !specificState) { return initSpan; } if (useContextManager) { return api.context.active(); } return specificState?.current ?? api.ROOT_CONTEXT; } const yogaLogger = utils.createDeferred(); let pluginLogger = options.logger ? utils.fakePromise( options.logger.child({ plugin: "OpenTelemetry" }) ) : yogaLogger.promise; function init() { if ("initializeNodeSDK" in options && options.initializeNodeSDK === false) { if (options.contextManager === false) { return utils.fakePromise(false); } if (options.contextManager === true || options.contextManager == void 0) { return tryContextManagerSetup(options.contextManager); } if (api.context.setGlobalContextManager(options.contextManager)) { return utils.fakePromise(true); } else { throw new Error( "[OTEL] The provided context manager failed to register, a context manger is already registered." ); } } const exporters$ = utils.fakePromise( containsOnlyValues(options.exporters) ? options.exporters : Promise.all(options.exporters) ); const resource = resources.detectResources().merge( resources.resourceFromAttributes({ [semanticConventions.ATTR_SERVICE_NAME]: options.serviceName ?? getEnvVar("OTEL_SERVICE_NAME", "Gateway"), [semanticConventions.ATTR_SERVICE_VERSION]: options.serviceVersion ?? getEnvVar("OTEL_SERVICE_VERSION", yogaVersion.promise) }) ); let contextManager$ = getContextManager(options.contextManager); const sampler = options.samplingRate ? new sdkTraceWeb.ParentBasedSampler({ root: new sdkTraceWeb.TraceIdRatioBasedSampler(options.samplingRate) }) : new sdkTraceWeb.AlwaysOnSampler(); core.setGlobalErrorHandler((err) => { api.diag.error("Uncaught Error", err); }); return exporters$.then((exporters) => { spanProcessors = exporters; provider = new sdkTraceWeb.WebTracerProvider({ resource, spanProcessors, sampler }); return contextManager$; }).then((contextManager) => { provider.register({ contextManager }); return !!contextManager; }); } let preparation$; preparation$ = init().then((contextManager) => { useContextManager = contextManager; tracer = options.tracer || api.trace.getTracer("gateway"); initSpan = api.trace.setSpan( api.context.active(), tracer.startSpan("gateway.initialization", { startTime: initializationTime }) ); preparation$ = utils.fakePromise(); return pluginLogger.then((logger) => { pluginLogger = utils.fakePromise(logger); logger.debug( `context manager is ${useContextManager ? "enabled" : "disabled"}` ); if (!useContextManager) { if (options.spans?.schema) { logger.warn( "Schema loading spans are disabled because no context manager is available" ); } options.spans = options.spans ?? {}; options.spans.schema = false; } api.diag.setLogger( { error: (message, ...args) => logger.error("[otel-diag] " + message, ...args), warn: (message, ...args) => logger.warn("[otel-diag] " + message, ...args), info: (message, ...args) => logger.info("[otel-diag] " + message, ...args), debug: (message, ...args) => logger.debug("[otel-diag] " + message, ...args), verbose: (message, ...args) => logger.debug("[otel-diag] " + message, ...args) }, options.diagLevel ?? api.DiagLogLevel.VERBOSE ); }); }); return withState((getState) => ({ getTracer: () => tracer, getOtelContext: ({ state }) => getContext(state), instrumentation: { request({ state: { forRequest }, request }, wrapped) { if (!shouldTrace(options.spans?.http, { request })) { return wrapped(); } const url = getURL(request); return promiseHelpers.unfakePromise( preparation$.then(() => { const ctx = inheritContext ? api.propagation.extract( api.context.active(), request.headers, HeadersTextMapGetter ) : api.context.active(); forRequest.otel = new OtelContextStack( createHttpSpan({ ctx, request, tracer, url }).ctx ); if (useContextManager) { wrapped = api.context.bind(forRequest.otel.current, wrapped); } return wrapped(); }).catch((error) => { registerException(forRequest.otel?.current, error); throw error; }).finally(() => { const ctx = forRequest.otel?.root; ctx && api.trace.getSpan(ctx)?.end(); }) ); }, operation({ context: gqlCtx, state: { forOperation, ...parentState } }, wrapped) { if (!isParentEnabled(parentState) || !shouldTrace(options.spans?.graphql, gqlCtx)) { return wrapped(); } return promiseHelpers.unfakePromise( preparation$.then(() => { const ctx = getContext(parentState); forOperation.otel = new OtelContextStack( createGraphQLSpan({ tracer, ctx }) ); if (useContextManager) { wrapped = api.context.bind(forOperation.otel.current, wrapped); } return utils.fakePromise().then(wrapped).catch((err) => { registerException(forOperation.otel?.current, err); throw err; }).finally(() => api.trace.getSpan(forOperation.otel.current)?.end()); }) ); }, context({ state, context: gqlCtx }, wrapped) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphqlContextBuilding, gqlCtx)) { return wrapped(); } const { forOperation } = state; const ctx = getContext(state); forOperation.otel.push( createGraphqlContextBuildingSpan({ ctx, tracer }) ); if (useContextManager) { wrapped = api.context.bind(forOperation.otel.current, wrapped); } try { wrapped(); } catch (err) { registerException(forOperation.otel?.current, err); throw err; } finally { api.trace.getSpan(forOperation.otel.current)?.end(); forOperation.otel.pop(); } }, parse({ state, context: gqlCtx }, wrapped) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphqlParse, gqlCtx)) { return wrapped(); } const ctx = getContext(state); const { forOperation } = state; forOperation.otel.push(createGraphQLParseSpan({ ctx, tracer })); if (useContextManager) { wrapped = api.context.bind(forOperation.otel.current, wrapped); } try { wrapped(); } catch (err) { registerException(forOperation.otel.current, err); throw err; } finally { api.trace.getSpan(forOperation.otel.current)?.end(); forOperation.otel.pop(); } }, validate({ state, context: gqlCtx }, wrapped) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphqlValidate, gqlCtx)) { return wrapped(); } const { forOperation } = state; forOperation.otel.push( createGraphQLValidateSpan({ ctx: getContext(state), tracer, query: gqlCtx.params.query?.trim(), operationName: gqlCtx.params.operationName }) ); if (useContextManager) { wrapped = api.context.bind(forOperation.otel.current, wrapped); } try { wrapped(); } catch (err) { registerException(forOperation.otel?.current, err); throw err; } finally { api.trace.getSpan(forOperation.otel.current)?.end(); forOperation.otel.pop(); } }, execute({ state, context: gqlCtx }, wrapped) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphqlExecute, gqlCtx)) { state.forOperation.skipExecuteSpan = true; return wrapped(); } const ctx = getContext(state); const { forOperation } = state; forOperation.otel?.push(createGraphQLExecuteSpan({ ctx, tracer })); if (useContextManager) { wrapped = api.context.bind(forOperation.otel.current, wrapped); } return promiseHelpers.unfakePromise( utils.fakePromise().then(wrapped).catch((err) => { registerException(forOperation.otel.current, err); throw err; }).finally(() => { api.trace.getSpan(forOperation.otel.current)?.end(); forOperation.otel.pop(); }) ); }, subgraphExecute({ state: { forSubgraphExecution, ...parentState }, executionRequest, subgraphName }, wrapped) { const isIntrospection = !executionRequest.context.params; if (!isParentEnabled(parentState) || parentState.forOperation?.skipExecuteSpan || !shouldTrace( isIntrospection ? options.spans?.schema : options.spans?.subgraphExecute, { subgraphName, executionRequest } )) { return wrapped(); } const parentContext = isIntrospection ? api.context.active() : getContext(parentState); forSubgraphExecution.otel = new OtelContextStack( startSubgraphExecuteFetchSpan({ ctx: parentContext, tracer, executionRequest, subgraphName }) ); if (useContextManager) { wrapped = api.context.bind(forSubgraphExecution.otel.current, wrapped); } return promiseHelpers.unfakePromise( utils.fakePromise().then(wrapped).catch((err) => { registerException(forSubgraphExecution.otel.current, err); throw err; }).finally(() => { api.trace.getSpan(forSubgraphExecution.otel.current)?.end(); forSubgraphExecution.otel.pop(); }) ); }, fetch({ state, executionRequest }, wrapped) { if (gatewayRuntime.isRetryExecutionRequest(executionRequest)) { state = getState(gatewayRuntime.getRetryInfo(executionRequest)); } if (!isParentEnabled(state) || !shouldTrace(options.spans?.upstreamFetch, executionRequest)) { return wrapped(); } return promiseHelpers.unfakePromise( preparation$.then(() => { const { forSubgraphExecution } = state; const ctx = createUpstreamHttpFetchSpan({ ctx: getContext(state), tracer }); forSubgraphExecution?.otel.push(ctx); if (useContextManager) { wrapped = api.context.bind(ctx, wrapped); } return utils.fakePromise().then(wrapped).catch((err) => { registerException(ctx, err); throw err; }).finally(() => { api.trace.getSpan(ctx)?.end(); forSubgraphExecution?.otel.pop(); }); }) ); }, schema(_, wrapped) { if (!shouldTrace(options.spans?.schema, null)) { return wrapped(); } return promiseHelpers.unfakePromise( preparation$.then(() => { const ctx = createSchemaLoadingSpan({ tracer }); return utils.fakePromise().then(() => api.context.with(ctx, wrapped)).catch((err) => { api.trace.getSpan(ctx)?.recordException(err); }).finally(() => { api.trace.getSpan(ctx)?.end(); }); }) ); } }, onYogaInit({ yoga }) { yogaVersion.resolve(yoga.version); yogaLogger.resolve(yoga.logger); }, onEnveloped({ state, extendContext }) { extendContext({ opentelemetry: { tracer, activeContext: () => getContext(state) } }); }, onCacheGet: (payload) => shouldTrace(options.spans?.cache, { key: payload.key, action: "read" }) ? { onCacheMiss: () => recordCacheEvent("miss", payload), onCacheHit: () => recordCacheEvent("hit", payload), onCacheGetError: ({ error }) => recordCacheError("read", error, payload) } : void 0, onCacheSet: (payload) => shouldTrace(options.spans?.cache, { key: payload.key, action: "write" }) ? { onCacheSetDone: () => recordCacheEvent("write", payload), onCacheSetError: ({ error }) => recordCacheError("write", error, payload) } : void 0, onResponse({ response, state }) { try { state.forRequest.otel && setResponseAttributes(state.forRequest.otel.root, response); } catch (error) { pluginLogger.then((l) => l.error("Failed to end http span", { error })); } }, onParams: function onParamsOTEL({ state, context: gqlCtx, params }) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphql, gqlCtx)) { return; } const ctx = getContext(state); setParamsAttributes({ ctx, params }); }, onExecutionResult: function onExeResOTEL({ result, context: gqlCtx, state }) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphql, gqlCtx)) { return; } setExecutionResultAttributes({ ctx: getContext(state), result }); }, onParse({ state, context: gqlCtx }) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphqlParse, gqlCtx)) { return; } return ({ result }) => { setGraphQLParseAttributes({ ctx: getContext(state), operationName: gqlCtx.params.operationName, query: gqlCtx.params.query?.trim(), result }); }; }, onValidate({ state, context: gqlCtx }) { if (!isParentEnabled(state) || !shouldTrace(options.spans?.graphqlValidate, gqlCtx)) { return; } return ({ result }) => { setGraphQLValidateAttributes({ ctx: getContext(state), result }); }; }, onExecute({ state, args }) { if (!isParentEnabled(state)) { return; } setExecutionAttributesOnOperationSpan( state.forOperation.otel.root, args ); if (state.forOperation.skipExecuteSpan) { return; } const ctx = getContext(state); setGraphQLExecutionAttributes({ ctx, args }); return { onExecuteDone({ result }) { setGraphQLExecutionResultAttributes({ ctx, result }); } }; }, onFetch(payload) { const { url, setFetchFn, fetchFn, executionRequest } = payload; let { state } = payload; if (executionRequest && gatewayRuntime.isRetryExecutionRequest(executionRequest)) { state = getState(gatewayRuntime.getRetryInfo(executionRequest)); } if (propagateContext) { setFetchFn((url2, options2, ...args) => { const reqHeaders = utils$1.getHeadersObj(options2?.headers || {}); api.propagation.inject(getContext(state), reqHeaders); return fetchFn(url2, { ...options2, headers: reqHeaders }, ...args); }); } if (!isParentEnabled(state) || !shouldTrace(options.spans?.upstreamFetch, executionRequest)) { return; } const ctx = getContext(state); setUpstreamFetchAttributes({ ctx, url, options: payload.options, executionRequest }); return ({ response }) => { setUpstreamFetchResponseAttributes({ ctx, response }); }; }, onSchemaChange(payload) { setSchemaAttributes(payload); if (initSpan) { api.trace.getSpan(initSpan)?.end(); initSpan = null; } }, async onDispose() { if (options.initializeNodeSDK) { await provider?.forceFlush?.(); await provider?.shutdown?.(); api.diag.disable(); api.trace.disable(); api.context.disable(); api.propagation.disable(); } } })); } function containsOnlyValues(maybePromises) { return !maybePromises.some(utils.isPromise); } function shouldTrace(value, args) { if (value == null) { return true; } if (typeof value === "function") { return value(args); } return value; } function getURL(request) { if ("parsedUrl" in request) { return request.parsedUrl; } return new URL(request.url, "http://localhost"); } function resolveBatchingConfig(exporter, batchingConfig) { const value = batchingConfig ?? true; if (value === true) { return new sdkTraceBase.BatchSpanProcessor(exporter); } else if (value === false) { return new sdkTraceBase.SimpleSpanProcessor(exporter); } else { return new sdkTraceBase.BatchSpanProcessor(exporter, value); } } function createStdoutExporter(batchingConfig) { return resolveBatchingConfig(new sdkTraceBase.ConsoleSpanExporter(), batchingConfig); } function createZipkinExporter(config, batchingConfig) { return resolveBatchingConfig(new exporterZipkin.ZipkinExporter(config), batchingConfig); } function createOtlpHttpExporter(config, batchingConfig) { return resolveBatchingConfig(new exporterTraceOtlpHttp.OTLPTraceExporter(config), batchingConfig); } function loadExporterLazily(exporterName, exporterModuleName, exportNameInModule) { try { return promiseHelpers.handleMaybePromise( () => import(exporterModuleName), (mod) => { const ExportCtor = mod?.default?.[exportNameInModule] || mod?.[exportNameInModule]; if (!ExportCtor) { throw new Error( `${exporterName} exporter is not available in the current environment` ); } return ExportCtor; } ); } catch (err) { throw new Error( `${exporterName} exporter is not available in the current environment` ); } } function createOtlpGrpcExporter(config, batchingConfig) { return promiseHelpers.handleMaybePromise( () => loadExporterLazily( "OTLP gRPC", "@opentelemetry/exporter-trace-otlp-grpc", "OTLPTraceExporter" ), (OTLPTraceExporter) => { return resolveBatchingConfig( new OTLPTraceExporter(config), batchingConfig ); } ); } const OpenTelemetryDiagLogLevel = api.DiagLogLevel; exports.OpenTelemetryDiagLogLevel = OpenTelemetryDiagLogLevel; exports.createOtlpGrpcExporter = createOtlpGrpcExporter; exports.createOtlpHttpExporter = createOtlpHttpExporter; exports.createStdoutExporter = createStdoutExporter; exports.createZipkinExporter = createZipkinExporter; exports.useOpenTelemetry = useOpenTelemetry;