Skip to content

CORS Preflight Requests not accounted for #5122

Open
@mariomc

Description

What happened?

Currently XHR and Fetch instrumentation are treating CORS with the assumption that there's 2 entries (one for the actual request, another for the preflight) on the performance entry.
This is not the case.

Steps to Reproduce

  • Launch the web-opentelemetry-example
  • Instrument a client with either XHR or fetch instrumentation

Expected Result

  • CORS preflight requests are created as child spans of the main request

Actual Result

  • No child spans are created

Additional Details

I believe this behaviour happens because according to Resource Timing Level 2:

For fetches composed of multiple requests (e.g. preflights, authentication challenge-response, redirects, and so on), the reported responseStart value is that of the last request.

Which means there's no duplicate entry performance entry in case of preflights.

OpenTelemetry Setup Code

const { context, trace } = require( '@opentelemetry/api');
const { ConsoleSpanExporter, SimpleSpanProcessor } = require( '@opentelemetry/sdk-trace-base');
const { OTLPTraceExporter } = require( '@opentelemetry/exporter-trace-otlp-http');
const { WebTracerProvider } = require( '@opentelemetry/sdk-trace-web');
const { FetchInstrumentation } = require( '@opentelemetry/instrumentation-fetch');
const { ZoneContextManager } = require( '@opentelemetry/context-zone');
const { B3Propagator } = require( '@opentelemetry/propagator-b3');
const { registerInstrumentations } = require( '@opentelemetry/instrumentation');
const { Resource } = require('@opentelemetry/resources');
const { SEMRESATTRS_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');

const provider = new WebTracerProvider({
  resource: new Resource({
    [SEMRESATTRS_SERVICE_NAME]: 'fetch-web-service'
  })
});

// Note: For production consider using the "BatchSpanProcessor" to reduce the number of requests
// to your exporter. Using the SimpleSpanProcessor here as it sends the spans immediately to the
// exporter without delay
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter()));
provider.register({
  contextManager: new ZoneContextManager(),
  propagator: new B3Propagator(),
});

registerInstrumentations({
  instrumentations: [
    new FetchInstrumentation({
      ignoreUrls: [/localhost:8090\/sockjs-node/],
      propagateTraceHeaderCorsUrls: [
        'https://cors-test.appspot.com/test',
        'https://httpbin.org/get',
      ],
      clearTimingResources: true,
    }),
  ],
});

const webTracerWithZone = provider.getTracer('example-tracer-web');

const getData = (url) => fetch(url, {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
});

// example of keeping track of context between async operations
const prepareClickEvent = () => {
  const url = 'https://httpbin.org/get';

  const element = document.getElementById('button1');

  const onClick = () => {
    const singleSpan = webTracerWithZone.startSpan('files-series-info');
    context.with(trace.setSpan(context.active(), singleSpan), () => {
      getData(url).then((_data) => {
        trace.getSpan(context.active()).addEvent('fetching-single-span-completed');
        singleSpan.end();
      });
    });
    for (let i = 0, j = 5; i < j; i += 1) {
      const span = webTracerWithZone.startSpan(`files-series-info-${i}`);
      context.with(trace.setSpan(context.active(), span), () => {
        getData(url).then((_data) => {
          trace.getSpan(context.active()).addEvent(`fetching-span-${i}-completed`);
          span.end();
        });
      });
    }
  };
  element.addEventListener('click', onClick);
};

window.addEventListener('load', prepareClickEvent);

package.json

{
  "name": "web-opentelemetry-example",
  "private": true,
  "version": "0.53.0",
  "description": "Example of using @opentelemetry/sdk-trace-web and @opentelemetry/sdk-metrics in browser",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve --progress --color --port 8090 --config webpack.dev.config.js --hot --host 0.0.0.0 --compress",
    "start-nc": "webpack serve --progress --color --port 8090 --config webpack.dev.config.js --hot --host 0.0.0.0 --no-compress",
    "start-prod": "webpack serve --progress --color --port 8090 --config webpack.prod.config.js --hot --host 0.0.0.0 --compress",
    "start-prodnc": "webpack serve --progress --color --port 8090 --config webpack.prod.config.js --hot --host 0.0.0.0 --no-compress",
    "docker:start": "cd ./docker && docker-compose down && docker-compose up",
    "docker:startd": "cd ./docker && docker-compose down && docker-compose up -d",
    "docker:stop": "cd ./docker && docker-compose down",
    "align-api-deps": "node ../../scripts/align-api-deps.js"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git"
  },
  "keywords": [
    "opentelemetry",
    "tracing",
    "metrics",
    "web"
  ],
  "engines": {
    "node": ">=14"
  },
  "author": "OpenTelemetry Authors",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/open-telemetry/opentelemetry-js/issues"
  },
  "devDependencies": {
    "@babel/core": "^7.23.6",
    "@babel/preset-env": "^7.22.20",
    "babel-loader": "^8.0.6",
    "ts-loader": "^9.2.6",
    "typescript": "^4.5.2",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.5.0",
    "webpack-merge": "^5.10.0"
  },
  "dependencies": {
    "@opentelemetry/api": "^1.3.0",
    "@opentelemetry/context-zone": "1.26.0",
    "@opentelemetry/core": "1.26.0",
    "@opentelemetry/exporter-metrics-otlp-http": "0.53.0",
    "@opentelemetry/exporter-trace-otlp-http": "0.53.0",
    "@opentelemetry/exporter-trace-otlp-proto": "0.53.0",
    "@opentelemetry/exporter-zipkin": "1.26.0",
    "@opentelemetry/instrumentation": "0.53.0",
    "@opentelemetry/instrumentation-fetch": "0.53.0",
    "@opentelemetry/instrumentation-xml-http-request": "0.53.0",
    "@opentelemetry/propagator-b3": "1.26.0",
    "@opentelemetry/sdk-metrics": "1.26.0",
    "@opentelemetry/sdk-trace-base": "1.26.0",
    "@opentelemetry/sdk-trace-web": "1.26.0",
    "@opentelemetry/semantic-conventions": "1.27.0"
  },
  "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/tracer-web"
}

Relevant log output

No response

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions