aws / aws-xray-sdk-node

The official AWS X-Ray SDK for Node.js.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

node 18 fetch capture

danilobuerger opened this issue · comments

node 18 introduced a native fetch: https://nodejs.org/de/blog/announcements/v18-release-announce/#fetch-experimental
how can we capture this? It seems like captureHTTPsGlobal does not work.

Hello!
It looks like opentelemetry-js introduced instrumentation for the new native fetch in Node a few days ago. The npm package opentelemetry/instrumentation-fetch was released 13 days ago, and the documentation on the opentelemetry-js repo shows an example of how to use this instrumentation! Please keep in mind that this package is experimental and still under active development.

@carolabadeer, what about supporting it in this X-Ray SDK?

At least for now there's no deprecation date and as you pointed out the Otel package is experimental, so what should customer use once Nodejs 18 runtime is released?

@dreamorosi to be fair the Node v18 fetch is itself experimental :)

At this time we do not have plans to support instrumenting fetch in the X-Ray SDK, and continue to recommend using OTel for instrumenting Node fetch. We would be open to a PR for this instrumentation with sufficient testing in sdk_contrib.

Not pretty, and ignoring manual mode.

// reverse engineered from https://github.com/aws/aws-xray-sdk-node/blob/93a4f31de2974c10b25ec317bfadd07aabcc9015/packages/core/lib/patchers/http_p.js
// we don't need to take care of manual mode

function traceFetch() {
  const fetch = globalThis.fetch
  globalThis.fetch = async function (resource, options) {
    const traceHeader =
      resource.headers?.get('X-Amzn-Trace-Id') ?? options?.['X-Amzn-Trace-Id']

    if (!traceHeader) {
      const parent = AWSXRay.resolveSegment()

      if (parent) {
        const url = resource?.url ?? resource
        const method = resource?.method ?? options?.method ?? 'GET'
        const { hostname } = new URL(url)
        const subsegment = parent.notTraced
          ? parent.addNewSubsegmentWithoutSampling(hostname)
          : parent.addNewSubsegment(hostname)
        const root = parent.segment ? parent.segment : parent
        subsegment.namespace = 'remote'

        if (!options) {
          options = {}
        }

        if (!options.headers) {
          options.headers = {}
        }

        options.headers['X-Amzn-Trace-Id'] =
          'Root=' +
          root.trace_id +
          ';Parent=' +
          subsegment.id +
          ';Sampled=' +
          (subsegment.notTraced ? '0' : '1')

        subsegment.http = {
          request: {
            url,
            method
          }
        }

        try {
          const res = await fetch.call(globalThis, resource, options)
          if (res.status === 429) {
            subsegment.addThrottleFlag()
          } else if (!res.ok) {
            subsegment.addErrorFlag()
          }
          const cause = AWSXRay.utils.getCauseTypeFromHttpStatus(res.status)
          if (cause) {
            subsegment[cause] = true
          }
          const contentLength = res.headers.get('content-length')
          subsegment.http.response = {
            status: res.status,
            ...(contentLength && { content_length: contentLength })
          }
          subsegment.close()
          return res
        } catch (err) {
          subsegment.close(err)
          throw err
        }
      }
    }

    return await fetch.call(globalThis, resource, options)
  }
}