Skip to content

Sharing context between network instrumentations and resource timing instrumentation #312

Description

@joaquin-diaz

Sharing OTel context between network and resource timing instrumentations

Background

This repo currently has (or will have) two complementary instrumentations:

  • Network instrumentation (fetch/XHR): monkey-patches fetch and XMLHttpRequest to create OTel CLIENT spans for each network request.
  • Resource timing instrumentation: observes PerformanceResourceTiming entries via PerformanceObserver and emits a browser.resource_timing log for each one, capturing browser-level timing data (DNS, TCP, TLS, queue time, etc.).

The resource timing instrumentation captures entries for all resource types — scripts, stylesheets, images, and also network requests made via fetch/XHR. This means that for every fetch/XHR call there is both a span (from the network instrumentation) and a resource timing log (from the resource timing instrumentation).

Problem

Today, browser.resource_timing logs emitted for fetch/XHR-originated entries carry no trace context. This makes it impossible to correlate them with the corresponding network span in the backend without additional client-side tooling.

Ideally, these logs should carry the traceId and spanId of the fetch/XHR span that triggered them.

Proposed solution

Separation of concerns

The network instrumentation should not observe PerformanceResourceTiming entries on its own — the resource timing instrumentation is already the right place for that. Instead, we can share context between the two using a lightweight coordination layer.

NetworkContextManager

A new shared utility class — NetworkContextManager — acts as the bridge:

  • The network instrumentation fires a generic callback (onNetworkSpanEnd) after a span ends, passing the completed span and the request URL. It has no direct dependency on NetworkContextManager.
  • The NetworkContextManager is wired to this callback. It stores the span context indexed by URL and timing window (derived from span start/end times), so that concurrent requests to the same URL can be distinguished.
  • The resource timing instrumentation exposes a new applyCustomLogRecordData hook (consistent with the pattern already used by navigation and web vitals instrumentations). The hook receives both the mutable log record and the PerformanceResourceTiming entry, allowing the caller to set the log's OTel context before emission.

Wiring

Users (or SDK wrappers) connect the two instrumentations via NetworkContextManager:

const networkCtxManager = new NetworkContextManager();

const fetchInstr = new FetchInstrumentation({
  onNetworkSpanEnd: (span, url) => networkCtxManager.register(span, url),
});

const resourceTimingInstr = new ResourceTimingInstrumentation({
  applyCustomLogRecordData: (logRecord, entry) => {
    logRecord.context = networkCtxManager.getContext(entry);
  },
});

The same NetworkContextManager will work for the XHR instrumentation when it lands, using the same onNetworkSpanEnd hook convention.

Changes required

Network instrumentation (fetch / future XHR)

  • Remove the per-request PerformanceObserver and resource timing matching logic — this is no longer the instrumentation's responsibility.
  • Add an onNetworkSpanEnd?: (span: Span, url: string) => void config option, called after a span ends.

Resource timing instrumentation

  • Add an applyCustomLogRecordData?: (logRecord, entry: PerformanceResourceTiming) => void config option, called before each browser.resource_timing log is emitted.

New: NetworkContextManager

  • Exported from the package so SDK wrappers and end users can use it.

Benefits

  • Clean separation of concerns: each instrumentation does exactly one job.
  • No tight coupling: the network instrumentation doesn't import or know about NetworkContextManager; it just fires a generic callback.
  • Extensible: applyCustomLogRecordData is a general-purpose hook that serves other use cases beyond context sharing (e.g. adding custom attributes).
  • XHR-ready: the same pattern applies to the XHR instrumentation with no changes to the resource timing side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions