Skip to content

Using ZoneContextManager and FetchInstrumentation in browser causes lots of leaked Promises #3030

@jwatte

Description

@jwatte

What version of OpenTelemetry are you using?

"@opentelemetry/api": "^1.1.0",
"@opentelemetry/context-zone": "^1.3.0",
"@opentelemetry/core": "^1.3.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.29.1",
"@opentelemetry/instrumentation-fetch": "^0.29.1",
"@opentelemetry/sdk-trace-base": "^1.3.0",
"@opentelemetry/semantic-conventions": "^1.3.0",

What version of Node are you using?

Not using Node, using Chrome Version 102.0.5005.63 (Official Build) (64-bit) on Windows 10.

Please provide the code you used to setup the OpenTelemetry SDK

See below.

What did you do?

This is in a React app.

Instantiate a tracer and an exporter, with ZoneContextManager, and add the FetchInstrumentation plugin.

What did you expect to see?

Fetch spans and traces.

What did you see instead?

Fetch spans and traces, and Promise leaks that pretty quickly grinds the process to a halt.

Additional context

How I instantiate the SDK:

import Logger from "../../../../common/src/client/Logger";
import {SamplingDecision} from "@opentelemetry/api";
import {Resource, ResourceAttributes} from "@opentelemetry/resources";
import {SemanticResourceAttributes} from "@opentelemetry/semantic-conventions";
import {
    ConsoleSpanExporter,
    BatchSpanProcessor,
    BufferConfig,
    SpanExporter,
} from "@opentelemetry/sdk-trace-base";
import {WebTracerConfig, WebTracerProvider} from "@opentelemetry/sdk-trace-web";
import {ZoneContextManager} from "@opentelemetry/context-zone";
import {registerInstrumentations} from "@opentelemetry/instrumentation";
import {FetchInstrumentation} from "@opentelemetry/instrumentation-fetch";
import {OTLPTraceExporter} from "@opentelemetry/exporter-trace-otlp-http";

export interface IWebTracerOptions {
    //  disable open telemetry tracing
    disable?: boolean;

    //  annotations that go on the root spans
    rootAnnotations?: ResourceAttributes;

    //  when tracing, configure parameters here
    customerId: string;
    accessToken: string;
    tracingEndpoint?: string;

    //  override exporter, for unit testing
    exporter?: SpanExporter;
    processorOpts?: BufferConfig;
}

let provider: WebTracerProvider;

export function startOpenTelemetry(opts?: IWebTracerOptions): WebTracerProvider {
    if (provider !== undefined) {
        Logger.info("OpenTelemetry already started");
        return provider;
    }

    //  Figure out whether to disable collection for this session.
    //  We want to sample either everything in a given session, or nothing.
    //  Additionally, when disabled, tell the tracer to sample nothing.
    let disable: boolean = opts?.disable || false;

    Logger.info("Initializing OpenTelemetry");
    const tracerConfig: WebTracerConfig = {};
    const rootAnnotations: ResourceAttributes = {
        [SemanticResourceAttributes.SERVICE_NAME]: "someapp",
        [SemanticResourceAttributes.SERVICE_NAMESPACE]: "somename",
        [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: "someid",
        [SemanticResourceAttributes.SERVICE_VERSION]: "0.0.0",
    };
    if (opts?.rootAnnotations) {
        for (const [k, v] of Object.entries(opts.rootAnnotations)) {
            rootAnnotations[k] = v;
        }
    }
    tracerConfig.resource = new Resource(rootAnnotations);
    if (disable) {
        tracerConfig.sampler = {
            shouldSample: () => {
                return {decision: SamplingDecision.NOT_RECORD};
            },
            toString: () => "discarding sampler",
        };
    }

    //  we always allocate a provider, but it may not be doing much if we're disabled
    provider = new WebTracerProvider(tracerConfig);

    if (!disable) {
        const exporter: SpanExporter = chooseExporter(opts);
        const processorOpts: BufferConfig = opts?.processorOpts || {
            maxExportBatchSize: 100,
            scheduledDelayMillis: 500,
            exportTimeoutMillis: 20000,
            maxQueueSize: 1000,
        };
        provider.addSpanProcessor(new BatchSpanProcessor(exporter, processorOpts));
        provider.register({
            contextManager: new ZoneContextManager(),
        });
        const fetchInstrumentation = new FetchInstrumentation({
            ignoreUrls: [/.*collect\.service.*/],
        });
        // Registering instrumentations / plugins
        registerInstrumentations({
            instrumentations: [fetchInstrumentation],
        });
        Logger.info("Started OpenTelemetry");
    } else {
        Logger.info("OpenTelemetry is disabled");
    }

    return provider;
}

function chooseExporter(opts?: IWebTracerOptions): SpanExporter {
    let exporter: SpanExporter;
    if (opts?.exporter) {
        Logger.info("Using provided SpanExporter");
        exporter = opts.exporter;
    } else if (opts?.tracingEndpoint && opts?.customerId && opts?.accessToken) {
        Logger.info(`Using OTLPTraceExporter to ${opts.tracingEndpoint}`);
        const cid = opts.customerId;
        const authtoken = opts.accessToken;
        exporter = new OTLPTraceExporter({
            url: opts.tracingEndpoint,
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${cid} ${authtoken}`,
            },
            concurrencyLimit: 3,
        });
    } else {
        Logger.info("Using ConsoleSpanExporter");
        exporter = new ConsoleSpanExporter();
    }
    return exporter;
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinginformation-requestedBug is waiting on additional information from the userpkg:context-zonepriority:p1Bugs which cause problems in end-user applications such as crashes, data inconsistencies, etc

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions