Skip to content

Commit

Permalink
[RUMF-824] support Request instances in tracing (DataDog#684)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenoitZugmeyer authored and kcaffrey committed Jan 29, 2021
1 parent e6861b8 commit 3b3c12a
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 44 deletions.
4 changes: 3 additions & 1 deletion packages/core/src/browser/fetchProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface FetchProxy<
export interface FetchStartContext {
method: string
startTime: number
input: RequestInfo
init?: RequestInit
url: string

Expand Down Expand Up @@ -77,6 +78,7 @@ function proxyFetch() {

const context: FetchStartContext = {
init,
input,
method,
startTime,
url,
Expand Down Expand Up @@ -106,7 +108,7 @@ function proxyFetch() {
}
beforeSendCallbacks.forEach((callback) => callback(context))

const responsePromise = originalFetch.call(this, input, context.init)
const responsePromise = originalFetch.call(this, context.input, context.init)
responsePromise.then(monitor(reportFetch), monitor(reportFetch))
return responsePromise
})
Expand Down
16 changes: 7 additions & 9 deletions packages/core/src/tools/specHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export function stubFetch(): FetchStubManager {
resolve = res
reject = rej
})
;(promise as FetchStubPromise).resolveWith = async (response: ResponseStub) => {
const resolved = resolve({
;(promise as FetchStubPromise).resolveWith = (response: ResponseStub) => {
resolve({
...response,
clone: () => {
const cloned = {
Expand All @@ -75,14 +75,12 @@ export function stubFetch(): FetchStubManager {
}
return cloned as Response
},
}) as Promise<ResponseStub>
})
onRequestEnd()
return resolved
}
;(promise as FetchStubPromise).rejectWith = async (error: Error) => {
const rejected = reject(error) as Promise<Error>
;(promise as FetchStubPromise).rejectWith = (error: Error) => {
reject(error)
onRequestEnd()
return rejected
}
return promise
}) as typeof window.fetch
Expand All @@ -106,8 +104,8 @@ export interface ResponseStub extends Partial<Response> {
export type FetchStub = (input: RequestInfo, init?: RequestInit) => FetchStubPromise

export interface FetchStubPromise extends Promise<Response> {
resolveWith: (response: ResponseStub) => Promise<ResponseStub>
rejectWith: (error: Error) => Promise<Error>
resolveWith: (response: ResponseStub) => void
rejectWith: (error: Error) => void
}

class StubXhr {
Expand Down
52 changes: 51 additions & 1 deletion packages/rum-core/src/domain/tracing/tracer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,48 @@ describe('tracer', () => {
])
})

it('should preserve original headers contained in a Request instance', () => {
const request = new Request(document.location.origin, {
headers: {
foo: 'bar',
},
})

const context: Partial<RumFetchCompleteContext> = {
...ALLOWED_DOMAIN_CONTEXT,
input: request,
}

const tracer = startTracer(configuration as Configuration)
tracer.traceFetch(context)

expect(context.init).toBe(undefined)
expect(context.input).not.toBe(request)
expect(headersAsArray((context.input as Request).headers)).toEqual([
['foo', 'bar'],
...tracingHeadersAsArrayFor(context.traceId!, context.spanId!),
])
expect(headersAsArray(request.headers)).toEqual([['foo', 'bar']])
})

it('should ignore headers from a Request instance if other headers are set', () => {
const context: Partial<RumFetchCompleteContext> = {
...ALLOWED_DOMAIN_CONTEXT,
init: { headers: { 'x-init-header': 'baz' } },
input: new Request(document.location.origin, {
headers: { 'x-request-header': 'bar' },
}),
}

const tracer = startTracer(configuration as Configuration)
tracer.traceFetch(context)

expect(context.init!.headers).toEqual([
['x-init-header', 'baz'],
...tracingHeadersAsArrayFor(context.traceId!, context.spanId!),
])
})

it('should not trace request on disallowed domain', () => {
const context: Partial<RumFetchCompleteContext> = { ...DISALLOWED_DOMAIN_CONTEXT }

Expand Down Expand Up @@ -277,5 +319,13 @@ function tracingHeadersFor(traceId: TraceIdentifier, spanId: TraceIdentifier) {
}

function tracingHeadersAsArrayFor(traceId: TraceIdentifier, spanId: TraceIdentifier) {
return objectEntries(tracingHeadersFor(traceId, spanId)) as string[][]
return objectEntries(tracingHeadersFor(traceId, spanId)) as Array<[string, string]>
}

function headersAsArray(headers: Headers) {
const result: Array<[string, string]> = []
headers.forEach((value, key) => {
result.push([key, value])
})
return result
}
35 changes: 21 additions & 14 deletions packages/rum-core/src/domain/tracing/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,29 @@ export function startTracer(configuration: Configuration): Tracer {
clearTracingIfCancelled,
traceFetch: (context) =>
injectHeadersIfTracingAllowed(configuration, context, (tracingHeaders: TracingHeaders) => {
context.init = { ...context.init }
const headers: string[][] = []
if (context.init.headers instanceof Headers) {
context.init.headers.forEach((value, key) => {
headers.push([key, value])
})
} else if (Array.isArray(context.init.headers)) {
context.init.headers.forEach((header) => {
headers.push(header)
})
} else if (context.init.headers) {
Object.keys(context.init.headers).forEach((key) => {
headers.push([key, (context.init!.headers as Record<string, string>)[key]])
if (context.input instanceof Request && !context.init?.headers) {
context.input = new Request(context.input)
Object.keys(tracingHeaders).forEach((key) => {
;(context.input as Request).headers.append(key, tracingHeaders[key])
})
} else {
context.init = { ...context.init }
const headers: string[][] = []
if (context.init.headers instanceof Headers) {
context.init.headers.forEach((value, key) => {
headers.push([key, value])
})
} else if (Array.isArray(context.init.headers)) {
context.init.headers.forEach((header) => {
headers.push(header)
})
} else if (context.init.headers) {
Object.keys(context.init.headers).forEach((key) => {
headers.push([key, (context.init!.headers as Record<string, string>)[key]])
})
}
context.init.headers = headers.concat(objectEntries(tracingHeaders) as string[][])
}
context.init.headers = headers.concat(objectEntries(tracingHeaders) as string[][])
}),
traceXhr: (context, xhr) =>
injectHeadersIfTracingAllowed(configuration, context, (tracingHeaders: TracingHeaders) => {
Expand Down
14 changes: 0 additions & 14 deletions test/e2e/lib/helpers/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,3 @@ export async function sendXhr(url: string, headers: string[][] = []): Promise<st
}
return result.response
}

export async function sendFetch(url: string, headers: string[][] = []): Promise<string> {
return browserExecuteAsync(
// tslint:disable-next-line: no-shadowed-variable
(url, headers, done) => {
window
.fetch(url, { headers })
.then((response) => response.text())
.then(done)
},
url,
headers
)
}
31 changes: 26 additions & 5 deletions test/e2e/scenario/rum/tracing.scenario.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createTest, EventRegistry } from '../../lib/framework'
import { sendFetch, sendXhr } from '../../lib/helpers/browser'
import { browserExecuteAsync, sendXhr } from '../../lib/helpers/browser'
import { flushEvents } from '../../lib/helpers/sdk'

describe('tracing', () => {
Expand All @@ -18,10 +18,31 @@ describe('tracing', () => {
createTest('trace fetch')
.withRum({ service: 'Service', allowedTracingOrigins: ['LOCATION_ORIGIN'] })
.run(async ({ events }) => {
const rawHeaders = await sendFetch(`/headers`, [
['x-foo', 'bar'],
['x-foo', 'baz'],
])
const rawHeaders = await browserExecuteAsync<string>((done) => {
window
.fetch('/headers', {
headers: [
['x-foo', 'bar'],
['x-foo', 'baz'],
],
})
.then((response) => response.text())
.then(done)
})
checkRequestHeaders(rawHeaders)
await flushEvents()
await checkTraceAssociatedToRumEvent(events)
})

createTest('trace fetch with Request argument')
.withRum({ service: 'Service', allowedTracingOrigins: ['LOCATION_ORIGIN'] })
.run(async ({ events }) => {
const rawHeaders = await browserExecuteAsync<string>((done) => {
window
.fetch(new Request('/headers', { headers: { 'x-foo': 'bar, baz' } }))
.then((response) => response.text())
.then(done)
})
checkRequestHeaders(rawHeaders)
await flushEvents()
await checkTraceAssociatedToRumEvent(events)
Expand Down

0 comments on commit 3b3c12a

Please sign in to comment.