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.
Sharing OTel context between network and resource timing instrumentations
Background
This repo currently has (or will have) two complementary instrumentations:
fetchandXMLHttpRequestto create OTel CLIENT spans for each network request.PerformanceResourceTimingentries viaPerformanceObserverand emits abrowser.resource_timinglog 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_timinglogs 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
traceIdandspanIdof the fetch/XHR span that triggered them.Proposed solution
Separation of concerns
The network instrumentation should not observe
PerformanceResourceTimingentries 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:onNetworkSpanEnd) after a span ends, passing the completed span and the request URL. It has no direct dependency onNetworkContextManager.NetworkContextManageris 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.applyCustomLogRecordDatahook (consistent with the pattern already used by navigation and web vitals instrumentations). The hook receives both the mutable log record and thePerformanceResourceTimingentry, allowing the caller to set the log's OTel context before emission.Wiring
Users (or SDK wrappers) connect the two instrumentations via
NetworkContextManager:The same
NetworkContextManagerwill work for the XHR instrumentation when it lands, using the sameonNetworkSpanEndhook convention.Changes required
Network instrumentation (fetch / future XHR)
PerformanceObserverand resource timing matching logic — this is no longer the instrumentation's responsibility.onNetworkSpanEnd?: (span: Span, url: string) => voidconfig option, called after a span ends.Resource timing instrumentation
applyCustomLogRecordData?: (logRecord, entry: PerformanceResourceTiming) => voidconfig option, called before eachbrowser.resource_timinglog is emitted.New: NetworkContextManager
Benefits
NetworkContextManager; it just fires a generic callback.applyCustomLogRecordDatais a general-purpose hook that serves other use cases beyond context sharing (e.g. adding custom attributes).