open-telemetry / opentelemetry-js

OpenTelemetry JavaScript Client
https://opentelemetry.io
Apache License 2.0
2.74k stars 795 forks source link

Exceptions are not recorded when using auto instrumentation with fetch instrumentation #4948

Closed jc-akkodis closed 1 month ago

jc-akkodis commented 2 months ago

I have a reactjs application where I use @opentelemetry/auto-instrumentations-web + @opentelemetry/instrumentation-fetch with the latest version. When I do an fetch and I get e.g. 400 back and I catch the exception, then I would assue to see the exception event in the span of the fetch request, but it is not recorded.

This is my current implementation:

import * as React from 'react';

const { WebTracerProvider } = require('@opentelemetry/sdk-trace-web');
const { getWebAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-web');
const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { ZoneContextManager } = require('@opentelemetry/context-zone');
import { Resource } from '@opentelemetry/resources';

import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';

const exporter = new OTLPTraceExporter({
  url: 'http://localhost:8080/v1/traces',
});

const provider = new WebTracerProvider({
  resource: new Resource({
    [SEMRESATTRS_SERVICE_NAME]: 'one one go',
  }),
});
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register({
  contextManager: new ZoneContextManager(),
});

const fetchInstrumentation = new FetchInstrumentation({});

fetchInstrumentation.setTracerProvider(provider);

registerInstrumentations({
  instrumentations: [
    getWebAutoInstrumentations({
      // load custom configuration for xml-http-request instrumentation
      '@opentelemetry/instrumentation-xml-http-request': {
        clearTimingResources: true,
      },
    }),
    fetchInstrumentation,
  ],
});

export default function TraceProvider({ children }) {
  return <>{children}</>;
}

Call:

public async getEmail(email: string): Promise<IAccountModel> {
    const { data } = await axios.get<IAccountModel>(this.backEndUrl + '/api/GetUserEmail?email=' + email, {
      withCredentials: true,
    });
    return data;
  }

...

getEmail("email")
.then((data) => {
  if (data) {
    setShowSpinner(false);

    toast.info(`todo: Text einfügen`);
  }
})
.catch((x) => {
  tryParseMessageAndInformUser(x.message);
  setShowSpinner(false);
});

I tried to force the record of the exception in the catch section but it does not work since the span which I get is enden (https://github.com/open-telemetry/opentelemetry-js/blob/5c1ae0aa5b686aae3e7511f1eb9cfcc4d7ab2326/packages/opentelemetry-sdk-trace-base/src/Span.ts#L144):

const span1 = opentelemetry.trace.getSpan(opentelemetry.context.active());
span1.recordException(x);
const span2 = opentelemetry.trace.getActiveSpan();
span1.recordException(x);

If i create an other span then the exception would be recorded but I would like to avoid this:

const span = opentelemetry.trace.getTracer('default').startSpan('Exception');
span.recordException(x);
pan.end();

It looks like that I can't get the span for the actual fetch call or is there something else?

P.s. I have the same issue if I try to set an attribute to the active span within catch section.

Please help!

t2t2 commented 2 months ago

When I do an fetch and I get e.g. 400 back and I catch the exception

Fetch API itself doesn't throw on 4xx/5xx, this is one of the many DX convenience things axios does (which yeah makes a lot of sense when actually creating something, but fetch is just a lower level API). So otel's instrumentation is correct in this as in there's no error (fetch only throws errors when there's something like a network issue, domain name didn't resolve, ...)

It looks like that I can't get the span for the actual fetch call or is there something else?

P.s. I have the same issue if I try to set an attribute to the active span within catch section.

Yeah generally by the time your code can process the response the span has already ended since the span represents the raw http call. If you want to do more, your options are:

  1. You can use applyCustomAttributesOnSpan option on fetch (& xhr) instrumentation(s). Note that you are limited to fields on the Response object, parsing the body would be an async operation and the span's ended by then

https://github.com/open-telemetry/opentelemetry-js/blob/5c1ae0aa5b686aae3e7511f1eb9cfcc4d7ab2326/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts#L74

  1. You can create a span out of the getEmail function itself image
import {trace, context} from '@opentelemetry/api';

const tracer = trace.getTracer('my-application', '1.0.0');

public function getEmail(email: string): Promise<IAccountModel> {
  const span = tracer.startSpan('getEmail');
  return context.with(trace.setSpan(context.active(), span), async () => {
    try {
      const { data } = await axios.get<IAccountModel>(this.backEndUrl + '/api/GetUserEmail?email=' + email, {
        withCredentials: true,
      });
      // do stuff with span & data
      return data;
    } catch (err) {
      // do stuff with span & err
      throw err;
    } finally {
      span.end();
    }
  });
}
pichlermarc commented 1 month ago

Thanks for answering this @t2t2. This is correct, there's no exception to record here as fetch does not throw. Closing this issue.