diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts b/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts index cf8b97b6986..03819704621 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts @@ -55,6 +55,7 @@ import { import { FetchError, FetchResponse, SpanData } from './types'; import { getFetchBodyLength, + isRequest, normalizeHttpRequestMethod, serverPortFromUrl, } from './utils'; @@ -215,7 +216,7 @@ export class FetchInstrumentation extends InstrumentationBase h.set(k, typeof v === 'string' ? v : String(v)), }); @@ -395,10 +396,10 @@ export class FetchInstrumentation extends InstrumentationBase { const self = this; const url = web.parseUrl( - args[0] instanceof Request ? args[0].url : String(args[0]) + isRequest(args[0]) ? args[0].url : String(args[0]) ).href; - const options = args[0] instanceof Request ? args[0] : args[1] || {}; + const options = isRequest(args[0]) ? args[0] : args[1] || {}; const createdSpan = plugin._createSpan(url, options); if (!createdSpan) { return original.apply(this, args); @@ -568,7 +569,7 @@ export class FetchInstrumentation extends InstrumentationBase) { } else { return Promise.resolve(getXHRBodyLength(requestInit.body)); } - } else { + } else if (isRequest(args[0])) { const info = args[0]; - if (!info?.body) { + if (!info.body) { return Promise.resolve(); } @@ -77,6 +104,8 @@ export function getFetchBodyLength(...args: Parameters) { .clone() .text() .then(t => getByteLength(t)); + } else { + return Promise.resolve(); } } @@ -214,7 +243,7 @@ function getKnownMethods() { ); if (cfgMethods && cfgMethods.length > 0) { knownMethods = {}; - cfgMethods.forEach(m => { + cfgMethods.forEach((m: string) => { knownMethods[m] = true; }); } else { diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts index e75bbf0b1f3..a40f6ce634c 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts @@ -42,6 +42,7 @@ import { FetchInstrumentation, FetchInstrumentationConfig, } from '../src'; +import { isRequest as isRequestLike } from '../src/utils'; import { AttributeNames } from '../src/enums/AttributeNames'; import { ATTR_HTTP_HOST, @@ -1750,7 +1751,7 @@ describe('fetch', () => { config: { requestHook: (span, request) => { assert.ok( - request instanceof Request, + isRequestLike(request), '`requestHook` should get the `Request` object passed to `fetch()`' ); @@ -1779,6 +1780,53 @@ describe('fetch', () => { ); }); + it('treats a `Request` argument as a request even if global Request is overwritten', async () => { + const OriginalRequest = globalThis.Request; + + try { + (globalThis as any).Request = function Request() {}; + + const { response } = await tracedFetch({ + config: { + requestHook: (span, request) => { + assert.ok( + isRequestLike(request), + '`requestHook` should get the `Request` object passed to `fetch()`' + ); + + assert.ok( + !((request as any) instanceof Request), + 'sanity check: `instanceof Request` should fail when global Request is overwritten' + ); + + (request as Request).headers.set('custom-foo', 'foo'); + }, + }, + callback: () => + fetch( + new OriginalRequest('/api/echo-headers.json', { + headers: new Headers({ 'custom-bar': 'bar' }), + }) + ), + }); + + const { request } = await response.json(); + + assert.strictEqual( + request.headers['custom-foo'], + 'foo', + 'header set from requestHook should be sent' + ); + assert.strictEqual( + request.headers['custom-bar'], + 'bar', + 'header set from fetch() should be sent' + ); + } finally { + globalThis.Request = OriginalRequest; + } + }); + it('can modify headers when called with a `RequestInit` object', async () => { const { response } = await tracedFetch({ config: {