{"version":3,"file":"undici.mjs","names":[],"sources":["../src/undici.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport * as diagch from 'diagnostics_channel';\nimport { URL } from 'url';\n\nimport {\n  InstrumentationBase,\n  safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport {\n  Attributes,\n  context,\n  Histogram,\n  HrTime,\n  INVALID_SPAN_CONTEXT,\n  propagation,\n  Span,\n  SpanKind,\n  SpanStatusCode,\n  trace,\n  ValueType,\n} from '@opentelemetry/api';\nimport {\n  hrTime,\n  hrTimeDuration,\n  hrTimeToMilliseconds,\n} from '@opentelemetry/core';\nimport {\n  ATTR_ERROR_TYPE,\n  ATTR_HTTP_REQUEST_METHOD,\n  ATTR_HTTP_REQUEST_METHOD_ORIGINAL,\n  ATTR_HTTP_RESPONSE_STATUS_CODE,\n  ATTR_NETWORK_PEER_ADDRESS,\n  ATTR_NETWORK_PEER_PORT,\n  ATTR_NETWORK_PROTOCOL_VERSION,\n  ATTR_SERVER_ADDRESS,\n  ATTR_SERVER_PORT,\n  ATTR_URL_FULL,\n  ATTR_URL_PATH,\n  ATTR_URL_QUERY,\n  ATTR_URL_SCHEME,\n  ATTR_USER_AGENT_ORIGINAL,\n  METRIC_HTTP_CLIENT_REQUEST_DURATION,\n} from '@opentelemetry/semantic-conventions';\n\n/** @knipignore */\nimport { PACKAGE_NAME, PACKAGE_VERSION } from './version';\n\nimport {\n  ListenerRecord,\n  RequestHeadersMessage,\n  RequestMessage,\n  RequestTrailersMessage,\n  ResponseHeadersMessage,\n} from './internal-types';\nimport { UndiciInstrumentationConfig, UndiciRequest } from './types';\n\ninterface InstrumentationRecord {\n  span: Span;\n  attributes: Attributes;\n  startTime: HrTime;\n}\n\n// A combination of https://github.com/elastic/apm-agent-nodejs and\n// https://github.com/gadget-inc/opentelemetry-instrumentations/blob/main/packages/opentelemetry-instrumentation-undici/src/index.ts\nexport class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumentationConfig> {\n  // Keep ref to avoid https://github.com/nodejs/node/issues/42170 bug and for\n  // unsubscribing.\n  declare private _channelSubs: Array<ListenerRecord>;\n  private _recordFromReq = new WeakMap<UndiciRequest, InstrumentationRecord>();\n\n  declare private _httpClientDurationHistogram: Histogram;\n\n  constructor(config: UndiciInstrumentationConfig = {}) {\n    super(PACKAGE_NAME, PACKAGE_VERSION, config);\n  }\n\n  // No need to instrument files/modules\n  protected override init() {\n    return undefined;\n  }\n\n  override disable(): void {\n    super.disable();\n    this._channelSubs.forEach(sub => sub.unsubscribe());\n    this._channelSubs.length = 0;\n  }\n\n  override enable(): void {\n    // \"enabled\" handling is currently a bit messy with InstrumentationBase.\n    // If constructed with `{enabled: false}`, this `.enable()` is still called,\n    // and `this.getConfig().enabled !== this.isEnabled()`, creating confusion.\n    //\n    // For now, this class will setup for instrumenting if `.enable()` is\n    // called, but use `this.getConfig().enabled` to determine if\n    // instrumentation should be generated. This covers the more likely common\n    // case of config being given a construction time, rather than later via\n    // `instance.enable()`, `.disable()`, or `.setConfig()` calls.\n    super.enable();\n\n    // This method is called by the super-class constructor before ours is\n    // called. So we need to ensure the property is initalized.\n    this._channelSubs = this._channelSubs || [];\n\n    // Avoid to duplicate subscriptions\n    if (this._channelSubs.length > 0) {\n      return;\n    }\n\n    this.subscribeToChannel(\n      'undici:request:create',\n      this.onRequestCreated.bind(this)\n    );\n    this.subscribeToChannel(\n      'undici:client:sendHeaders',\n      this.onRequestHeaders.bind(this)\n    );\n    this.subscribeToChannel(\n      'undici:request:headers',\n      this.onResponseHeaders.bind(this)\n    );\n    this.subscribeToChannel('undici:request:trailers', this.onDone.bind(this));\n    this.subscribeToChannel('undici:request:error', this.onError.bind(this));\n  }\n\n  protected override _updateMetricInstruments() {\n    this._httpClientDurationHistogram = this.meter.createHistogram(\n      METRIC_HTTP_CLIENT_REQUEST_DURATION,\n      {\n        description: 'Measures the duration of outbound HTTP requests.',\n        unit: 's',\n        valueType: ValueType.DOUBLE,\n        advice: {\n          explicitBucketBoundaries: [\n            0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,\n            7.5, 10,\n          ],\n        },\n      }\n    );\n  }\n\n  private subscribeToChannel(\n    diagnosticChannel: string,\n    onMessage: (message: any, name: string | symbol) => void\n  ) {\n    diagch.subscribe(diagnosticChannel, onMessage);\n    const unsubscribe = () => diagch.unsubscribe(diagnosticChannel, onMessage);\n\n    this._channelSubs.push({\n      name: diagnosticChannel,\n      unsubscribe,\n    });\n  }\n\n  private parseRequestHeaders(request: UndiciRequest) {\n    const result = new Map<string, string | string[]>();\n\n    if (Array.isArray(request.headers)) {\n      // headers are an array [k1, v2, k2, v2] (undici v6+)\n      // values could be string or a string[] for multiple values\n      for (let i = 0; i < request.headers.length; i += 2) {\n        const key = request.headers[i];\n        const value = request.headers[i + 1];\n\n        // Key should always be a string, but the types don't know that, and let's be safe\n        if (typeof key === 'string') {\n          result.set(key.toLowerCase(), value);\n        }\n      }\n    } else if (typeof request.headers === 'string') {\n      // headers are a raw string (undici v5)\n      // headers could be repeated in several lines for multiple values\n      const headers = request.headers.split('\\r\\n');\n      for (const line of headers) {\n        if (!line) {\n          continue;\n        }\n        const colonIndex = line.indexOf(':');\n        if (colonIndex === -1) {\n          // Invalid header? Probably this can't happen, but again let's be safe.\n          continue;\n        }\n        const key = line.substring(0, colonIndex).toLowerCase();\n        const value = line.substring(colonIndex + 1).trim();\n        const allValues = result.get(key);\n\n        if (allValues && Array.isArray(allValues)) {\n          allValues.push(value);\n        } else if (allValues) {\n          result.set(key, [allValues, value]);\n        } else {\n          result.set(key, value);\n        }\n      }\n    }\n    return result;\n  }\n\n  // This is the 1st message we receive for each request (fired after request creation). Here we will\n  // create the span and populate some atttributes, then link the span to the request for further\n  // span processing\n  private onRequestCreated({ request }: RequestMessage): void {\n    // Ignore if:\n    // - instrumentation is disabled\n    // - ignored by config\n    // - method is 'CONNECT'\n    const config = this.getConfig();\n    const enabled = config.enabled !== false;\n    const shouldIgnoreReq = safeExecuteInTheMiddle(\n      () =>\n        !enabled ||\n        request.method === 'CONNECT' ||\n        config.ignoreRequestHook?.(request),\n      e => e && this._diag.error('caught ignoreRequestHook error: ', e),\n      true\n    );\n\n    if (shouldIgnoreReq) {\n      return;\n    }\n\n    const startTime = hrTime();\n    let requestUrl;\n    try {\n      requestUrl = new URL(request.path, request.origin);\n    } catch (err) {\n      this._diag.warn('could not determine url.full:', err);\n      // Skip instrumenting this request.\n      return;\n    }\n    const urlScheme = requestUrl.protocol.replace(':', '');\n    const requestMethod = this.getRequestMethod(request.method);\n    const attributes: Attributes = {\n      [ATTR_HTTP_REQUEST_METHOD]: requestMethod,\n      [ATTR_HTTP_REQUEST_METHOD_ORIGINAL]: request.method,\n      [ATTR_URL_FULL]: requestUrl.toString(),\n      [ATTR_URL_PATH]: requestUrl.pathname,\n      [ATTR_URL_QUERY]: requestUrl.search,\n      [ATTR_URL_SCHEME]: urlScheme,\n    };\n\n    const schemePorts: Record<string, string> = { https: '443', http: '80' };\n    const serverAddress = requestUrl.hostname;\n    const serverPort = requestUrl.port || schemePorts[urlScheme];\n\n    attributes[ATTR_SERVER_ADDRESS] = serverAddress;\n    if (serverPort && !isNaN(Number(serverPort))) {\n      attributes[ATTR_SERVER_PORT] = Number(serverPort);\n    }\n\n    // Get user agent from headers\n    const headersMap = this.parseRequestHeaders(request);\n    const userAgentValues = headersMap.get('user-agent');\n\n    if (userAgentValues) {\n      // NOTE: having multiple user agents is not expected so\n      // we're going to take last one like `curl` does\n      // ref: https://curl.se/docs/manpage.html#-A\n      const userAgent = Array.isArray(userAgentValues)\n        ? userAgentValues[userAgentValues.length - 1]\n        : userAgentValues;\n      attributes[ATTR_USER_AGENT_ORIGINAL] = userAgent;\n    }\n\n    // Get attributes from the hook if present\n    const hookAttributes = safeExecuteInTheMiddle(\n      () => config.startSpanHook?.(request),\n      e => e && this._diag.error('caught startSpanHook error: ', e),\n      true\n    );\n    if (hookAttributes) {\n      Object.entries(hookAttributes).forEach(([key, val]) => {\n        attributes[key] = val;\n      });\n    }\n\n    // Check if parent span is required via config and:\n    // - if a parent is required but not present, we use a `NoopSpan` to still\n    //   propagate context without recording it.\n    // - create a span otherwise\n    const activeCtx = context.active();\n    const currentSpan = trace.getSpan(activeCtx);\n    let span: Span;\n\n    if (\n      config.requireParentforSpans &&\n      (!currentSpan || !trace.isSpanContextValid(currentSpan.spanContext()))\n    ) {\n      span = trace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n    } else {\n      span = this.tracer.startSpan(\n        requestMethod === '_OTHER' ? 'HTTP' : requestMethod,\n        {\n          kind: SpanKind.CLIENT,\n          attributes: attributes,\n        },\n        activeCtx\n      );\n    }\n\n    // Execute the request hook if defined\n    safeExecuteInTheMiddle(\n      () => config.requestHook?.(span, request),\n      e => e && this._diag.error('caught requestHook error: ', e),\n      true\n    );\n\n    // Context propagation goes last so no hook can tamper\n    // the propagation headers\n    const requestContext = trace.setSpan(context.active(), span);\n    const addedHeaders: Record<string, string> = {};\n    propagation.inject(requestContext, addedHeaders);\n\n    const headerEntries = Object.entries(addedHeaders);\n\n    for (let i = 0; i < headerEntries.length; i++) {\n      const [k, v] = headerEntries[i];\n\n      if (typeof request.addHeader === 'function') {\n        request.addHeader(k, v);\n      } else if (typeof request.headers === 'string') {\n        request.headers += `${k}: ${v}\\r\\n`;\n      } else if (Array.isArray(request.headers)) {\n        // undici@6.11.0 accidentally, briefly removed `request.addHeader()`.\n        request.headers.push(k, v);\n      }\n    }\n    this._recordFromReq.set(request, { span, attributes, startTime });\n  }\n\n  // This is the 2nd message we receive for each request. It is fired when connection with\n  // the remote is established and about to send the first byte. Here we do have info about the\n  // remote address and port so we can populate some `network.*` attributes into the span\n  private onRequestHeaders({ request, socket }: RequestHeadersMessage): void {\n    const record = this._recordFromReq.get(request as UndiciRequest);\n\n    if (!record) {\n      return;\n    }\n\n    const config = this.getConfig();\n    const { span } = record;\n    const { remoteAddress, remotePort, alpnProtocol } = socket;\n    const spanAttributes: Attributes = {\n      [ATTR_NETWORK_PEER_ADDRESS]: remoteAddress,\n      [ATTR_NETWORK_PEER_PORT]: remotePort,\n      // undici only ever offers `http/1.1` and, with `allowH2`, `h2` as ALPN\n      // protocols; a connection that negotiated neither is HTTP/1.1.\n      [ATTR_NETWORK_PROTOCOL_VERSION]: alpnProtocol === 'h2' ? '2' : '1.1',\n    };\n\n    // After hooks have been processed (which may modify request headers)\n    // we can collect the headers based on the configuration\n    if (config.headersToSpanAttributes?.requestHeaders) {\n      const headersToAttribs = new Set(\n        config.headersToSpanAttributes.requestHeaders.map(n => n.toLowerCase())\n      );\n      const headersMap = this.parseRequestHeaders(request);\n\n      for (const [name, value] of headersMap.entries()) {\n        if (headersToAttribs.has(name)) {\n          const attrValue = Array.isArray(value) ? value : [value];\n          spanAttributes[`http.request.header.${name}`] = attrValue;\n        }\n      }\n    }\n\n    span.setAttributes(spanAttributes);\n    record.attributes = Object.assign(record.attributes, spanAttributes);\n  }\n\n  // This is the 3rd message we get for each request and it's fired when the server\n  // headers are received, body may not be accessible yet.\n  // From the response headers we can set the status and content length\n  private onResponseHeaders({\n    request,\n    response,\n  }: ResponseHeadersMessage): void {\n    const record = this._recordFromReq.get(request);\n\n    if (!record) {\n      return;\n    }\n\n    const { span, attributes } = record;\n    const isError = response.statusCode >= 400;\n    const spanAttributes: Attributes = {\n      [ATTR_HTTP_RESPONSE_STATUS_CODE]: response.statusCode,\n    };\n\n    if (isError) {\n      spanAttributes[ATTR_ERROR_TYPE] = String(response.statusCode);\n    }\n\n    const config = this.getConfig();\n\n    // Execute the response hook if defined\n    safeExecuteInTheMiddle(\n      () => config.responseHook?.(span, { request, response }),\n      e => e && this._diag.error('caught responseHook error: ', e),\n      true\n    );\n\n    if (config.headersToSpanAttributes?.responseHeaders) {\n      const headersToAttribs = new Set();\n      config.headersToSpanAttributes?.responseHeaders.forEach(name =>\n        headersToAttribs.add(name.toLowerCase())\n      );\n\n      for (let idx = 0; idx < response.headers.length; idx = idx + 2) {\n        const name = response.headers[idx].toString().toLowerCase();\n        const value = response.headers[idx + 1];\n\n        if (headersToAttribs.has(name)) {\n          const attrName = `http.response.header.${name}`;\n          if (!Object.hasOwn(spanAttributes, attrName)) {\n            spanAttributes[attrName] = [value.toString()];\n          } else {\n            (spanAttributes[attrName] as string[]).push(value.toString());\n          }\n        }\n      }\n    }\n\n    span.setAttributes(spanAttributes);\n    span.setStatus({\n      code: isError ? SpanStatusCode.ERROR : SpanStatusCode.UNSET,\n    });\n    record.attributes = Object.assign(attributes, spanAttributes);\n  }\n\n  // This is the last event we receive if the request went without any errors\n  private onDone({ request }: RequestTrailersMessage): void {\n    const record = this._recordFromReq.get(request);\n\n    if (!record) {\n      return;\n    }\n    const { span, attributes, startTime } = record;\n\n    // End the span\n    span.end();\n    this._recordFromReq.delete(request);\n\n    // Record metrics\n    this.recordRequestDuration(attributes, startTime);\n  }\n\n  // This is the event we get when something is wrong in the request like\n  // - invalid options when calling `fetch` global API or any undici method for request\n  // - connectivity errors such as unreachable host\n  // - requests aborted through an `AbortController.signal`\n  // NOTE: server errors are considered valid responses and it's the lib consumer\n  // who should deal with that.\n  private onError({ request, error }: any): void {\n    const record = this._recordFromReq.get(request);\n\n    if (!record) {\n      return;\n    }\n\n    const { span, attributes, startTime } = record;\n\n    // Per OTel HTTP spec: if the request was intentionally cancelled via an\n    // AbortController signal, it SHOULD NOT be treated as an error.\n    // Span status should be left unset and error.type should not be set.\n    // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client\n    const isAbort =\n      error.name === 'AbortError' ||\n      (typeof DOMException !== 'undefined' &&\n        error instanceof DOMException &&\n        error.code === DOMException.ABORT_ERR);\n\n    if (isAbort) {\n      span.end();\n    } else {\n      // error.type must be a low-cardinality class of error per semconv, and an\n      // empty value collapses into a duplicate series on Prometheus-based\n      // exporters. Use the error code/name, never the free-form, possibly empty\n      // message.\n      const errorType = error.code || error.name || 'Error';\n      attributes[ATTR_ERROR_TYPE] = errorType;\n      span.setAttribute(ATTR_ERROR_TYPE, errorType);\n\n      span.recordException(error);\n      span.setStatus({\n        code: SpanStatusCode.ERROR,\n        message: error.message,\n      });\n      span.end();\n    }\n\n    this._recordFromReq.delete(request);\n    this.recordRequestDuration(attributes, startTime);\n  }\n\n  private recordRequestDuration(attributes: Attributes, startTime: HrTime) {\n    // Time to record metrics\n    const metricsAttributes: Attributes = {};\n    // Get the attribs already in span attributes\n    const keysToCopy = [\n      ATTR_HTTP_RESPONSE_STATUS_CODE,\n      ATTR_HTTP_REQUEST_METHOD,\n      ATTR_SERVER_ADDRESS,\n      ATTR_SERVER_PORT,\n      ATTR_URL_SCHEME,\n      ATTR_ERROR_TYPE,\n      ATTR_NETWORK_PROTOCOL_VERSION,\n    ];\n    keysToCopy.forEach(key => {\n      if (key in attributes) {\n        metricsAttributes[key] = attributes[key];\n      }\n    });\n\n    // Take the duration and record it\n    const durationSeconds =\n      hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime())) / 1000;\n    this._httpClientDurationHistogram.record(\n      durationSeconds,\n      metricsAttributes\n    );\n  }\n\n  private getRequestMethod(original: string): string {\n    const knownMethods = {\n      CONNECT: true,\n      OPTIONS: true,\n      HEAD: true,\n      GET: true,\n      POST: true,\n      PUT: true,\n      PATCH: true,\n      DELETE: true,\n      TRACE: true,\n      // QUERY from https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/\n      QUERY: true,\n    };\n\n    if (original.toUpperCase() in knownMethods) {\n      return original.toUpperCase();\n    }\n\n    return '_OTHER';\n  }\n}\n"],"mappings":"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"}