@graphql-hive/plugin-opentelemetry
Version: 
403 lines (397 loc) • 15.5 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-Dv3s2O19.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 (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 = 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 (span.name === "graphql.execute") {
      copyAttribute(span, operationSpan, plugin.SEMATTRS_HIVE_GRAPHQL_ERROR_CODES);
      copyAttribute(span, operationSpan, plugin.SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT);
      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];
}
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.0.11-alpha-f29abb07ca556b593bd55f8a4324e67665d784dc';
function openTelemetrySetup(options) {
  const log = options.log || new logger.Logger();
  if (plugin.getEnvBool("OTEL_SDK_DISABLED")) {
    log.warn(
      "OpenTelemetry integration is disabled because `OTEL_SDK_DISABLED` environment variable is truthy"
    );
    return;
  }
  const logAttributes = { registrationResults: {} };
  let logMessage = "OpenTelemetry 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 baseResource = resources.resourceFromAttributes({
        [semanticConventions.ATTR_SERVICE_NAME]: options.resource && "serviceName" in options.resource ? options.resource?.serviceName : plugin.getEnvStr("OTEL_SERVICE_NAME") || "@graphql-hive/plugin-opentelemetry",
        [semanticConventions.ATTR_SERVICE_VERSION]: options.resource && "serviceVersion" in options.resource ? options.resource?.serviceVersion : plugin.getEnvStr("OTEL_SERVICE_VERSION") || globalThis.__OTEL_PLUGIN_VERSION__ || "unknown",
        ["hive.gateway.version"]: globalThis.__VERSION__,
        ["hive.otel.version"]: globalThis.__OTEL_PLUGIN_VERSION__ || "unknown"
      });
      const resource = options.resource && !("serviceName" in options.resource) ? baseResource.merge(options.resource) : baseResource;
      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(config) {
  const log = config.log || new logger.Logger();
  config.target ??= plugin.getEnvStr("HIVE_TARGET");
  if (!config.target) {
    throw new Error(
      "You must specify the Hive Registry `target`. Either provide `target` option or `HIVE_TARGET` environment variable."
    );
  }
  const logAttributes = { target: config.target };
  if (!config.processor) {
    config.accessToken ??= plugin.getEnvStr("HIVE_TRACING_ACCESS_TOKEN") ?? plugin.getEnvStr("HIVE_ACCESS_TOKEN");
    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."
      );
    }
    logAttributes["endpoint"] = config.endpoint;
    logAttributes["batching"] = config.batching;
  }
  openTelemetrySetup({
    log,
    contextManager: config.contextManager,
    resource: resources.resourceFromAttributes({
      "hive.target_id": config.target
    }),
    traces: {
      processors: [
        new HiveTracingSpanProcessor(config)
      ]
    }
  });
  log.info(logAttributes, "Hive Tracing integration has been enabled");
}
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);
  }
}
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.getContextForRequest = getContextForRequest;
exports.hiveTracingSetup = hiveTracingSetup;
exports.openTelemetrySetup = openTelemetrySetup;