-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathindex.ts
More file actions
151 lines (130 loc) · 5.37 KB
/
index.ts
File metadata and controls
151 lines (130 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import type { UndiciInstrumentationConfig } from './vendored/types';
import { UndiciInstrumentation } from './vendored/undici';
import type { IntegrationFn } from '@sentry/core';
import {
defineIntegration,
getClient,
hasSpansEnabled,
SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_URL_FULL,
stripDataUrlContent,
} from '@sentry/core';
import type { NodeClient } from '@sentry/node-core';
import { generateInstrumentOnce, SentryNodeFetchInstrumentation } from '@sentry/node-core';
import type { NodeClientOptions } from '../../types';
const INTEGRATION_NAME = 'NodeFetch';
interface NodeFetchOptions extends Pick<
UndiciInstrumentationConfig,
'requestHook' | 'responseHook' | 'headersToSpanAttributes'
> {
/**
* Whether breadcrumbs should be recorded for requests.
* Defaults to true
*/
breadcrumbs?: boolean;
/**
* If set to false, do not emit any spans.
* This will ensure that the default UndiciInstrumentation from OpenTelemetry is not setup,
* only the Sentry-specific instrumentation for breadcrumbs & trace propagation is applied.
*
* If `skipOpenTelemetrySetup: true` is configured, this defaults to `false`, otherwise it defaults to `true`.
*/
spans?: boolean;
/**
* Whether to inject trace propagation headers (sentry-trace, baggage, traceparent) into outgoing fetch requests.
*
* When set to `false`, Sentry will not inject any trace propagation headers, but will still create breadcrumbs
* (if `breadcrumbs` is enabled). This is useful when `skipOpenTelemetrySetup: true` is configured and you want
* to avoid duplicate trace headers being injected by both Sentry and OpenTelemetry's UndiciInstrumentation.
*
* @default `true`
*/
tracePropagation?: boolean;
/**
* Do not capture spans or breadcrumbs for outgoing fetch requests to URLs where the given callback returns `true`.
* This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
*/
ignoreOutgoingRequests?: (url: string) => boolean;
}
const instrumentOtelNodeFetch = generateInstrumentOnce(
INTEGRATION_NAME,
UndiciInstrumentation,
(options: NodeFetchOptions) => {
return _getConfigWithDefaults(options);
},
);
const instrumentSentryNodeFetch = generateInstrumentOnce(
`${INTEGRATION_NAME}.sentry`,
SentryNodeFetchInstrumentation,
(options: NodeFetchOptions) => {
return options;
},
);
const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
return {
name: 'NodeFetch',
setupOnce() {
const instrumentSpans = _shouldInstrumentSpans(options, getClient<NodeClient>()?.getOptions());
// This is the "regular" OTEL instrumentation that emits spans
if (instrumentSpans) {
instrumentOtelNodeFetch(options);
}
// This is the Sentry-specific instrumentation that creates breadcrumbs & propagates traces
// This must be registered after the OTEL one, to ensure that the core trace propagation logic takes presedence
// Otherwise, the sentry-trace header may be set multiple times
instrumentSentryNodeFetch(options);
},
};
}) satisfies IntegrationFn;
export const nativeNodeFetchIntegration = defineIntegration(_nativeNodeFetchIntegration);
// Matching the behavior of the base instrumentation
function getAbsoluteUrl(origin: string, path: string = '/'): string {
const url = `${origin}`;
if (url.endsWith('/') && path.startsWith('/')) {
return `${url}${path.slice(1)}`;
}
if (!url.endsWith('/') && !path.startsWith('/')) {
return `${url}/${path}`;
}
return `${url}${path}`;
}
function _shouldInstrumentSpans(options: NodeFetchOptions, clientOptions: Partial<NodeClientOptions> = {}): boolean {
// If `spans` is passed in, it takes precedence
// Else, we by default emit spans, unless `skipOpenTelemetrySetup` is set to `true` or spans are not enabled
return typeof options.spans === 'boolean'
? options.spans
: !clientOptions.skipOpenTelemetrySetup && hasSpansEnabled(clientOptions);
}
/** Exported only for tests. */
export function _getConfigWithDefaults(options: Partial<NodeFetchOptions> = {}): UndiciInstrumentationConfig {
const instrumentationConfig = {
requireParentforSpans: false,
ignoreRequestHook: request => {
const url = getAbsoluteUrl(request.origin, request.path);
const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
const shouldIgnore = _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url);
return !!shouldIgnore;
},
startSpanHook: request => {
const url = getAbsoluteUrl(request.origin, request.path);
// Sanitize data URLs to prevent long base64 strings in span attributes
if (url.startsWith('data:')) {
const sanitizedUrl = stripDataUrlContent(url);
return {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.node_fetch',
'http.url': sanitizedUrl,
[SEMANTIC_ATTRIBUTE_URL_FULL]: sanitizedUrl,
[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: `${request.method || 'GET'} ${sanitizedUrl}`,
};
}
return {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.node_fetch',
};
},
requestHook: options.requestHook,
responseHook: options.responseHook,
headersToSpanAttributes: options.headersToSpanAttributes,
} satisfies UndiciInstrumentationConfig;
return instrumentationConfig;
}