@graphql-mesh/plugin-opentelemetry
Version:
310 lines (305 loc) • 11.5 kB
JavaScript
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 };