Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions packages/browser/src/tracing/request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable max-lines */
import type {
Client,
HandlerDataFetch,
HandlerDataXhr,
RequestHookInfo,
ResponseHookInfo,
Expand Down Expand Up @@ -124,7 +125,7 @@ export interface RequestInstrumentationOptions {
}

const responseToSpanId = new WeakMap<object, string>();
const spanIdToEndTimestamp = new Map<string, number>();
const spanIdToDeferredData = new Map<string, { span: Span; handlerData: HandlerDataFetch }>();

export const defaultRequestInstrumentationOptions: RequestInstrumentationOptions = {
traceFetch: true,
Expand Down Expand Up @@ -159,44 +160,46 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial<Re
const propagateTraceparent = (client as BrowserClient).getOptions().propagateTraceparent;

if (traceFetch) {
// Keeping track of http requests, whose body payloads resolved later than the initial resolved request
// e.g. streaming using server sent events (SSE)
client.addEventProcessor(event => {
if (event.type === 'transaction' && event.spans) {
event.spans.forEach(span => {
if (span.op === 'http.client') {
const updatedTimestamp = spanIdToEndTimestamp.get(span.span_id);
if (updatedTimestamp) {
span.timestamp = updatedTimestamp / 1000;
spanIdToEndTimestamp.delete(span.span_id);
}
}
});
}
return event;
});

if (trackFetchStreamPerformance) {
addFetchEndInstrumentationHandler(handlerData => {
if (handlerData.response) {
const span = responseToSpanId.get(handlerData.response);
if (span && handlerData.endTimestamp) {
spanIdToEndTimestamp.set(span, handlerData.endTimestamp);
const spanId = responseToSpanId.get(handlerData.response);
if (spanId) {
const deferred = spanIdToDeferredData.get(spanId);
if (deferred && handlerData.endTimestamp) {
spans[spanId] = deferred.span;
deferred.handlerData.endTimestamp = handlerData.endTimestamp;
instrumentFetchRequest(deferred.handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans, {
propagateTraceparent,
onRequestSpanEnd,
});
spanIdToDeferredData.delete(spanId);
}
}
}
});
}

addFetchInstrumentationHandler(handlerData => {
// When tracking streaming performance, defer span end until the response body resolves.
// We intercept the end call, save the span, and let the fetchEndInstrumentationHandler
// end it with the correct timestamp.
if (trackFetchStreamPerformance && handlerData.endTimestamp && handlerData.response) {
const spanId = handlerData.fetchData?.__span;
if (spanId && spans[spanId]) {
responseToSpanId.set(handlerData.response, spanId);
spanIdToDeferredData.set(spanId, { span: spans[spanId], handlerData });
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete spans[spanId];
return;
}
}

const createdSpan = instrumentFetchRequest(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans, {
propagateTraceparent,
onRequestSpanEnd,
});

if (handlerData.response && handlerData.fetchData.__span) {
responseToSpanId.set(handlerData.response, handlerData.fetchData.__span);
}

// We cannot use `window.location` in the generic fetch instrumentation,
// but we need it for reliable `server.address` attribute.
// so we extend this in here
Expand Down
5 changes: 0 additions & 5 deletions packages/browser/test/tracing/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ beforeAll(() => {
});

class MockClient implements Partial<Client> {
public addEventProcessor: () => void;
constructor() {
// Mock addEventProcessor function
this.addEventProcessor = vi.fn();
}
// @ts-expect-error not returning options for the test
public getOptions() {
return {};
Expand Down
Loading