@graphql-hive/plugin-opentelemetry
Version:
476 lines (470 loc) • 17.4 kB
JavaScript
;
var logger = require('@graphql-hive/logger');
var api = require('@opentelemetry/api');
var core = require('@opentelemetry/core');
var resources = require('@opentelemetry/resources');
var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
var semanticConventions = require('@opentelemetry/semantic-conventions');
var plugin = require('./plugin-BsCbZ1j2.cjs');
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
var apiLogs = require('@opentelemetry/api-logs');
var sdkLogs = require('@opentelemetry/sdk-logs');
require('@graphql-hive/gateway-runtime');
require('@graphql-mesh/utils');
require('@graphql-tools/utils');
require('@whatwg-node/promise-helpers');
require('./api.cjs');
require('@graphql-hive/core');
require('@graphql-mesh/transport-common');
require('graphql');
class HiveTracingSpanProcessor {
traceStateById = /* @__PURE__ */ new Map();
processor;
constructor(config) {
if (config.processor) {
this.processor = config.processor;
} else {
this.processor = new sdkTraceBase.BatchSpanProcessor(
new exporterTraceOtlpHttp.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 (isOperationSpan(span)) {
span.setAttribute("hive.graphql", true);
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 = core.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 (SPANS_WITH_ERRORS.includes(span.name)) {
copyAttribute(span, operationSpan, plugin.SEMATTRS_HIVE_GRAPHQL_ERROR_CODES);
copyAttribute(span, operationSpan, plugin.SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT);
}
if (span.name === "graphql.execute") {
copyAttribute(
span,
operationSpan,
plugin.SEMATTRS_HIVE_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[semanticConventions.SEMATTRS_HTTP_METHOD];
}
function copyAttribute(source, target, sourceAttrName, targetAttrName = sourceAttrName) {
target.attributes[targetAttrName] = source.attributes[sourceAttrName];
}
function isOperationSpan(span) {
if (!span.name.startsWith("graphql.operation")) {
return false;
}
const followingChar = span.name.at(17);
return !followingChar || followingChar === " ";
}
const SPANS_WITH_ERRORS = [
"graphql.parse",
"graphql.validate",
"graphql.execute"
];
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 {
apiLogs.logs.setGlobalLoggerProvider(options.provider);
}
} else {
const processors = options.processors ?? [];
if (options.exporter) {
if (options.batching !== false) {
processors.push(
new sdkLogs.BatchLogRecordProcessor(
options.exporter,
options.batching === true ? {} : options.batching
)
);
}
processors.push(new sdkLogs.SimpleLogRecordProcessor(options.exporter));
}
if (options.console) {
processors.push(
new sdkLogs.SimpleLogRecordProcessor(new sdkLogs.ConsoleLogRecordExporter())
);
}
apiLogs.logs.setGlobalLoggerProvider(
new sdkLogs.LoggerProvider({
...options,
processors
})
);
}
this.logger = apiLogs.logs.getLogger("gateway");
}
flush() {
const provider = apiLogs.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 ? api.context.active() : getContextForRequest(attributes)
});
}
}
const HIVE_LOG_LEVEL_NUMBERS = {
trace: apiLogs.SeverityNumber.TRACE,
debug: apiLogs.SeverityNumber.DEBUG,
info: apiLogs.SeverityNumber.INFO,
warn: apiLogs.SeverityNumber.WARN,
error: apiLogs.SeverityNumber.ERROR
};
function getContextForRequest(attributes) {
if (!attributes?.requestId) {
return api.ROOT_CONTEXT;
}
return plugin.otelCtxForRequestId.get(attributes.requestId) ?? api.ROOT_CONTEXT;
}
globalThis.__OTEL_PLUGIN_VERSION__ = '1.2.0-alpha-bb6f20ec80866c7d6f260782b156f9460b62848a';
let initialized = false;
function openTelemetrySetup(options) {
const log = options.log || new logger.Logger();
if (initialized) {
log.error(
`${initialized.name} integration has already been initialized by ${initialized.source}`
);
throw new Error(
`${initialized.name} integration already initialized. See previous logs for more information`
);
}
initialized = options._initialization || {
name: "OpenTelemetry",
source: `\`openTelemetrySetup\` utility function call ${getStackTrace()}`
};
if (plugin.getEnvBool("OTEL_SDK_DISABLED")) {
log.warn(
`${initialized.name} integration is disabled because \`OTEL_SDK_DISABLED\` environment variable is truthy`
);
return;
}
const logAttributes = {
...options._initialization?.logAttributes,
registrationResults: {}
};
let logMessage = `${initialized.name} integration is enabled`;
if (options.configureDiagLogger !== false) {
const [diagLogLevel, hiveLogLevel] = plugin.diagLogLevelFromEnv() ?? [
api.DiagLogLevel.VERBOSE,
null
];
const diagLog = log.child("[diag] ");
diagLog.verbose = diagLog.trace;
if (hiveLogLevel) {
diagLog.setLevel(hiveLogLevel);
}
api.diag.setLogger(diagLog, diagLogLevel);
core.setGlobalErrorHandler((err) => diagLog.error(err));
}
if (options.traces) {
if (options.traces.tracerProvider) {
if ("register" in options.traces.tracerProvider && typeof options.traces.tracerProvider.register === "function") {
logAttributes["registrationResults"].tracer = options.traces.tracerProvider.register();
} else {
logAttributes["registrationResults"].tracer = api.trace.setGlobalTracerProvider(options.traces.tracerProvider);
}
logMessage += " and provided TracerProvider has been registered";
} else {
let spanProcessors = options.traces.processors ?? [];
if (options.traces.exporter) {
spanProcessors.push(
resolveBatchingConfig(
options.traces.exporter,
options.traces.batching
)
);
logMessage += " and exporter have been registered";
logAttributes["batching"] = options.traces.batching ?? true;
}
if (options.traces.console) {
spanProcessors.push(new sdkTraceBase.SimpleSpanProcessor(new sdkTraceBase.ConsoleSpanExporter()));
logMessage += " in addition to an stdout debug exporter";
logAttributes["console"] = true;
}
const resource = createResource(
resources.resourceFromAttributes({
[semanticConventions.ATTR_SERVICE_NAME]: plugin.getEnvStr("OTEL_SERVICE_NAME") || "@graphql-hive/plugin-opentelemetry",
[semanticConventions.ATTR_SERVICE_VERSION]: plugin.getEnvStr("OTEL_SERVICE_VERSION") || globalThis.__OTEL_PLUGIN_VERSION__ || "unknown",
["hive.gateway.version"]: globalThis.__VERSION__,
["hive.otel.version"]: globalThis.__OTEL_PLUGIN_VERSION__ || "unknown"
}),
options.resource
);
logAttributes["resource"] = resource.attributes;
logAttributes["sampling"] = options.sampler ? "custom" : options.samplingRate;
logAttributes["registrationResults"].tracerProvider = api.trace.setGlobalTracerProvider(
new sdkTraceBase.BasicTracerProvider({
resource,
sampler: options.sampler ?? (options.samplingRate ? new sdkTraceBase.ParentBasedSampler({
root: new sdkTraceBase.TraceIdRatioBasedSampler(options.samplingRate)
}) : new sdkTraceBase.AlwaysOnSampler()),
spanProcessors,
generalLimits: options.generalLimits,
spanLimits: options.traces.spanLimits
})
);
}
}
if (options.contextManager !== null) {
logAttributes["registrationResults"].contextManager = api.context.setGlobalContextManager(options.contextManager);
}
if (!options.propagators || options.propagators.length !== 0) {
const propagators = options.propagators ?? [
new core.W3CBaggagePropagator(),
new core.W3CTraceContextPropagator()
];
logAttributes["registrationResults"].propagators = api.propagation.setGlobalPropagator(
propagators.length === 1 ? propagators[0] : new core.CompositePropagator({ propagators })
);
}
log.info(logAttributes, logMessage);
}
function hiveTracingSetup(options) {
const log = options.log || new logger.Logger();
options.target ??= plugin.getEnvStr("HIVE_TARGET");
if (!options.target) {
throw new Error(
"You must specify the Hive Registry `target`. Either provide `target` option or `HIVE_TARGET` environment variable."
);
}
const logAttributes = {
...options._initialization?.logAttributes,
target: options.target
};
if (!options.processor) {
options.accessToken ??= plugin.getEnvStr("HIVE_TRACING_ACCESS_TOKEN") ?? plugin.getEnvStr("HIVE_ACCESS_TOKEN");
if (!options.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."
);
}
logAttributes["endpoint"] = options.endpoint;
logAttributes["batching"] = options.batching;
}
openTelemetrySetup({
...options,
log,
resource: createResource(
resources.resourceFromAttributes({
"hive.target_id": options.target,
[semanticConventions.ATTR_SERVICE_NAME]: plugin.getEnvStr("OTEL_SERVICE_NAME") || "hive-gateway",
[semanticConventions.ATTR_SERVICE_VERSION]: plugin.getEnvStr("OTEL_SERVICE_VERSION") || globalThis.__OTEL_PLUGIN_VERSION__ || "unknown"
}),
options.resource
),
traces: {
processors: [
new HiveTracingSpanProcessor(
options
)
],
spanLimits: options.spanLimits,
console: options.console
},
_initialization: options._initialization || {
name: "Hive Tracing",
source: `\`hiveTracingSetup\` utility function call ${getStackTrace()}`,
logAttributes
}
});
}
function resolveBatchingConfig(exporter, batchingConfig) {
const value = batchingConfig ?? true;
if (value === true) {
return new sdkTraceBase.BatchSpanProcessor(exporter);
} else if (value === false) {
return new sdkTraceBase.SimpleSpanProcessor(exporter);
} else {
return new sdkTraceBase.BatchSpanProcessor(exporter, value);
}
}
function createResource(baseResource, userResource) {
if (!userResource) {
return baseResource;
}
if ("serviceName" in userResource) {
return baseResource.merge(
resources.resourceFromAttributes({
[semanticConventions.ATTR_SERVICE_NAME]: userResource.serviceName,
[semanticConventions.ATTR_SERVICE_VERSION]: userResource.serviceVersion
})
);
}
return baseResource.merge(userResource);
}
function getStackTrace() {
return (new Error().stack ?? "").split("\n").slice(3).join("\n").trim();
}
function disable() {
api.trace.disable();
api.context.disable();
api.propagation.disable();
initialized = false;
}
Object.defineProperty(exports, "ATTR_SERVICE_NAME", {
enumerable: true,
get: function () { return semanticConventions.ATTR_SERVICE_NAME; }
});
Object.defineProperty(exports, "ATTR_SERVICE_VERSION", {
enumerable: true,
get: function () { return semanticConventions.ATTR_SERVICE_VERSION; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_CLIENT_IP", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_CLIENT_IP; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_HOST", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_HOST; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_METHOD", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_METHOD; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_ROUTE", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_ROUTE; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_SCHEME", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_SCHEME; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_SERVER_NAME", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_SERVER_NAME; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_STATUS_CODE", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_STATUS_CODE; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_URL", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_URL; }
});
Object.defineProperty(exports, "SEMATTRS_HTTP_USER_AGENT", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_HTTP_USER_AGENT; }
});
Object.defineProperty(exports, "SEMATTRS_NET_HOST_NAME", {
enumerable: true,
get: function () { return semanticConventions.SEMATTRS_NET_HOST_NAME; }
});
exports.SEMATTRS_GRAPHQL_DOCUMENT = plugin.SEMATTRS_GRAPHQL_DOCUMENT;
exports.SEMATTRS_GRAPHQL_OPERATION_NAME = plugin.SEMATTRS_GRAPHQL_OPERATION_NAME;
exports.SEMATTRS_GRAPHQL_OPERATION_TYPE = plugin.SEMATTRS_GRAPHQL_OPERATION_TYPE;
exports.SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES = plugin.SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES;
exports.SEMATTRS_HIVE_GATEWAY_UPSTREAM_SUBGRAPH_NAME = plugin.SEMATTRS_HIVE_GATEWAY_UPSTREAM_SUBGRAPH_NAME;
exports.SEMATTRS_HIVE_GRAPHQL_ERROR_CODES = plugin.SEMATTRS_HIVE_GRAPHQL_ERROR_CODES;
exports.SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT = plugin.SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT;
exports.SEMATTRS_HIVE_GRAPHQL_OPERATION_HASH = plugin.SEMATTRS_HIVE_GRAPHQL_OPERATION_HASH;
exports.HIVE_LOG_LEVEL_NUMBERS = HIVE_LOG_LEVEL_NUMBERS;
exports.HiveTracingSpanProcessor = HiveTracingSpanProcessor;
exports.OpenTelemetryLogWriter = OpenTelemetryLogWriter;
exports.disable = disable;
exports.getContextForRequest = getContextForRequest;
exports.hiveTracingSetup = hiveTracingSetup;
exports.openTelemetrySetup = openTelemetrySetup;