UNPKG

@graphql-mesh/plugin-opentelemetry

Version:
310 lines (305 loc) • 11.5 kB
import { context, ROOT_CONTEXT, trace, propagation } from '@opentelemetry/api'; import { hrTimeDuration, W3CBaggagePropagator, W3CTraceContextPropagator, CompositePropagator } from '@opentelemetry/core'; import { resourceFromAttributes } from '@opentelemetry/resources'; import { BatchSpanProcessor, SimpleSpanProcessor, ConsoleSpanExporter, BasicTracerProvider, ParentBasedSampler, AlwaysOnSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base'; import { SEMATTRS_HTTP_METHOD, ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; export { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, SEMATTRS_HTTP_CLIENT_IP, SEMATTRS_HTTP_HOST, SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_ROUTE, SEMATTRS_HTTP_SCHEME, SEMATTRS_HTTP_SERVER_NAME, SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_HTTP_URL, SEMATTRS_HTTP_USER_AGENT, SEMATTRS_NET_HOST_NAME } from '@opentelemetry/semantic-conventions'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { e as SEMATTRS_GRAPHQL_ERROR_CODES, d as SEMATTRS_GRAPHQL_ERROR_COUNT, g as SEMATTRS_GATEWAY_OPERATION_SUBGRAPH_NAMES, o as otelCtxForRequestId, h as getEnvVar } from './plugin-4GHsp7Q6.js'; export { f as SEMATTRS_GATEWAY_UPSTREAM_SUBGRAPH_NAME, S as SEMATTRS_GRAPHQL_DOCUMENT, c as SEMATTRS_GRAPHQL_OPERATION_HASH, b as SEMATTRS_GRAPHQL_OPERATION_NAME, a as SEMATTRS_GRAPHQL_OPERATION_TYPE } from './plugin-4GHsp7Q6.js'; import { SeverityNumber, logs } from '@opentelemetry/api-logs'; import { BatchLogRecordProcessor, SimpleLogRecordProcessor, ConsoleLogRecordExporter, LoggerProvider } from '@opentelemetry/sdk-logs'; import '@graphql-hive/gateway-runtime'; import '@graphql-hive/logger/request'; import '@graphql-mesh/utils'; import '@graphql-tools/utils'; import '@whatwg-node/promise-helpers'; import '@graphql-hive/core'; import '@graphql-mesh/transport-common'; import 'graphql'; class HiveTracingSpanProcessor { traceStateById = /* @__PURE__ */ new Map(); processor; constructor(config) { if (config.processor) { this.processor = config.processor; } else { this.processor = new BatchSpanProcessor( new OTLPTraceExporter({ url: config.endpoint, headers: { Authorization: `Bearer ${config.accessToken}`, "X-Hive-Target-Ref": config.target } }), config.batching ); } } onStart(span, parentContext) { this.processor.onStart(span, parentContext); const { spanId, traceId } = span.spanContext(); const parentId = span.parentSpanContext?.spanId; if (isHttpSpan(span)) { this.traceStateById.set(traceId, { traceId, rootId: spanId, httpSpan: span, operationRoots: /* @__PURE__ */ new Map(), subgraphExecutions: /* @__PURE__ */ new Map() }); return; } const traceState = this.traceStateById.get(traceId); if (!traceState || !parentId) { return; } if (span.name.startsWith("graphql.operation")) { traceState?.operationRoots.set(spanId, span); return; } const operationRoot = traceState.operationRoots.get(parentId); if (operationRoot) { traceState.operationRoots.set(spanId, operationRoot); } if (span.name.startsWith("subgraph.execute")) { traceState.subgraphExecutions.set(spanId, span); return; } const subgraphExecution = traceState.subgraphExecutions.get(parentId); if (subgraphExecution) { traceState.subgraphExecutions.set(spanId, subgraphExecution); } } onEnd(span) { const { traceId, spanId } = span.spanContext(); const traceState = this.traceStateById.get(traceId); if (!traceState) { return; } if (traceState.rootId === spanId) { this.traceStateById.delete(traceId); for (let operationSpan2 of new Set(traceState.operationRoots.values())) { operationSpan2.startTime = span.startTime; operationSpan2.endTime = span.endTime; operationSpan2._duration = hrTimeDuration( operationSpan2.startTime, operationSpan2.endTime ); operationSpan2.parentSpanContext = null; for (const attr in span.attributes) { operationSpan2.attributes[attr] ??= span.attributes[attr]; } this.processor.onEnd(operationSpan2); } return; } const operationSpan = traceState.operationRoots.get(spanId); if (!operationSpan) { return; } if (operationSpan === span) { return; } if (span.name === "graphql.execute") { copyAttribute(span, operationSpan, SEMATTRS_GRAPHQL_ERROR_CODES); copyAttribute(span, operationSpan, SEMATTRS_GRAPHQL_ERROR_COUNT); copyAttribute( span, operationSpan, SEMATTRS_GATEWAY_OPERATION_SUBGRAPH_NAMES ); } const subgraphExecution = traceState.subgraphExecutions.get(spanId); if (span.name === "http.fetch" && subgraphExecution) { for (const attr in span.attributes) { subgraphExecution.attributes[attr] ??= span.attributes[attr]; } } this.processor.onEnd(span); } async forceFlush() { return this.processor.forceFlush(); } async shutdown() { await this.forceFlush(); this.traceStateById.clear(); return this.processor.shutdown(); } } function isHttpSpan(span) { return !!span.attributes[SEMATTRS_HTTP_METHOD]; } function copyAttribute(source, target, sourceAttrName, targetAttrName = sourceAttrName) { target.attributes[targetAttrName] = source.attributes[sourceAttrName]; } class OpenTelemetryLogWriter { logger; useContextManager; constructor(options) { this.useContextManager = options.useContextManager ?? true; if ("logger" in options) { this.logger = options.logger; return; } if ("provider" in options) { if ("register" in options.provider && typeof options.provider.register === "function") { options.provider.register(); } else { logs.setGlobalLoggerProvider(options.provider); } } else { const processors = options.processors ?? []; if (options.exporter) { if (options.batching !== false) { processors.push( new BatchLogRecordProcessor( options.exporter, options.batching === true ? {} : options.batching ) ); } processors.push(new SimpleLogRecordProcessor(options.exporter)); } if (options.console) { processors.push( new SimpleLogRecordProcessor(new ConsoleLogRecordExporter()) ); } logs.setGlobalLoggerProvider( new LoggerProvider({ ...options, processors }) ); } this.logger = logs.getLogger("gateway"); } flush() { const provider = logs.getLoggerProvider(); if ("forceFlush" in provider && typeof provider.forceFlush === "function") { provider.forceFlush(); } } write(level, attrs, msg) { const attributes = Array.isArray(attrs) ? { ...attrs } : attrs ?? void 0; return this.logger.emit({ body: msg, attributes, severityNumber: HIVE_LOG_LEVEL_NUMBERS[level], severityText: level, context: this.useContextManager ? context.active() : getContextForRequest(attributes) }); } } const HIVE_LOG_LEVEL_NUMBERS = { trace: SeverityNumber.TRACE, debug: SeverityNumber.DEBUG, info: SeverityNumber.INFO, warn: SeverityNumber.WARN, error: SeverityNumber.ERROR }; function getContextForRequest(attributes) { if (!attributes?.requestId) { return ROOT_CONTEXT; } return otelCtxForRequestId.get(attributes.requestId) ?? ROOT_CONTEXT; } function openTelemetrySetup(options) { if (getEnvVar("OTEL_SDK_DISABLED", "false") === "true") { return; } if (options.traces) { if (options.traces.tracerProvider) { if ("register" in options.traces.tracerProvider && typeof options.traces.tracerProvider.register === "function") { options.traces.tracerProvider.register(); } else { trace.setGlobalTracerProvider(options.traces.tracerProvider); } } else { let spanProcessors = options.traces.processors ?? []; if (options.traces.exporter) { spanProcessors.push( resolveBatchingConfig( options.traces.exporter, options.traces.batching ) ); } if (options.traces.console) { spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter())); } const baseResource = resourceFromAttributes({ [ATTR_SERVICE_NAME]: options.resource && "serviceName" in options.resource ? options.resource?.serviceName : getEnvVar( "OTEL_SERVICE_NAME", "@graphql-mesh/plugin-opentelemetry" ), [ATTR_SERVICE_VERSION]: options.resource && "serviceVersion" in options.resource ? options.resource?.serviceVersion : getEnvVar( "OTEL_SERVICE_VERSION", globalThis.__OTEL_PLUGIN_VERSION__ ) }); trace.setGlobalTracerProvider( new BasicTracerProvider({ resource: options.resource && !("serviceName" in options.resource) ? baseResource.merge(options.resource) : baseResource, sampler: options.sampler ?? (options.samplingRate ? new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(options.samplingRate) }) : new AlwaysOnSampler()), spanProcessors, generalLimits: options.generalLimits, spanLimits: options.traces.spanLimits }) ); } } if (options.contextManager !== null) { context.setGlobalContextManager(options.contextManager); } if (!options.propagators || options.propagators.length !== 0) { const propagators = options.propagators ?? [ new W3CBaggagePropagator(), new W3CTraceContextPropagator() ]; propagation.setGlobalPropagator( propagators.length === 1 ? propagators[0] : new CompositePropagator({ propagators }) ); } } function hiveTracingSetup(config) { config.target ??= getEnvVar("HIVE_TARGET", void 0); if (!config.target) { throw new Error( "You must specify the Hive Registry `target`. Either provide `target` option or `HIVE_TARGET` environment variable." ); } if (!config.processor) { config.accessToken ??= getEnvVar("HIVE_TRACING_ACCESS_TOKEN", void 0) ?? getEnvVar("HIVE_ACCESS_TOKEN", void 0); if (!config.accessToken) { throw new Error( "You must specify the Hive Registry `accessToken`. Either provide `accessToken` option or `HIVE_ACCESS_TOKEN`/`HIVE_TRACE_ACCESS_TOKEN` environment variable." ); } } openTelemetrySetup({ contextManager: config.contextManager, resource: resourceFromAttributes({ "hive.target_id": config.target }), traces: { processors: [ new HiveTracingSpanProcessor(config) ] } }); } 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); } } export { HIVE_LOG_LEVEL_NUMBERS, HiveTracingSpanProcessor, OpenTelemetryLogWriter, SEMATTRS_GATEWAY_OPERATION_SUBGRAPH_NAMES, SEMATTRS_GRAPHQL_ERROR_CODES, SEMATTRS_GRAPHQL_ERROR_COUNT, getContextForRequest, getEnvVar, hiveTracingSetup, openTelemetrySetup };