From 6b044f7bb70eeca6fe4714d218b69fe14e8301ca Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Thu, 4 Dec 2025 12:41:16 -0500 Subject: [PATCH 1/9] feat: new approach for fetch instrumentation --- packages/otel/src/instrumentations/fetch.ts | 221 ++++++++++++++------ 1 file changed, 154 insertions(+), 67 deletions(-) diff --git a/packages/otel/src/instrumentations/fetch.ts b/packages/otel/src/instrumentations/fetch.ts index 33c592a97..0ca50b4d4 100644 --- a/packages/otel/src/instrumentations/fetch.ts +++ b/packages/otel/src/instrumentations/fetch.ts @@ -1,3 +1,5 @@ +import * as diagnosticsChannel from 'diagnostics_channel' + import * as api from '@opentelemetry/api' import { SugaredTracer } from '@opentelemetry/api/experimental' import { _globalThis } from '@opentelemetry/core' @@ -14,10 +16,12 @@ export interface FetchInstrumentationConfig extends InstrumentationConfig { export class FetchInstrumentation implements Instrumentation { instrumentationName = '@netlify/otel/instrumentation-fetch' instrumentationVersion = '1.0.0' - private originalFetch: typeof fetch | null = null private config: FetchInstrumentationConfig private provider?: api.TracerProvider + declare private _channelSubs: ListenerRecord[] + private _recordFromReq = new WeakMap() + constructor(config: FetchInstrumentationConfig = {}) { this.config = config } @@ -36,60 +40,50 @@ export class FetchInstrumentation implements Instrumentation { return this.provider } - private annotateFromRequest(span: api.Span, request: Request): void { - const extras = this.config.getRequestAttributes?.(request) ?? {} - const url = new URL(request.url) + private annotateFromRequest(span: api.Span, request: UndiciRequest): void { + // const extras = this.config.getRequestAttributes?.(request) ?? {} + const url = new URL(request.path, request.origin) + // these are based on @opentelemetry/semantic-convention 1.36 span.setAttributes({ - ...extras, + // ...extras, 'http.request.method': request.method, 'url.full': url.href, 'url.host': url.host, 'url.scheme': url.protocol.slice(0, -1), 'server.address': url.hostname, 'server.port': url.port, - ...this.prepareHeaders('request', request.headers), }) } - private annotateFromResponse(span: api.Span, response: Response): void { - const extras = this.config.getResponseAttributes?.(response) ?? {} + private annotateFromResponse(span: api.Span, response: UndiciResponse): void { + // const extras = this.config.getResponseAttributes?.(response) ?? {} // these are based on @opentelemetry/semantic-convention 1.36 span.setAttributes({ - ...extras, - 'http.response.status_code': response.status, - ...this.prepareHeaders('response', response.headers), + // ...extras, + 'http.response.status_code': response.statusCode, + 'http.response.header.content-length': this.getContentLength(response), + }) + + span.setStatus({ + code: response.statusCode >= 400 ? api.SpanStatusCode.ERROR : api.SpanStatusCode.UNSET, }) - span.setStatus({ code: response.status >= 400 ? api.SpanStatusCode.ERROR : api.SpanStatusCode.UNSET }) } - private prepareHeaders(type: 'request' | 'response', headers: Headers): api.Attributes { - if (this.config.skipHeaders === true) { - return {} - } - const everything = ['*', '/.*/'] - const skips = this.config.skipHeaders ?? [] - const redacts = this.config.redactHeaders ?? [] - const everythingSkipped = skips.some((skip) => everything.includes(skip.toString())) - const attributes: api.Attributes = {} - if (everythingSkipped) return attributes - const entries = headers.entries() - for (const [key, value] of entries) { - if (skips.some((skip) => (typeof skip == 'string' ? skip == key : skip.test(key)))) { - continue - } - const attributeKey = `http.${type}.header.${key}` - if ( - redacts === true || - redacts.some((redact) => (typeof redact == 'string' ? redact == key : redact.test(key))) - ) { - attributes[attributeKey] = 'REDACTED' - } else { - attributes[attributeKey] = value - } + private getContentLength(response: UndiciResponse) { + for (let idx = 0; idx < response.headers.length; idx = idx + 2) { + const name = response.headers[idx].toString().toLowerCase() + + if (name !== 'content-length') continue + + const value = response.headers[idx + 1] + const contentLength = Number(value.toString()) + + if (!isNaN(contentLength)) return contentLength } - return attributes + + return undefined } private getTracer(): SugaredTracer | undefined { @@ -105,39 +99,132 @@ export class FetchInstrumentation implements Instrumentation { return new SugaredTracer(tracer) } - /** - * patch global fetch - */ enable(): void { - const originalFetch = _globalThis.fetch - this.originalFetch = originalFetch - _globalThis.fetch = async (resource: RequestInfo | URL, options?: RequestInit): Promise => { - const url = typeof resource === 'string' ? resource : resource instanceof URL ? resource.href : resource.url - const tracer = this.getTracer() - if ( - !tracer || - this.config.skipURLs?.some((skip) => (typeof skip == 'string' ? url.startsWith(skip) : skip.test(url))) - ) { - return await originalFetch(resource, options) - } - - return tracer.withActiveSpan('fetch', async (span) => { - const request = new Request(resource, options) - this.annotateFromRequest(span, request) - const response = await originalFetch(request, options) - this.annotateFromResponse(span, response) - return response - }) - } + // https://undici.nodejs.org/#/docs/api/DiagnosticsChannel?id=diagnostics-channel-support + this.subscribe('undici:request:create', this.onRequestCreated.bind(this)) + this.subscribe('undici:request:headers', this.onResponseHeaders.bind(this)) + this.subscribe('undici:request:trailers', this.onDone.bind(this)) + this.subscribe('undici:request:error', this.onError.bind(this)) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private subscribe(channelName: string, onMessage: (message: any, name: string | symbol) => void) { + diagnosticsChannel.subscribe(channelName, onMessage) + + const unsubscribe = () => diagnosticsChannel.unsubscribe(channelName, onMessage) + this._channelSubs.push({ name: channelName, unsubscribe }) + } + + disable() { + this._channelSubs.forEach((sub) => { + sub.unsubscribe() + }) + this._channelSubs.length = 0 + } + + private onRequestCreated({ request }: RequestMessage): void { + const tracer = this.getTracer() + if (!tracer) return + + const activeCtx = api.context.active() + + if (request.method === 'CONNECT') return + + const span = tracer.startSpan( + // TODO - filter down request methods + request.method, + { + kind: api.SpanKind.CLIENT, + }, + activeCtx, + ) + + this.annotateFromRequest(span, request) + + this._recordFromReq.set(request, span) + } + + private onResponseHeaders({ request, response }: ResponseHeadersMessage): void { + const span = this._recordFromReq.get(request) + + if (!span) return + + this.annotateFromResponse(span, response) + } + + private onError({ request, error }: RequestErrorMessage): void { + const span = this._recordFromReq.get(request) + if (!span) return + + span.recordException(error) + span.setStatus({ + code: api.SpanStatusCode.ERROR, + message: error.message, + }) + + span.end() + this._recordFromReq.delete(request) } + private onDone({ request }: RequestTrailersMessage): void { + const span = this._recordFromReq.get(request) + + if (!span) return + + span.end() + this._recordFromReq.delete(request) + } +} + +interface ListenerRecord { + name: string + unsubscribe: () => void +} + +interface RequestMessage { + request: UndiciRequest +} + +interface ResponseHeadersMessage { + request: UndiciRequest + response: UndiciResponse +} + +interface RequestErrorMessage { + request: UndiciRequest + error: Error +} + +interface RequestTrailersMessage { + request: UndiciRequest + response: UndiciResponse +} + +interface UndiciRequest { + origin: string + method: string + path: string /** - * unpatch global fetch + * Serialized string of headers in the form `name: value\r\n` for v5 + * Array of strings `[key1, value1, key2, value2]`, where values are + * `string | string[]` for v6 */ - disable(): void { - if (this.originalFetch) { - _globalThis.fetch = this.originalFetch - this.originalFetch = null - } - } + headers: string | (string | string[])[] + /** + * Helper method to add headers (from v6) + */ + addHeader: (name: string, value: string) => void + throwOnError: boolean + completed: boolean + aborted: boolean + idempotent: boolean + contentLength: number | null + contentType: string | null + body: unknown +} + +interface UndiciResponse { + headers: Buffer[] + statusCode: number + statusText: string } From 8f5201673a7c7c544859247faae0ae1578939d3c Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Thu, 4 Dec 2025 17:36:31 -0500 Subject: [PATCH 2/9] fix: restore extras and prepareHeaders --- packages/otel/src/instrumentations/fetch.ts | 123 ++++++++++---------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/packages/otel/src/instrumentations/fetch.ts b/packages/otel/src/instrumentations/fetch.ts index 0ca50b4d4..3fb2cd46a 100644 --- a/packages/otel/src/instrumentations/fetch.ts +++ b/packages/otel/src/instrumentations/fetch.ts @@ -6,8 +6,8 @@ import { _globalThis } from '@opentelemetry/core' import { InstrumentationConfig, type Instrumentation } from '@opentelemetry/instrumentation' export interface FetchInstrumentationConfig extends InstrumentationConfig { - getRequestAttributes?(headers: Request): api.Attributes - getResponseAttributes?(response: Response): api.Attributes + getRequestAttributes?(request: FetchRequest): api.Attributes + getResponseAttributes?(response: FetchResponse): api.Attributes skipURLs?: (string | RegExp)[] skipHeaders?: (string | RegExp)[] | true redactHeaders?: (string | RegExp)[] | true @@ -20,7 +20,7 @@ export class FetchInstrumentation implements Instrumentation { private provider?: api.TracerProvider declare private _channelSubs: ListenerRecord[] - private _recordFromReq = new WeakMap() + private _recordFromReq = new WeakMap() constructor(config: FetchInstrumentationConfig = {}) { this.config = config @@ -40,30 +40,31 @@ export class FetchInstrumentation implements Instrumentation { return this.provider } - private annotateFromRequest(span: api.Span, request: UndiciRequest): void { - // const extras = this.config.getRequestAttributes?.(request) ?? {} + private annotateFromRequest(span: api.Span, request: FetchRequest): void { + const extras = this.config.getRequestAttributes?.(request) ?? {} const url = new URL(request.path, request.origin) // these are based on @opentelemetry/semantic-convention 1.36 span.setAttributes({ - // ...extras, + ...extras, 'http.request.method': request.method, 'url.full': url.href, 'url.host': url.host, 'url.scheme': url.protocol.slice(0, -1), 'server.address': url.hostname, 'server.port': url.port, + ...this.prepareHeaders('request', request.headers), }) } - private annotateFromResponse(span: api.Span, response: UndiciResponse): void { - // const extras = this.config.getResponseAttributes?.(response) ?? {} + private annotateFromResponse(span: api.Span, response: FetchResponse): void { + const extras = this.config.getResponseAttributes?.(response) ?? {} // these are based on @opentelemetry/semantic-convention 1.36 span.setAttributes({ - // ...extras, + ...extras, 'http.response.status_code': response.statusCode, - 'http.response.header.content-length': this.getContentLength(response), + ...this.prepareHeaders('response', response.headers), }) span.setStatus({ @@ -71,19 +72,46 @@ export class FetchInstrumentation implements Instrumentation { }) } - private getContentLength(response: UndiciResponse) { - for (let idx = 0; idx < response.headers.length; idx = idx + 2) { - const name = response.headers[idx].toString().toLowerCase() - - if (name !== 'content-length') continue + private prepareHeaders( + type: 'request' | 'response', + headers: FetchRequest['headers'] | FetchResponse['headers'], + ): api.Attributes { + if (this.config.skipHeaders === true) { + return {} + } + const everything = ['*', '/.*/'] + const skips = this.config.skipHeaders ?? [] + const redacts = this.config.redactHeaders ?? [] + const everythingSkipped = skips.some((skip) => everything.includes(skip.toString())) + const attributes: api.Attributes = {} + if (everythingSkipped) return attributes + for (let idx = 0; idx < headers.length; idx = idx + 2) { + const key = headers[idx].toString().toLowerCase() + const value = headers[idx + 1].toString() + if (skips.some((skip) => (typeof skip == 'string' ? skip == key : skip.test(key)))) { + continue + } + const attributeKey = `http.${type}.header.${key}` + if ( + redacts === true || + redacts.some((redact) => (typeof redact == 'string' ? redact == key : redact.test(key))) + ) { + attributes[attributeKey] = 'REDACTED' + } else { + attributes[attributeKey] = value + } + } + return attributes + } - const value = response.headers[idx + 1] - const contentLength = Number(value.toString()) + private getRequestMethod(original: string): string { + const acceptedMethods = ['HEAD', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'] - if (!isNaN(contentLength)) return contentLength + if (acceptedMethods.includes(original.toUpperCase())) { + return original.toUpperCase() } - return undefined + return '_OTHER' } private getTracer(): SugaredTracer | undefined { @@ -122,17 +150,22 @@ export class FetchInstrumentation implements Instrumentation { this._channelSubs.length = 0 } - private onRequestCreated({ request }: RequestMessage): void { + private onRequestCreated({ request }: { request: FetchRequest }): void { const tracer = this.getTracer() - if (!tracer) return + const url = new URL(request.path, request.origin) - const activeCtx = api.context.active() + if ( + !tracer || + request.method === 'CONNECT' || + this.config.skipURLs?.some((skip) => (typeof skip == 'string' ? url.href.startsWith(skip) : skip.test(url.href))) + ) { + return + } - if (request.method === 'CONNECT') return + const activeCtx = api.context.active() const span = tracer.startSpan( - // TODO - filter down request methods - request.method, + this.getRequestMethod(request.method), { kind: api.SpanKind.CLIENT, }, @@ -144,15 +177,14 @@ export class FetchInstrumentation implements Instrumentation { this._recordFromReq.set(request, span) } - private onResponseHeaders({ request, response }: ResponseHeadersMessage): void { + private onResponseHeaders({ request, response }: { request: FetchRequest; response: FetchResponse }): void { const span = this._recordFromReq.get(request) - if (!span) return this.annotateFromResponse(span, response) } - private onError({ request, error }: RequestErrorMessage): void { + private onError({ request, error }: { request: FetchRequest; error: Error }): void { const span = this._recordFromReq.get(request) if (!span) return @@ -166,9 +198,8 @@ export class FetchInstrumentation implements Instrumentation { this._recordFromReq.delete(request) } - private onDone({ request }: RequestTrailersMessage): void { + private onDone({ request }: { request: FetchRequest; response: FetchResponse }): void { const span = this._recordFromReq.get(request) - if (!span) return span.end() @@ -181,38 +212,12 @@ interface ListenerRecord { unsubscribe: () => void } -interface RequestMessage { - request: UndiciRequest -} - -interface ResponseHeadersMessage { - request: UndiciRequest - response: UndiciResponse -} - -interface RequestErrorMessage { - request: UndiciRequest - error: Error -} - -interface RequestTrailersMessage { - request: UndiciRequest - response: UndiciResponse -} - -interface UndiciRequest { +// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/packages/instrumentation-undici/src/types.ts +interface FetchRequest { origin: string method: string path: string - /** - * Serialized string of headers in the form `name: value\r\n` for v5 - * Array of strings `[key1, value1, key2, value2]`, where values are - * `string | string[]` for v6 - */ headers: string | (string | string[])[] - /** - * Helper method to add headers (from v6) - */ addHeader: (name: string, value: string) => void throwOnError: boolean completed: boolean @@ -223,7 +228,7 @@ interface UndiciRequest { body: unknown } -interface UndiciResponse { +interface FetchResponse { headers: Buffer[] statusCode: number statusText: string From 4b31d61ef43b4cf4c7ddc76bdcf34ee96e60d869 Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Thu, 4 Dec 2025 17:46:26 -0500 Subject: [PATCH 3/9] fix: rename request handler methods --- packages/otel/src/instrumentations/fetch.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/otel/src/instrumentations/fetch.ts b/packages/otel/src/instrumentations/fetch.ts index 3fb2cd46a..d59d3ce21 100644 --- a/packages/otel/src/instrumentations/fetch.ts +++ b/packages/otel/src/instrumentations/fetch.ts @@ -129,10 +129,10 @@ export class FetchInstrumentation implements Instrumentation { enable(): void { // https://undici.nodejs.org/#/docs/api/DiagnosticsChannel?id=diagnostics-channel-support - this.subscribe('undici:request:create', this.onRequestCreated.bind(this)) - this.subscribe('undici:request:headers', this.onResponseHeaders.bind(this)) - this.subscribe('undici:request:trailers', this.onDone.bind(this)) - this.subscribe('undici:request:error', this.onError.bind(this)) + this.subscribe('undici:request:create', this.onRequestCreate.bind(this)) + this.subscribe('undici:request:headers', this.onRequestHeaders.bind(this)) + this.subscribe('undici:request:trailers', this.onRequestEnd.bind(this)) + this.subscribe('undici:request:error', this.onRequestError.bind(this)) } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -150,7 +150,7 @@ export class FetchInstrumentation implements Instrumentation { this._channelSubs.length = 0 } - private onRequestCreated({ request }: { request: FetchRequest }): void { + private onRequestCreate({ request }: { request: FetchRequest }): void { const tracer = this.getTracer() const url = new URL(request.path, request.origin) @@ -177,14 +177,14 @@ export class FetchInstrumentation implements Instrumentation { this._recordFromReq.set(request, span) } - private onResponseHeaders({ request, response }: { request: FetchRequest; response: FetchResponse }): void { + private onRequestHeaders({ request, response }: { request: FetchRequest; response: FetchResponse }): void { const span = this._recordFromReq.get(request) if (!span) return this.annotateFromResponse(span, response) } - private onError({ request, error }: { request: FetchRequest; error: Error }): void { + private onRequestError({ request, error }: { request: FetchRequest; error: Error }): void { const span = this._recordFromReq.get(request) if (!span) return @@ -198,7 +198,7 @@ export class FetchInstrumentation implements Instrumentation { this._recordFromReq.delete(request) } - private onDone({ request }: { request: FetchRequest; response: FetchResponse }): void { + private onRequestEnd({ request }: { request: FetchRequest; response: FetchResponse }): void { const span = this._recordFromReq.get(request) if (!span) return From 62432c699ecbe3cf4fa99413e6bb398987afe396 Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Thu, 4 Dec 2025 17:51:27 -0500 Subject: [PATCH 4/9] fix: avoid duplicate subscriptions --- packages/otel/src/instrumentations/fetch.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/otel/src/instrumentations/fetch.ts b/packages/otel/src/instrumentations/fetch.ts index d59d3ce21..37d2982ad 100644 --- a/packages/otel/src/instrumentations/fetch.ts +++ b/packages/otel/src/instrumentations/fetch.ts @@ -128,6 +128,9 @@ export class FetchInstrumentation implements Instrumentation { } enable(): void { + // Avoid to duplicate subscriptions + if (this._channelSubs.length > 0) return + // https://undici.nodejs.org/#/docs/api/DiagnosticsChannel?id=diagnostics-channel-support this.subscribe('undici:request:create', this.onRequestCreate.bind(this)) this.subscribe('undici:request:headers', this.onRequestHeaders.bind(this)) From 34b06bc6ecdf836c10133651ecbc81042249e087 Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Fri, 5 Dec 2025 08:56:08 -0500 Subject: [PATCH 5/9] fix: initialize _channelSubs array --- packages/otel/src/instrumentations/fetch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/otel/src/instrumentations/fetch.ts b/packages/otel/src/instrumentations/fetch.ts index 37d2982ad..b2f3f4823 100644 --- a/packages/otel/src/instrumentations/fetch.ts +++ b/packages/otel/src/instrumentations/fetch.ts @@ -24,6 +24,7 @@ export class FetchInstrumentation implements Instrumentation { constructor(config: FetchInstrumentationConfig = {}) { this.config = config + this._channelSubs = [] } getConfig(): FetchInstrumentationConfig { From 677b5b66f85415d1295814a7d36d46c32bb686aa Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Fri, 5 Dec 2025 09:25:41 -0500 Subject: [PATCH 6/9] test: update request/response header format --- .../otel/src/instrumentations/fetch.test.ts | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/otel/src/instrumentations/fetch.test.ts b/packages/otel/src/instrumentations/fetch.test.ts index ca2161499..13764b1e1 100644 --- a/packages/otel/src/instrumentations/fetch.test.ts +++ b/packages/otel/src/instrumentations/fetch.test.ts @@ -14,11 +14,7 @@ describe('header exclusion', () => { // eslint-disable-next-line @typescript-eslint/dot-notation const attributes = instrumentation['prepareHeaders']( 'request', - new Headers({ - a: 'a', - b: 'b', - authorization: 'secret', - }), + ['a', 'a', 'b', 'b', 'authorization', 'secret'].map((value) => Buffer.from(value)), ) expect(attributes).toEqual({ 'http.request.header.a': 'a', @@ -33,11 +29,7 @@ describe('header exclusion', () => { // eslint-disable-next-line @typescript-eslint/dot-notation const empty = everything['prepareHeaders']( 'request', - new Headers({ - a: 'a', - b: 'b', - authorization: 'secret', - }), + ['a', 'a', 'b', 'b', 'authorization', 'secret'].map((value) => Buffer.from(value)), ) expect(empty).toEqual({}) }) @@ -50,13 +42,9 @@ describe('header exclusion', () => { // eslint-disable-next-line @typescript-eslint/dot-notation const attributes = instrumentation['prepareHeaders']( 'request', - new Headers({ - a: 'a', - b: 'b', - authorization: 'a secret', - }), + ['a', 'a', 'b', 'b', 'authorization', 'secret'].map((value) => Buffer.from(value)), ) - expect(attributes['http.request.header.authorization']).not.toBe('a secret') + expect(attributes['http.request.header.authorization']).not.toBe('secret') expect(attributes['http.request.header.authorization']).toBeTypeOf('string') expect(attributes['http.request.header.a']).toBe('a') expect(attributes['http.request.header.b']).toBe('b') @@ -70,11 +58,7 @@ describe('header exclusion', () => { // eslint-disable-next-line @typescript-eslint/dot-notation const attributes = instrumentation['prepareHeaders']( 'request', - new Headers({ - a: 'a', - b: 'b', - authorization: 'a secret', - }), + ['a', 'a', 'b', 'b', 'authorization', 'secret'].map((value) => Buffer.from(value)), ) expect(attributes['http.request.header.authorization']).not.toBe('a secret') expect(attributes['http.request.header.a']).not.toBe('a') From 2f945ba5e97cd40076d4f9634aa2146108ec230e Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Fri, 5 Dec 2025 13:01:17 -0500 Subject: [PATCH 7/9] chore: no need for new variable --- packages/otel/src/instrumentations/fetch.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/otel/src/instrumentations/fetch.ts b/packages/otel/src/instrumentations/fetch.ts index b2f3f4823..5305c480a 100644 --- a/packages/otel/src/instrumentations/fetch.ts +++ b/packages/otel/src/instrumentations/fetch.ts @@ -166,14 +166,12 @@ export class FetchInstrumentation implements Instrumentation { return } - const activeCtx = api.context.active() - const span = tracer.startSpan( this.getRequestMethod(request.method), { kind: api.SpanKind.CLIENT, }, - activeCtx, + api.context.active(), ) this.annotateFromRequest(span, request) From 5632e54808de5cebd2d1a762019c1d41e245237e Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Fri, 5 Dec 2025 13:27:02 -0500 Subject: [PATCH 8/9] test: no longer patching fetch --- .../otel/src/instrumentations/fetch.test.ts | 80 +------------------ 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/packages/otel/src/instrumentations/fetch.test.ts b/packages/otel/src/instrumentations/fetch.test.ts index 13764b1e1..385e56112 100644 --- a/packages/otel/src/instrumentations/fetch.test.ts +++ b/packages/otel/src/instrumentations/fetch.test.ts @@ -1,8 +1,4 @@ -import { http, HttpResponse } from 'msw' -import { setupServer } from 'msw/node' -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test } from 'vitest' -import { createTracerProvider } from '../bootstrap/main.ts' -import { shutdownTracers } from '../main.ts' +import { describe, expect, test } from 'vitest' import { FetchInstrumentation } from './fetch.ts' describe('header exclusion', () => { @@ -68,77 +64,3 @@ describe('header exclusion', () => { expect(attributes['http.request.header.b']).toBeTypeOf('string') }) }) - -describe('patched fetch', () => { - const server = setupServer( - http.get('http://localhost:3000/ok', () => HttpResponse.json({ message: 'ok' })), - http.post('http://localhost:3000/ok', () => HttpResponse.json({ message: 'ok' })), - ) - - beforeAll(() => { - server.listen({ onUnhandledRequest: 'error' }) - }) - - beforeEach(() => { - createTracerProvider({ - serviceName: 'test-service', - serviceVersion: '1.0.0', - deploymentEnvironment: 'test', - siteUrl: 'https://example.com', - siteId: '12345', - siteName: 'example', - instrumentations: [new FetchInstrumentation()], - }) - }) - - afterEach(async () => { - server.resetHandlers() - await shutdownTracers() - }) - - afterAll(() => { - server.close() - }) - - it('can GET url', async () => { - createTracerProvider({ - serviceName: 'test-service', - serviceVersion: '1.0.0', - deploymentEnvironment: 'test', - siteUrl: 'https://example.com', - siteId: '12345', - siteName: 'example', - instrumentations: [new FetchInstrumentation()], - }) - - await expect(fetch('http://localhost:3000/ok').then((r) => r.json())).resolves.toEqual({ message: 'ok' }) - }) - - it('can POST url', async () => { - await expect( - fetch('http://localhost:3000/ok', { - method: 'POST', - body: JSON.stringify({ hello: 'rabbit' }), - headers: { - 'Content-Type': 'application/json', - }, - }).then((r) => r.json()), - ).resolves.toEqual({ message: 'ok' }) - }) - - it('can GET request', async () => { - const req = new Request('http://localhost:3000/ok') - await expect(fetch(req).then((r) => r.json())).resolves.toEqual({ message: 'ok' }) - }) - - it('can POST request', async () => { - const req = new Request('http://localhost:3000/ok', { - method: 'POST', - body: JSON.stringify({ hello: 'rabbit' }), - headers: { - 'Content-Type': 'application/json', - }, - }) - await expect(fetch(req).then((r) => r.json())).resolves.toEqual({ message: 'ok' }) - }) -}) From df369c5b61932ee2cb8b6fefd2567fa51c207969 Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Fri, 5 Dec 2025 13:34:51 -0500 Subject: [PATCH 9/9] test: fix typo in test case --- packages/otel/src/instrumentations/fetch.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/otel/src/instrumentations/fetch.test.ts b/packages/otel/src/instrumentations/fetch.test.ts index 385e56112..504dc2d09 100644 --- a/packages/otel/src/instrumentations/fetch.test.ts +++ b/packages/otel/src/instrumentations/fetch.test.ts @@ -56,7 +56,7 @@ describe('header exclusion', () => { 'request', ['a', 'a', 'b', 'b', 'authorization', 'secret'].map((value) => Buffer.from(value)), ) - expect(attributes['http.request.header.authorization']).not.toBe('a secret') + expect(attributes['http.request.header.authorization']).not.toBe('secret') expect(attributes['http.request.header.a']).not.toBe('a') expect(attributes['http.request.header.b']).not.toBe('b') expect(attributes['http.request.header.authorization']).toBeTypeOf('string')