|
| 1 | +/* eslint-disable max-lines */ |
1 | 2 | import type * as http from 'node:http'; |
2 | 3 | import type { IncomingMessage, RequestOptions } from 'node:http'; |
3 | 4 | import type * as https from 'node:https'; |
4 | 5 | import type { EventEmitter } from 'node:stream'; |
5 | | -/* eslint-disable max-lines */ |
6 | 6 | import { VERSION } from '@opentelemetry/core'; |
7 | 7 | import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; |
8 | 8 | import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; |
9 | 9 | import type { AggregationCounts, Client, RequestEventData, SanitizedRequestData, Scope } from '@sentry/core'; |
10 | 10 | import { |
11 | | - LRUMap, |
12 | 11 | addBreadcrumb, |
13 | 12 | generateSpanId, |
14 | 13 | getBreadcrumbLogLevelFromHttpStatusCode, |
15 | 14 | getClient, |
16 | 15 | getIsolationScope, |
17 | 16 | getSanitizedUrlString, |
18 | | - getTraceData, |
19 | 17 | httpRequestToRequestData, |
20 | 18 | logger, |
21 | | - objectToBaggageHeader, |
22 | | - parseBaggageHeader, |
23 | 19 | parseUrl, |
24 | 20 | stripUrlQueryAndFragment, |
25 | 21 | withIsolationScope, |
26 | 22 | withScope, |
27 | 23 | } from '@sentry/core'; |
28 | | -import { shouldPropagateTraceForUrl } from '@sentry/opentelemetry'; |
29 | 24 | import { DEBUG_BUILD } from '../../debug-build'; |
30 | 25 | import { getRequestUrl } from '../../utils/getRequestUrl'; |
31 | 26 | import { getRequestInfo } from './vendor/getRequestInfo'; |
32 | 27 |
|
33 | 28 | type Http = typeof http; |
34 | 29 | type Https = typeof https; |
35 | 30 |
|
36 | | -type RequestArgs = |
37 | | - // eslint-disable-next-line @typescript-eslint/ban-types |
38 | | - | [url: string | URL, options?: RequestOptions, callback?: Function] |
39 | | - // eslint-disable-next-line @typescript-eslint/ban-types |
40 | | - | [options: RequestOptions, callback?: Function]; |
41 | | - |
42 | 31 | type SentryHttpInstrumentationOptions = InstrumentationConfig & { |
43 | 32 | /** |
44 | 33 | * Whether breadcrumbs should be recorded for requests. |
@@ -91,11 +80,8 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024; |
91 | 80 | * https://github.com/open-telemetry/opentelemetry-js/blob/f8ab5592ddea5cba0a3b33bf8d74f27872c0367f/experimental/packages/opentelemetry-instrumentation-http/src/http.ts |
92 | 81 | */ |
93 | 82 | export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpInstrumentationOptions> { |
94 | | - private _propagationDecisionMap: LRUMap<string, boolean>; |
95 | | - |
96 | 83 | public constructor(config: SentryHttpInstrumentationOptions = {}) { |
97 | 84 | super('@sentry/instrumentation-http', VERSION, config); |
98 | | - this._propagationDecisionMap = new LRUMap<string, boolean>(100); |
99 | 85 | } |
100 | 86 |
|
101 | 87 | /** @inheritdoc */ |
@@ -222,32 +208,22 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns |
222 | 208 | return function outgoingRequest(this: unknown, ...args: unknown[]): http.ClientRequest { |
223 | 209 | instrumentation._diag.debug('http instrumentation for outgoing requests'); |
224 | 210 |
|
| 211 | + // Making a copy to avoid mutating the original args array |
225 | 212 | // We need to access and reconstruct the request options object passed to `ignoreOutgoingRequests` |
226 | 213 | // so that it matches what Otel instrumentation passes to `ignoreOutgoingRequestHook`. |
227 | 214 | // @see https://github.com/open-telemetry/opentelemetry-js/blob/7293e69c1e55ca62e15d0724d22605e61bd58952/experimental/packages/opentelemetry-instrumentation-http/src/http.ts#L756-L789 |
228 | | - const requestArgs = [...args] as RequestArgs; |
229 | | - |
230 | | - let options = requestArgs[0]; |
| 215 | + const argsCopy = [...args]; |
231 | 216 |
|
232 | | - // Make sure correct fallback attributes are set on the options object for https before we pass them to the vendored getRequestInfo function. |
233 | | - // Ref: https://github.com/open-telemetry/opentelemetry-js/blob/887ff1cd6e3f795f703e40a9fbe89b3cba7e88c3/experimental/packages/opentelemetry-instrumentation-http/src/http.ts#L390 |
234 | | - if (component === 'https' && typeof options === 'object' && options?.constructor?.name !== 'URL') { |
235 | | - options = Object.assign({}, options); |
236 | | - options.protocol = options.protocol || 'https:'; |
237 | | - options.port = options.port || 443; |
238 | | - } |
| 217 | + const options = argsCopy.shift() as URL | http.RequestOptions | string; |
239 | 218 |
|
240 | | - const extraOptions = typeof requestArgs[1] === 'object' ? requestArgs[1] : undefined; |
| 219 | + const extraOptions = |
| 220 | + typeof argsCopy[0] === 'object' && (typeof options === 'string' || options instanceof URL) |
| 221 | + ? (argsCopy.shift() as http.RequestOptions) |
| 222 | + : undefined; |
241 | 223 |
|
242 | | - const { optionsParsed, origin, pathname } = getRequestInfo(instrumentation._diag, options, extraOptions); |
| 224 | + const { optionsParsed } = getRequestInfo(instrumentation._diag, options, extraOptions); |
243 | 225 |
|
244 | | - const url = getAbsoluteUrl(origin, pathname); |
245 | | - |
246 | | - addSentryHeadersToRequestOptions(url, optionsParsed, instrumentation._propagationDecisionMap); |
247 | | - |
248 | | - const request = original.apply(this, [optionsParsed, ...requestArgs.slice(1)]) as ReturnType< |
249 | | - typeof http.request |
250 | | - >; |
| 226 | + const request = original.apply(this, args) as ReturnType<typeof http.request>; |
251 | 227 |
|
252 | 228 | request.prependListener('response', (response: http.IncomingMessage) => { |
253 | 229 | const _breadcrumbs = instrumentation.getConfig().breadcrumbs; |
@@ -481,44 +457,6 @@ function patchRequestToCaptureBody(req: IncomingMessage, isolationScope: Scope): |
481 | 457 | } |
482 | 458 | } |
483 | 459 |
|
484 | | -/** |
485 | | - * Mutates the passed in `options` and adds `sentry-trace` / `baggage` headers, if they are not already set. |
486 | | - */ |
487 | | -function addSentryHeadersToRequestOptions( |
488 | | - url: string, |
489 | | - options: RequestOptions, |
490 | | - propagationDecisionMap: LRUMap<string, boolean>, |
491 | | -): void { |
492 | | - // Manually add the trace headers, if it applies |
493 | | - // Note: We do not use `propagation.inject()` here, because our propagator relies on an active span |
494 | | - // Which we do not have in this case |
495 | | - const tracePropagationTargets = getClient()?.getOptions().tracePropagationTargets; |
496 | | - const addedHeaders = shouldPropagateTraceForUrl(url, tracePropagationTargets, propagationDecisionMap) |
497 | | - ? getTraceData() |
498 | | - : undefined; |
499 | | - |
500 | | - if (!addedHeaders) { |
501 | | - return; |
502 | | - } |
503 | | - |
504 | | - if (!options.headers) { |
505 | | - options.headers = {}; |
506 | | - } |
507 | | - const headers = options.headers; |
508 | | - |
509 | | - const { 'sentry-trace': sentryTrace, baggage } = addedHeaders; |
510 | | - |
511 | | - // We do not want to overwrite existing header here, if it was already set |
512 | | - if (sentryTrace && !headers['sentry-trace']) { |
513 | | - headers['sentry-trace'] = sentryTrace; |
514 | | - } |
515 | | - |
516 | | - // For baggage, we make sure to merge this into a possibly existing header |
517 | | - if (baggage) { |
518 | | - headers['baggage'] = mergeBaggageHeaders(headers['baggage'], baggage); |
519 | | - } |
520 | | -} |
521 | | - |
522 | 460 | /** |
523 | 461 | * Starts a session and tracks it in the context of a given isolation scope. |
524 | 462 | * When the passed response is finished, the session is put into a task and is |
@@ -593,49 +531,3 @@ const clientToRequestSessionAggregatesMap = new Map< |
593 | 531 | Client, |
594 | 532 | { [timestampRoundedToSeconds: string]: { exited: number; crashed: number; errored: number } } |
595 | 533 | >(); |
596 | | - |
597 | | -function getAbsoluteUrl(origin: string, path: string = '/'): string { |
598 | | - try { |
599 | | - const url = new URL(path, origin); |
600 | | - return url.toString(); |
601 | | - } catch { |
602 | | - // fallback: Construct it on our own |
603 | | - const url = `${origin}`; |
604 | | - |
605 | | - if (url.endsWith('/') && path.startsWith('/')) { |
606 | | - return `${url}${path.slice(1)}`; |
607 | | - } |
608 | | - |
609 | | - if (!url.endsWith('/') && !path.startsWith('/')) { |
610 | | - return `${url}/${path.slice(1)}`; |
611 | | - } |
612 | | - |
613 | | - return `${url}${path}`; |
614 | | - } |
615 | | -} |
616 | | - |
617 | | -function mergeBaggageHeaders( |
618 | | - existing: string | string[] | number | undefined, |
619 | | - baggage: string, |
620 | | -): string | string[] | number | undefined { |
621 | | - if (!existing) { |
622 | | - return baggage; |
623 | | - } |
624 | | - |
625 | | - const existingBaggageEntries = parseBaggageHeader(existing); |
626 | | - const newBaggageEntries = parseBaggageHeader(baggage); |
627 | | - |
628 | | - if (!newBaggageEntries) { |
629 | | - return existing; |
630 | | - } |
631 | | - |
632 | | - // Existing entries take precedence, ensuring order remains stable for minimal changes |
633 | | - const mergedBaggageEntries = { ...existingBaggageEntries }; |
634 | | - Object.entries(newBaggageEntries).forEach(([key, value]) => { |
635 | | - if (!mergedBaggageEntries[key]) { |
636 | | - mergedBaggageEntries[key] = value; |
637 | | - } |
638 | | - }); |
639 | | - |
640 | | - return objectToBaggageHeader(mergedBaggageEntries); |
641 | | -} |
0 commit comments