@graphql-mesh/plugin-opentelemetry
Version:
1,011 lines (1,000 loc) • 34.8 kB
JavaScript
import { trace, SpanStatusCode, ROOT_CONTEXT, SpanKind, context, diag, DiagLogLevel, propagation } from '@opentelemetry/api';
import { isRetryExecutionRequest as isRetryExecutionRequest$1, getRetryInfo as getRetryInfo$1 } from '@graphql-hive/gateway-runtime';
import { getHeadersObj } from '@graphql-mesh/utils';
import { getOperationASTFromDocument, isAsyncIterable, createDeferred, fakePromise as fakePromise$1, isPromise } from '@graphql-tools/utils';
import { setGlobalErrorHandler } from '@opentelemetry/core';
import { detectResources, resourceFromAttributes } from '@opentelemetry/resources';
import { ParentBasedSampler, TraceIdRatioBasedSampler, AlwaysOnSampler, WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { fakePromise, unfakePromise, handleMaybePromise } from '@whatwg-node/promise-helpers';
import { defaultPrintFn } from '@graphql-mesh/transport-common';
import { SEMATTRS_EXCEPTION_STACKTRACE, SEMATTRS_EXCEPTION_MESSAGE, SEMATTRS_EXCEPTION_TYPE, SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL, SEMATTRS_NET_HOST_NAME, SEMATTRS_HTTP_HOST, SEMATTRS_HTTP_ROUTE, SEMATTRS_HTTP_SCHEME, SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_HTTP_USER_AGENT, SEMATTRS_HTTP_CLIENT_IP, ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { printSchema } from 'graphql';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { ConsoleSpanExporter, BatchSpanProcessor, SimpleSpanProcessor } from '@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(trace.getSpan(node.ctx).name);
node = node.previous;
}
return names.join(" -> ");
}
}
function getContextManager(contextManager) {
if (contextManager === false) {
return 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 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: {
[SEMATTRS_HTTP_METHOD]: request.method || "GET",
[SEMATTRS_HTTP_URL]: request.url,
[SEMATTRS_HTTP_ROUTE]: url.pathname,
[SEMATTRS_HTTP_SCHEME]: url.protocol,
[SEMATTRS_NET_HOST_NAME]: url.hostname || url.host || request.headers.get("host") || "localhost",
[SEMATTRS_HTTP_HOST]: url.host || request.headers.get("host") || void 0,
[SEMATTRS_HTTP_CLIENT_IP]: request.headers.get("x-forwarded-for")?.split(",")[0],
[SEMATTRS_HTTP_USER_AGENT]: request.headers.get("user-agent") || void 0
},
kind: SpanKind.SERVER
},
input.ctx
);
return {
ctx: trace.setSpan(input.ctx, span)
};
}
function setResponseAttributes(ctx, response) {
const span = trace.getSpan(ctx);
if (span) {
span.setAttribute(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 ? SpanStatusCode.OK : SpanStatusCode.ERROR,
message: response.ok ? void 0 : response.statusText
});
}
}
function createGraphQLSpan(input) {
const span = input.tracer.startSpan(
`graphql.operation`,
{ kind: SpanKind.INTERNAL },
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function setParamsAttributes(input) {
const { ctx, params } = input;
const span = 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 = trace.getSpan(ctx);
if (span) {
const operation = getOperationASTFromDocument(
args.document,
args.operationName || void 0
);
const operationName = operation.name?.value ?? "Anonymous";
const document = 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: SpanKind.INTERNAL },
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function createGraphQLParseSpan(input) {
const span = input.tracer.startSpan(
"graphql.parse",
{
kind: SpanKind.INTERNAL
},
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function setGraphQLParseAttributes(input) {
const span = 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: SpanKind.INTERNAL
},
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function setGraphQLValidateAttributes(input) {
const { result, ctx } = input;
const span = trace.getSpan(ctx);
if (!span) {
return;
}
if (result instanceof Error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: result.message
});
} else if (Array.isArray(result) && result.length > 0) {
span.setAttribute(SEMATTRS_GRAPHQL_ERROR_COUNT, result.length);
span.setStatus({
code: 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: SpanKind.INTERNAL },
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function setGraphQLExecutionAttributes(input) {
const { ctx, args } = input;
const span = trace.getSpan(ctx);
if (!span) {
return;
}
const operation = getOperationASTFromDocument(
args.document,
args.operationName || void 0
);
const operationName = operation.name?.value ?? "Anonymous";
const document = 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 = trace.getSpan(ctx);
if (!span) {
return;
}
if (!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: 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]: defaultPrintFn(
input.executionRequest.document
),
[SEMATTRS_GRAPHQL_OPERATION_TYPE]: getOperationASTFromDocument(
input.executionRequest.document,
input.executionRequest.operationName
)?.operation,
[SEMATTRS_GATEWAY_UPSTREAM_SUBGRAPH_NAME]: input.subgraphName
},
kind: SpanKind.CLIENT
},
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function createUpstreamHttpFetchSpan(input) {
const span = input.tracer.startSpan(
"http.fetch",
{
attributes: {},
kind: SpanKind.CLIENT
},
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function setUpstreamFetchAttributes(input) {
const { ctx, url, options: fetchOptions } = input;
const span = trace.getSpan(ctx);
if (!span) {
return;
}
const urlObj = new URL(input.url);
span.setAttribute(SEMATTRS_HTTP_METHOD, fetchOptions.method ?? "GET");
span.setAttribute(SEMATTRS_HTTP_URL, url);
span.setAttribute(SEMATTRS_NET_HOST_NAME, urlObj.hostname);
span.setAttribute(SEMATTRS_HTTP_HOST, urlObj.host);
span.setAttribute(SEMATTRS_HTTP_ROUTE, urlObj.pathname);
span.setAttribute(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 = trace.getSpan(ctx);
if (!span) {
return;
}
span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, response.status);
span.setStatus({
code: response.ok ? SpanStatusCode.OK : SpanStatusCode.ERROR,
message: response.ok ? void 0 : response.statusText
});
}
function recordCacheEvent(event, payload) {
trace.getActiveSpan()?.addEvent("gateway.cache." + event, {
"gateway.cache.key": payload.key,
"gateway.cache.ttl": payload.ttl
});
}
function recordCacheError(action, error, payload) {
trace.getActiveSpan()?.addEvent("gateway.cache.error", {
"gateway.cache.key": payload.key,
"gateway.cache.ttl": payload.ttl,
"gateway.cache.action": action,
[SEMATTRS_EXCEPTION_TYPE]: "code" in error ? error.code : error.message,
[SEMATTRS_EXCEPTION_MESSAGE]: error.message,
[SEMATTRS_EXCEPTION_STACKTRACE]: error.stack
});
}
const responseCacheSymbol = Symbol.for("servedFromResponseCache");
function setExecutionResultAttributes(input) {
const span = 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 } },
ROOT_CONTEXT
);
return trace.setSpan(ROOT_CONTEXT, span);
}
function setSchemaAttributes(inputs) {
const span = trace.getActiveSpan();
if (!span) {
return;
}
span.setAttribute("gateway.schema.changed", true);
span.setAttribute("graphql.schema", printSchema(inputs.schema));
}
function registerException(ctx, error) {
const span = ctx && trace.getSpan(ctx);
if (!span) {
return;
}
const message = error?.message?.toString() ?? error?.toString();
span.setStatus({ code: 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 (!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 = context.active();
return context.with(root.setValue(symbol, true), async () => {
return 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 = 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 context.active();
}
return specificState?.current ?? ROOT_CONTEXT;
}
const yogaLogger = createDeferred();
let pluginLogger = options.logger ? fakePromise$1(
options.logger.child({
plugin: "OpenTelemetry"
})
) : yogaLogger.promise;
function init() {
if ("initializeNodeSDK" in options && options.initializeNodeSDK === false) {
if (options.contextManager === false) {
return fakePromise$1(false);
}
if (options.contextManager === true || options.contextManager == void 0) {
return tryContextManagerSetup(options.contextManager);
}
if (context.setGlobalContextManager(options.contextManager)) {
return fakePromise$1(true);
} else {
throw new Error(
"[OTEL] The provided context manager failed to register, a context manger is already registered."
);
}
}
const exporters$ = fakePromise$1(
containsOnlyValues(options.exporters) ? options.exporters : Promise.all(options.exporters)
);
const resource = detectResources().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: options.serviceName ?? getEnvVar("OTEL_SERVICE_NAME", "Gateway"),
[ATTR_SERVICE_VERSION]: options.serviceVersion ?? getEnvVar("OTEL_SERVICE_VERSION", yogaVersion.promise)
})
);
let contextManager$ = getContextManager(options.contextManager);
const sampler = options.samplingRate ? new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(options.samplingRate)
}) : new AlwaysOnSampler();
setGlobalErrorHandler((err) => {
diag.error("Uncaught Error", err);
});
return exporters$.then((exporters) => {
spanProcessors = exporters;
provider = new WebTracerProvider({ resource, spanProcessors, sampler });
return contextManager$;
}).then((contextManager) => {
provider.register({ contextManager });
return !!contextManager;
});
}
let preparation$;
preparation$ = init().then((contextManager) => {
useContextManager = contextManager;
tracer = options.tracer || trace.getTracer("gateway");
initSpan = trace.setSpan(
context.active(),
tracer.startSpan("gateway.initialization", {
startTime: initializationTime
})
);
preparation$ = fakePromise$1();
return pluginLogger.then((logger) => {
pluginLogger = fakePromise$1(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;
}
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 ?? 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 unfakePromise(
preparation$.then(() => {
const ctx = inheritContext ? propagation.extract(
context.active(),
request.headers,
HeadersTextMapGetter
) : context.active();
forRequest.otel = new OtelContextStack(
createHttpSpan({ ctx, request, tracer, url }).ctx
);
if (useContextManager) {
wrapped = context.bind(forRequest.otel.current, wrapped);
}
return wrapped();
}).catch((error) => {
registerException(forRequest.otel?.current, error);
throw error;
}).finally(() => {
const ctx = forRequest.otel?.root;
ctx && trace.getSpan(ctx)?.end();
})
);
},
operation({ context: gqlCtx, state: { forOperation, ...parentState } }, wrapped) {
if (!isParentEnabled(parentState) || !shouldTrace(options.spans?.graphql, gqlCtx)) {
return wrapped();
}
return unfakePromise(
preparation$.then(() => {
const ctx = getContext(parentState);
forOperation.otel = new OtelContextStack(
createGraphQLSpan({ tracer, ctx })
);
if (useContextManager) {
wrapped = context.bind(forOperation.otel.current, wrapped);
}
return fakePromise$1().then(wrapped).catch((err) => {
registerException(forOperation.otel?.current, err);
throw err;
}).finally(() => 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 = context.bind(forOperation.otel.current, wrapped);
}
try {
wrapped();
} catch (err) {
registerException(forOperation.otel?.current, err);
throw err;
} finally {
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 = context.bind(forOperation.otel.current, wrapped);
}
try {
wrapped();
} catch (err) {
registerException(forOperation.otel.current, err);
throw err;
} finally {
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 = context.bind(forOperation.otel.current, wrapped);
}
try {
wrapped();
} catch (err) {
registerException(forOperation.otel?.current, err);
throw err;
} finally {
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 = context.bind(forOperation.otel.current, wrapped);
}
return unfakePromise(
fakePromise$1().then(wrapped).catch((err) => {
registerException(forOperation.otel.current, err);
throw err;
}).finally(() => {
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 ? context.active() : getContext(parentState);
forSubgraphExecution.otel = new OtelContextStack(
startSubgraphExecuteFetchSpan({
ctx: parentContext,
tracer,
executionRequest,
subgraphName
})
);
if (useContextManager) {
wrapped = context.bind(forSubgraphExecution.otel.current, wrapped);
}
return unfakePromise(
fakePromise$1().then(wrapped).catch((err) => {
registerException(forSubgraphExecution.otel.current, err);
throw err;
}).finally(() => {
trace.getSpan(forSubgraphExecution.otel.current)?.end();
forSubgraphExecution.otel.pop();
})
);
},
fetch({ state, executionRequest }, wrapped) {
if (isRetryExecutionRequest$1(executionRequest)) {
state = getState(getRetryInfo$1(executionRequest));
}
if (!isParentEnabled(state) || !shouldTrace(options.spans?.upstreamFetch, executionRequest)) {
return wrapped();
}
return unfakePromise(
preparation$.then(() => {
const { forSubgraphExecution } = state;
const ctx = createUpstreamHttpFetchSpan({
ctx: getContext(state),
tracer
});
forSubgraphExecution?.otel.push(ctx);
if (useContextManager) {
wrapped = context.bind(ctx, wrapped);
}
return fakePromise$1().then(wrapped).catch((err) => {
registerException(ctx, err);
throw err;
}).finally(() => {
trace.getSpan(ctx)?.end();
forSubgraphExecution?.otel.pop();
});
})
);
},
schema(_, wrapped) {
if (!shouldTrace(options.spans?.schema, null)) {
return wrapped();
}
return unfakePromise(
preparation$.then(() => {
const ctx = createSchemaLoadingSpan({ tracer });
return fakePromise$1().then(() => context.with(ctx, wrapped)).catch((err) => {
trace.getSpan(ctx)?.recordException(err);
}).finally(() => {
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 && isRetryExecutionRequest$1(executionRequest)) {
state = getState(getRetryInfo$1(executionRequest));
}
if (propagateContext) {
setFetchFn((url2, options2, ...args) => {
const reqHeaders = getHeadersObj(options2?.headers || {});
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) {
trace.getSpan(initSpan)?.end();
initSpan = null;
}
},
async onDispose() {
if (options.initializeNodeSDK) {
await provider?.forceFlush?.();
await provider?.shutdown?.();
diag.disable();
trace.disable();
context.disable();
propagation.disable();
}
}
}));
}
function containsOnlyValues(maybePromises) {
return !maybePromises.some(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 BatchSpanProcessor(exporter);
} else if (value === false) {
return new SimpleSpanProcessor(exporter);
} else {
return new BatchSpanProcessor(exporter, value);
}
}
function createStdoutExporter(batchingConfig) {
return resolveBatchingConfig(new ConsoleSpanExporter(), batchingConfig);
}
function createZipkinExporter(config, batchingConfig) {
return resolveBatchingConfig(new ZipkinExporter(config), batchingConfig);
}
function createOtlpHttpExporter(config, batchingConfig) {
return resolveBatchingConfig(new OTLPTraceExporter(config), batchingConfig);
}
function loadExporterLazily(exporterName, exporterModuleName, exportNameInModule) {
try {
return 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 handleMaybePromise(
() => loadExporterLazily(
"OTLP gRPC",
"@opentelemetry/exporter-trace-otlp-grpc",
"OTLPTraceExporter"
),
(OTLPTraceExporter) => {
return resolveBatchingConfig(
new OTLPTraceExporter(config),
batchingConfig
);
}
);
}
const OpenTelemetryDiagLogLevel = DiagLogLevel;
export { OpenTelemetryDiagLogLevel, createOtlpGrpcExporter, createOtlpHttpExporter, createStdoutExporter, createZipkinExporter, useOpenTelemetry };