@graphql-hive/plugin-opentelemetry
Version:
1,298 lines (1,274 loc) • 123 kB
JavaScript
import { isRetryExecutionRequest, getRetryInfo, Logger } from '@graphql-hive/gateway-runtime';
import { getHeadersObj } from '@graphql-mesh/utils';
import { isAsyncIterable, getOperationASTFromDocument, fakePromise } from '@graphql-tools/utils';
import { unfakePromise } from '@whatwg-node/promise-helpers';
import { hive } from './api.js';
import { SEMATTRS_HIVE_GRAPHQL_OPERATION_HASH, SEMATTRS_GRAPHQL_OPERATION_TYPE, SEMATTRS_GRAPHQL_OPERATION_NAME, SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES, SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, SEMATTRS_GRAPHQL_DOCUMENT, SEMATTRS_IS_HIVE_SUBGRAPH_EXECUTION, SEMATTRS_HIVE_GATEWAY_UPSTREAM_SUBGRAPH_NAME, SEMATTRS_IS_HIVE_GRAPHQL_OPERATION, SEMATTRS_IS_HIVE_REQUEST, SEMATTRS_HIVE_REQUEST_ID } from './attributes.js';
import { trace, SpanStatusCode, context, ROOT_CONTEXT, SpanKind, DiagLogLevel, diag, propagation } from '@opentelemetry/api';
import { hashOperation } from '@graphql-hive/core';
import { defaultPrintFn } from '@graphql-mesh/transport-common';
import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL, SEMATTRS_NET_HOST_NAME, SEMATTRS_HTTP_HOST, SEMATTRS_HTTP_ROUTE, SEMATTRS_HTTP_SCHEME, SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_EXCEPTION_STACKTRACE, SEMATTRS_EXCEPTION_MESSAGE, SEMATTRS_EXCEPTION_TYPE, SEMATTRS_HTTP_USER_AGENT, SEMATTRS_HTTP_CLIENT_IP } from '@opentelemetry/semantic-conventions';
import { printSchema, TypeInfo } from 'graphql';
import { ExportResultCode } from '@opentelemetry/core';
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 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 = {};
const properties = Object.entries(Object.getOwnPropertyDescriptors(src));
for (const [hookName, descriptor] of properties) {
const hook = descriptor.value;
if (typeof hook !== "function") {
descriptor.get &&= () => src[hookName];
descriptor.set &&= (value) => {
src[hookName] = value;
};
Object.defineProperty(result, hookName, descriptor);
} else {
result[hookName] = {
[hook.name](payload, ...args) {
return hook(
{
...payload,
get state() {
return getState(payload);
}
},
...args
);
}
}[hook.name];
}
}
return result;
}
const plugin = pluginFactory(getState);
const pluginWithState = addStateGetters(plugin);
pluginWithState.instrumentation = addStateGetters(plugin.instrumentation);
return pluginWithState;
}
function getMostSpecificState(state = {}) {
const { forOperation, forRequest, forSubgraphExecution } = state;
return forSubgraphExecution ?? forOperation ?? forRequest;
}
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,
"hive.client.name": request.headers.get("graphql-client-name") || request.headers.get("x-graphql-client-name") || void 0,
"hive.client.version": request.headers.get("graphql-client-version") || request.headers.get("x-graphql-client-version") || void 0,
[SEMATTRS_IS_HIVE_REQUEST]: true
},
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,
attributes: { [SEMATTRS_IS_HIVE_GRAPHQL_OPERATION]: true }
},
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>");
if (params.operationName) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, params.operationName);
}
}
const typeInfos = /* @__PURE__ */ new WeakMap();
const defaultOperationHashingFn = (input) => {
if (!typeInfos.has(input.schema)) {
typeInfos.set(input.schema, new TypeInfo(input.schema));
}
const typeInfo = typeInfos.get(input.schema);
return hashOperation({
documentNode: input.document,
operationName: input.operationName ?? null,
schema: input.schema,
variables: null,
// Unstable feature, not using it for now
typeInfo
});
};
function setDocumentAttributesOnOperationSpan(input) {
const { ctx, document } = input;
const span = trace.getSpan(ctx);
if (span) {
span.setAttribute(SEMATTRS_GRAPHQL_DOCUMENT, defaultPrintFn(document));
const operation = getOperationFromDocument(document, input.operationName);
if (operation) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_TYPE, operation.operation);
const operationName = operation.name?.value;
if (operationName) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, operationName);
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;
}
if (input.query) {
span.setAttribute(SEMATTRS_GRAPHQL_DOCUMENT, input.query);
}
if (input.result instanceof Error) {
span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, 1);
} else {
const document = input.result;
const operation = getOperationFromDocument(document, input.operationName);
if (operation) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_TYPE, operation.operation);
const operationName = operation.name?.value;
if (operationName) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, operationName);
}
}
}
}
function createGraphQLValidateSpan(input) {
const span = input.tracer.startSpan(
"graphql.validate",
{ kind: SpanKind.INTERNAL },
input.ctx
);
return trace.setSpan(input.ctx, span);
}
function setGraphQLValidateAttributes(input) {
const { result, ctx, document } = input;
const span = trace.getSpan(ctx);
if (!span) {
return;
}
const operation = getOperationFromDocument(document, input.operationName);
if (operation) {
const operationName = operation.name?.value;
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_TYPE, operation.operation);
if (operationName) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, operationName);
}
}
const errors = Array.isArray(result) ? result : [];
if (result instanceof Error) {
errors.push(result);
}
if (errors.length > 0) {
span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, result.length);
span.setStatus({
code: SpanStatusCode.ERROR,
message: result.map((e) => e.message).join(", ")
});
const codes = [];
for (const error of result) {
if (error.extensions?.code) {
codes.push(`${error.extensions.code}`);
}
span.recordException(error);
}
span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, codes);
}
}
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,
hashOperationFn = defaultOperationHashingFn,
operationCtx
} = input;
const operationSpan = trace.getSpan(operationCtx);
if (operationSpan) {
const hash = hashOperationFn?.({ ...args });
if (hash) {
operationSpan.setAttribute(SEMATTRS_HIVE_GRAPHQL_OPERATION_HASH, hash);
}
}
const span = trace.getSpan(ctx);
if (!span) {
return;
}
const operation = getOperationFromDocument(
args.document,
args.operationName
);
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_TYPE, operation.operation);
const operationName = operation.name?.value;
if (operationName) {
span.setAttribute(SEMATTRS_GRAPHQL_OPERATION_NAME, operationName);
}
}
function setGraphQLExecutionResultAttributes(input) {
const { ctx, result } = input;
const span = trace.getSpan(ctx);
if (!span) {
return;
}
if (input.subgraphNames?.length) {
span.setAttribute(
SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES,
input.subgraphNames
);
}
if (!isAsyncIterable(result) && // FIXME: Handle async iterable too
result.errors && result.errors.length > 0) {
span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, result.errors.length);
span.setStatus({
code: SpanStatusCode.ERROR,
message: result.errors.map((e) => e.message).join(", ")
});
const codes = [];
for (const error of result.errors) {
span.recordException(error);
if (error.extensions?.["code"]) {
codes.push(`${error.extensions["code"]}`);
}
}
if (codes.length > 0) {
span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, codes);
}
}
}
function createSubgraphExecuteSpan(input) {
const operation = getOperationASTFromDocument(
input.executionRequest.document,
input.executionRequest.operationName
);
const span = input.tracer.startSpan(
`subgraph.execute (${input.subgraphName})`,
{
attributes: {
[SEMATTRS_GRAPHQL_OPERATION_NAME]: operation.name?.value,
[SEMATTRS_GRAPHQL_DOCUMENT]: defaultPrintFn(
input.executionRequest.document
),
[SEMATTRS_GRAPHQL_OPERATION_TYPE]: operation.operation,
[SEMATTRS_HIVE_GATEWAY_UPSTREAM_SUBGRAPH_NAME]: input.subgraphName,
[SEMATTRS_IS_HIVE_SUBGRAPH_EXECUTION]: true
},
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 } },
inputs.ctx
);
const currentContext = context.active();
if (currentContext !== inputs.ctx) {
const currentSpan = trace.getActiveSpan();
currentSpan?.addLink({ context: span.spanContext() });
}
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);
}
const operationByDocument = /* @__PURE__ */ new WeakMap();
const getOperationFromDocument = (document, operationName) => {
let operation = operationByDocument.get(document)?.get(operationName ?? null);
if (operation) {
return operation;
}
try {
operation = getOperationASTFromDocument(
document,
operationName || void 0
);
} catch {
}
let operationNameMap = operationByDocument.get(document);
if (!operationNameMap) {
operationByDocument.set(document, operationNameMap = /* @__PURE__ */ new Map());
}
operationNameMap.set(operationName ?? null, operation);
return operation;
};
function getEnvStr(key, opts = {}) {
const globalThat = opts.globalThis ?? globalThis;
let variable = globalThat.process?.env?.[key] || // @ts-expect-error can exist in wrangler and maybe other runtimes
globalThat.env?.[key] || // @ts-expect-error can exist in deno
globalThat.Deno?.env?.get(key) || // @ts-expect-error could be
globalThat[key];
if (variable != null) {
variable += "";
} else {
variable = void 0;
}
return variable?.trim();
}
function getEnvBool(key, opts = {}) {
return strToBool(getEnvStr(key, opts));
}
function strToBool(str) {
return ["1", "t", "true", "y", "yes", "on", "enabled"].includes(
(str || "").toLowerCase()
);
}
function isContextManagerCompatibleWithAsync() {
const symbol = Symbol();
const root = context.active();
return context.with(root.setValue(symbol, true), () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(context.active().getValue(symbol) || false);
});
});
});
}
const logLevelMap = {
ALL: [DiagLogLevel.ALL, "trace"],
VERBOSE: [DiagLogLevel.VERBOSE, "trace"],
DEBUG: [DiagLogLevel.DEBUG, "debug"],
INFO: [DiagLogLevel.INFO, "info"],
WARN: [DiagLogLevel.WARN, "warn"],
ERROR: [DiagLogLevel.ERROR, "error"],
NONE: [DiagLogLevel.NONE, null]
};
function diagLogLevelFromEnv() {
const value = getEnvStr("OTEL_LOG_LEVEL");
if (value == null) {
return void 0;
}
const resolvedLogLevel = logLevelMap[value.toUpperCase()];
if (resolvedLogLevel == null) {
diag.warn(
`Unknown log level "${value}", expected one of ${Object.keys(logLevelMap)}, using default`
);
return logLevelMap["INFO"];
}
return resolvedLogLevel;
}
const initializationTime = "performance" in globalThis ? performance.now() : void 0;
const ignoredRequests = /* @__PURE__ */ new WeakSet();
const otelCtxForRequestId = /* @__PURE__ */ new Map();
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 traces;
let initSpan;
let pluginLogger = options.log && options.log.child("[OpenTelemetry] ");
pluginLogger?.info("Enabled");
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;
}
let preparation$ = init();
preparation$.then(() => {
preparation$ = fakePromise();
});
async function init() {
if (options.useContextManager !== false && !await isContextManagerCompatibleWithAsync()) {
useContextManager = false;
if (options.useContextManager === true) {
throw new Error(
"[OTEL] Context Manager usage is enabled, but the registered one is not compatible with async calls. Please use another context manager, such as `AsyncLocalStorageContextManager`."
);
}
} else {
useContextManager = options.useContextManager ?? true;
}
pluginLogger?.info("Initializing");
tracer = traces?.tracer ?? trace.getTracer("gateway");
traces = resolveTracesConfig(options, useContextManager, pluginLogger);
initSpan = trace.setSpan(
context.active(),
tracer.startSpan("gateway.initialization", {
startTime: initializationTime
})
);
}
const plugin = withState((getState) => {
hive.setPluginUtils({
get tracer() {
return tracer;
},
getActiveContext: (matcher) => getContext(getState(matcher)),
getHttpContext: (request) => getState({ request }).forRequest.otel?.root,
getOperationContext: (context2) => getState({ context: context2 }).forOperation.otel?.root,
getExecutionRequestContext: (executionRequest) => getState({ executionRequest }).forSubgraphExecution.otel?.root,
ignoreRequest: (request) => ignoredRequests.add(request)
});
return {
get tracer() {
return tracer;
},
instrumentation: {
request({ state: { forRequest }, request }, wrapped) {
return unfakePromise(
preparation$.then(() => {
if (!traces || !shouldTrace(traces.spans?.http, { request, ignoredRequests })) {
return wrapped();
}
const url = getURL(request);
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 (!traces || !isParentEnabled(parentState) || !shouldTrace(traces.spans?.graphql, { context: 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().then(wrapped).catch((err) => {
registerException(forOperation.otel?.current, err);
throw err;
}).finally(
() => trace.getSpan(forOperation.otel.current)?.end()
);
})
);
},
context({ state, context: gqlCtx }, wrapped) {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphqlContextBuilding, {
context: 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 (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphqlParse, { context: 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 (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphqlValidate, { context: gqlCtx })) {
return wrapped();
}
const { forOperation } = state;
forOperation.otel.push(
createGraphQLValidateSpan({ ctx: getContext(state), 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();
}
},
execute({ state, context: gqlCtx }, wrapped) {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphqlExecute, { context: 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().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 (!traces || !isParentEnabled(parentState) || parentState.forOperation?.skipExecuteSpan || !shouldTrace(
isIntrospection ? traces.spans?.schema : traces.spans?.subgraphExecute,
{
subgraphName,
executionRequest
}
)) {
return wrapped();
}
const parentContext = isIntrospection ? context.active() : getContext(parentState);
forSubgraphExecution.otel = new OtelContextStack(
createSubgraphExecuteSpan({
ctx: parentContext,
tracer,
executionRequest,
subgraphName
})
);
if (useContextManager) {
wrapped = context.bind(forSubgraphExecution.otel.current, wrapped);
}
return unfakePromise(
fakePromise().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(executionRequest)) {
state = getState(getRetryInfo(executionRequest));
}
return unfakePromise(
preparation$.then(() => {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.upstreamFetch, { executionRequest })) {
return wrapped();
}
const { forSubgraphExecution } = state;
const ctx = createUpstreamHttpFetchSpan({
ctx: getContext(state),
tracer
});
forSubgraphExecution?.otel.push(ctx);
if (useContextManager) {
wrapped = context.bind(ctx, wrapped);
}
return fakePromise().then(wrapped).catch((err) => {
registerException(ctx, err);
throw err;
}).finally(() => {
trace.getSpan(ctx)?.end();
forSubgraphExecution?.otel.pop();
});
})
);
},
schema(_, wrapped) {
return unfakePromise(
preparation$.then(() => {
if (!traces || !shouldTrace(traces.spans?.schema, null)) {
return wrapped();
}
const ctx = createSchemaLoadingSpan({
ctx: initSpan ?? ROOT_CONTEXT,
tracer
});
return fakePromise().then(() => context.with(ctx, wrapped)).catch((err) => {
trace.getSpan(ctx)?.recordException(err);
}).finally(() => {
trace.getSpan(ctx)?.end();
});
})
);
}
},
onYogaInit({ yoga }) {
pluginLogger ??= new Logger({
writers: [
{
write(level, attrs, msg) {
level = level === "trace" ? "debug" : level;
yoga.logger[level](msg, attrs);
}
}
]
}).child("[OpenTelemetry] ");
pluginLogger.debug(
`Context manager is ${useContextManager ? "enabled" : "disabled"}`
);
},
onRequest({ state, serverContext }) {
if (traces && !useContextManager) {
const requestId = (
// TODO: serverContext.log will not be available in Yoga, this will be fixed when Hive Logger is integrated into Yoga
serverContext.log?.attrs?.[
// @ts-expect-error even if the attrs is an array this will work
"requestId"
]
);
if (typeof requestId === "string") {
const httpCtx = state.forRequest.otel?.root;
const httpSpan = httpCtx && trace.getSpan(httpCtx);
httpSpan?.setAttribute(SEMATTRS_HIVE_REQUEST_ID, requestId);
otelCtxForRequestId.set(requestId, getContext(state));
}
}
},
onEnveloped({ state, extendContext }) {
extendContext({
openTelemetry: {
tracer,
getHttpContext: (request) => {
const { forRequest } = request ? getState({ request }) : state;
return forRequest.otel?.root;
},
getOperationContext: (context2) => {
const { forOperation } = context2 ? getState({ context: context2 }) : state;
return forOperation.otel?.root;
},
getExecutionRequestContext: (executionRequest) => {
return getState({ executionRequest }).forSubgraphExecution.otel?.root;
},
getActiveContext: (contextMatcher) => getContext(contextMatcher ? getState(contextMatcher) : state)
}
});
},
onCacheGet: (payload) => traces && shouldTrace(traces.events?.cache, { key: payload.key, action: "read" }) ? {
onCacheMiss: () => recordCacheEvent("miss", payload),
onCacheHit: () => recordCacheEvent("hit", payload),
onCacheGetError: ({ error }) => recordCacheError("read", error, payload)
} : void 0,
onCacheSet: (payload) => traces && shouldTrace(traces.events?.cache, { key: payload.key, action: "write" }) ? {
onCacheSetDone: () => recordCacheEvent("write", payload),
onCacheSetError: ({ error }) => recordCacheError("write", error, payload)
} : void 0,
onResponse({ response, state, serverContext }) {
traces && state.forRequest.otel && setResponseAttributes(state.forRequest.otel.root, response);
if (!useContextManager) {
const requestId = (
// TODO: serverContext.log will not be available in Yoga, this will be fixed when Hive Logger is integrated into Yoga
serverContext.log?.attrs?.[
// @ts-expect-error even if the attrs is an array this will work
"requestId"
]
);
if (typeof requestId === "string") {
otelCtxForRequestId.delete(requestId);
}
}
},
onParams({ state, context: gqlCtx, params }) {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphql, { context: gqlCtx })) {
return;
}
const ctx = getContext(state);
setParamsAttributes({ ctx, params });
},
onExecutionResult({ result, context: gqlCtx, state }) {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphql, { context: gqlCtx })) {
return;
}
setExecutionResultAttributes({ ctx: getContext(state), result });
},
onParse({ state, context: gqlCtx }) {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphqlParse, { context: gqlCtx })) {
return;
}
return ({ result }) => {
setGraphQLParseAttributes({
ctx: getContext(state),
operationName: gqlCtx.params.operationName,
query: gqlCtx.params.query?.trim(),
result
});
if (!(result instanceof Error)) {
setDocumentAttributesOnOperationSpan({
ctx: state.forOperation.otel.root,
document: result,
operationName: gqlCtx.params.operationName
});
}
};
},
onValidate({ state, context: gqlCtx, params }) {
if (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.graphqlValidate, { context: gqlCtx })) {
return;
}
return ({ result }) => {
setGraphQLValidateAttributes({
ctx: getContext(state),
result,
document: params.documentAST,
operationName: gqlCtx.params.operationName
});
};
},
onExecute({ state, args }) {
if (state.forOperation.skipExecuteSpan) {
return;
}
const ctx = getContext(state);
setGraphQLExecutionAttributes({
ctx,
operationCtx: state.forOperation.otel.root,
args,
hashOperationFn: options.hashOperation
});
state.forOperation.subgraphNames = [];
return {
onExecuteDone({ result }) {
setGraphQLExecutionResultAttributes({
ctx,
result,
subgraphNames: state.forOperation.subgraphNames
});
}
};
},
onSubgraphExecute({ subgraphName, state }) {
state.forOperation?.subgraphNames?.push(subgraphName);
},
onFetch(payload) {
const { url, setFetchFn, fetchFn, executionRequest } = payload;
let { state } = payload;
if (executionRequest && isRetryExecutionRequest(executionRequest)) {
state = getState(getRetryInfo(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 (!traces || !isParentEnabled(state) || !shouldTrace(traces.spans?.upstreamFetch, { executionRequest })) {
return;
}
const ctx = getContext(state);
setUpstreamFetchAttributes({
ctx,
url,
options: payload.options,
executionRequest
});
return ({ response }) => {
setUpstreamFetchResponseAttributes({ ctx, response });
};
},
onSchemaChange(payload) {
if (initSpan) {
trace.getSpan(initSpan)?.end();
initSpan = null;
}
if (!traces || !shouldTrace(traces?.spans?.schema, null)) {
setSchemaAttributes(payload);
}
},
onDispose() {
if (options.flushOnDispose !== false) {
const flushMethod = options.flushOnDispose ?? "forceFlush";
const provider = trace.getTracerProvider();
if (flushMethod in provider && typeof provider[flushMethod] === "function") {
return provider[flushMethod]();
}
}
}
};
});
plugin.getActiveContext = hive.getActiveContext;
plugin.getHttpContext = hive.getHttpContext;
plugin.getOperationContext = hive.getOperationContext;
plugin.getExecutionRequestContext = hive.getExecutionRequestContext;
plugin.ignoreRequest = hive.ignoreRequest;
Object.defineProperty(plugin, "tracer", {
enumerable: true,
get: () => tracer
});
return plugin;
}
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");
}
const defaultHttpFilter = ({ request }) => {
if (ignoredRequests.has(request)) {
return false;
}
return true;
};
function resolveTracesConfig(options, useContextManager, log) {
if (options.traces === false) {
return void 0;
}
let traces = typeof options.traces === "object" ? options.traces : {};
traces.spans ??= {};
if ((traces.spans.http ?? true) === true) {
traces.spans = { ...traces.spans, http: defaultHttpFilter };
}
if (!useContextManager) {
if (traces.spans.schema) {
log?.warn(
"Schema loading spans are disabled because no context manager is available"
);
}
traces.spans.schema = false;
}
return traces;
}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var opossum = {exports: {}};
(function (module, exports) {
(function webpackUniversalModuleDefinition(root, factory) {
module.exports = factory();
})(module.exports, () => {
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((module, exports, __webpack_require__) => {
module.exports = __webpack_require__(/*! ./lib/circuit */ "./lib/circuit.js");
/***/ }),
/***/ "./lib/cache.js":
/*!**********************!*\
!*** ./lib/cache.js ***!
\**********************/
/***/ ((module, exports) => {
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", { writable: false }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return (String )(t); }
/**
* Simple in-memory cache implementation
* @class MemoryCache
* @property {Map} cache Cache map
*/
var MemoryCache = /*#__PURE__*/function () {
function MemoryCache(maxEntries) {
_classCallCheck(this, MemoryCache);
this.cache = new Map();
this.maxEntries = maxEntries !== null && maxEntries !== void 0 ? maxEntries : Math.pow(2, 24) - 1; // Max size for Map is 2^24.
}
/**
* Get cache value by key
* @param {string} key Cache key
* @return {any} Response from cache
*/
return _createClass(MemoryCache, [{
key: "get",
value: function get(key) {
var cached = this.cache.get(key);
if (cached) {
if (cached.expiresAt > Date.now() || cached.expiresAt === 0) {
return cached.value;
}
this.cache["delete"](key);
}
return undefined;
}
/**
* Set cache key with value and ttl
* @param {string} key Cache key
* @param {any} value Value to cache
* @param {number} ttl Time to live in milliseconds
* @return {void}
*/
}, {
key: "set",
value: function set(key, value, ttl) {
// Evict first entry when at capacity - only when it's a new key.
if (this.cache.size === this.maxEntries && this.get(key) === undefined) {
this.cache["delete"](this.cache.keys().next().value);
}
this.cache.set(key, {
expiresAt: ttl,
value: value
});
}
/**
* Delete cache key
* @param {string} key Cache key
* @return {void}
*/
}, {
key: "delete",
value: function _delete(key) {
this.cache["delete"](key);
}
/**
* Clear cache
* @returns {void}
*/
}, {
key: "flush",
value: function flush() {
this.cache.clear();
}
}]);
}();
module.exports = MemoryCache;
/***/ }),
/***/ "./lib/circuit.js":
/*!************************!*\
!*** ./lib/circuit.js ***!
\************************/
/***/ ((module, exports, __webpack_require__) => {
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: false }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return (String )(t); }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: true, configurable: true } }), Object.defineProperty(t, "prototype", { writable: false }), e && _setPrototypeOf(t, e); }
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
var EventEmitter = __webpack_require__(/*! events */ "./node_modules/events/events.js");
var Status = __webpack_require__(/*! ./status */ "./lib/status.js");
var Semaphore = __webpack_require__(/*! ./semaphore */ "./lib/semaphore.js");
var MemoryCache = __webpack_require__(/*! ./cache */ "./lib/cache.js");
var STATE = Symbol('state');
var OPEN = Symbol('open');
var CLOSED = Symbol('closed');
var HALF_OPEN = Symbol('half-open');
var PENDING_CLOSE = Symbol('pending-close');
var SHUTDOWN = Symbol('shutdown');
var FALLBACK_FUNCTION = Symbol('fallback');
var STATUS = Symbol('status');
var NAME = Symbol('name');
var GROUP = Symbol('group');
var ENABLED = Symbol('Enabled');
var WARMING_UP = Symbol('warming-up');
var VOLUME_THRESHOLD = Symbol('volume-threshold');
var OUR_ERROR = Symbol('our-error');
var RESET_TIMEOUT = Symbol('reset-timeout');
var WARMUP_TIMEOUT = Symbol('warmup-timeout');
var LAST_TIMER_AT = Symbol('last-timer-at');
var deprecation = "options.maxFailures is deprecated. Please use options.errorThresholdPercentage";
/**
* Constructs a {@link CircuitBreaker}.
*
* @class CircuitBreaker
* @extends EventEmitter
* @param {Function} action The action to fire for this {@link CircuitBreaker}
* @param {Object} options Options for the {@link CircuitBreaker}
* @param {Status} options.status A {@link Status} object that might
* have pre-prime stats
* @param {Number} options.timeout The time in milliseconds that action should
* be allowed to execute before timing out. Timeout can be disabled by setting
* this to `false`. Default 10000 (10 seconds)
* @param {Number} options.maxFailures (Deprecated) The number of times the
* circuit can fail before opening. Default 10.
* @param {Number} options.resetTimeout The time in milliseconds to wait before
* setting the breaker to `halfOpen` state, and trying the action again.
* Default: 30000 (30 seconds)
* @param {Number} options.rollingCountTimeout Sets the duration of the
* statistical rolling window, in milliseconds. This is how long Opossum keeps
* metrics for the circuit breaker to use and for publishing. Default: 10000
* @param {Number} options.rollingCountBuckets Sets the number of buckets the
* rolling statistical window is divided into. So, if
* options.rollingCountTimeout is 10000, and options.rollingCountBuckets is 10,
* then the statistical window will be 1000/1 second snapshots in the
* statistical window. Default: 10
* @param {String} options.name the circuit name to use when reporting stats.
* Default: the name of the function this circuit controls.
* @param {boolean} options.rollingPercentilesEnabled This property indicates
* whether execution latencies should be tracked and calculated as percentiles.
* If they are disabled, all summary statistics (mean, percentiles) are
* returned as -1. Default: true
* @param {Number} options.capacity the number of concurrent requests allowed.
* If the number currently executing function calls is equal to
* options.capacity, further calls to `fire()` are rejected until at least one
* of the current requests completes. Default: `Number.MAX_SAFE_INTEGER`.
* @param {Number} options.errorThresholdPercentage the error percentage at
* which to open the circuit and start short-circuiting requests to fallback.
* Default: 50
* @param {boolean} options.enabled whether this circuit is enabled upon
* construction. Default: true
* @param {boolean} options.allowWarmUp determines whether to allow failures
* without opening the circuit during a brief warmup period (this is the
* `rollingCountTimeout` property). Default: fal