Skip to content

Commit 24e2a27

Browse files
authored
feat(node): Add shouldCreateSpanForRequest option (#6055)
As per the summary here in #5285 (comment)) this PR adds support for an optional `shouldCreateSpanForRequest` function in the options. When it's defined and returns false, spans will not be attached.
1 parent fae7b24 commit 24e2a27

File tree

3 files changed

+63
-17
lines changed

3 files changed

+63
-17
lines changed

packages/node/src/integrations/http.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getCurrentHub, Hub } from '@sentry/core';
2-
import { EventProcessor, Integration, Span, TracePropagationTargets } from '@sentry/types';
2+
import { EventProcessor, Integration, Span } from '@sentry/types';
33
import {
44
dynamicSamplingContextToSentryBaggageHeader,
55
fill,
@@ -10,6 +10,7 @@ import {
1010
import * as http from 'http';
1111
import * as https from 'https';
1212

13+
import { NodeClient } from '../client';
1314
import { NodeClientOptions } from '../types';
1415
import {
1516
cleanSpanDescription,
@@ -67,13 +68,8 @@ export class Http implements Integration {
6768
return;
6869
}
6970

70-
const clientOptions = setupOnceGetCurrentHub().getClient()?.getOptions() as NodeClientOptions | undefined;
71-
72-
const wrappedHandlerMaker = _createWrappedRequestMethodFactory(
73-
this._breadcrumbs,
74-
this._tracing,
75-
clientOptions?.tracePropagationTargets,
76-
);
71+
const clientOptions = setupOnceGetCurrentHub().getClient<NodeClient>()?.getOptions();
72+
const wrappedHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, this._tracing, clientOptions);
7773

7874
// eslint-disable-next-line @typescript-eslint/no-var-requires
7975
const httpModule = require('http');
@@ -109,24 +105,40 @@ type WrappedRequestMethodFactory = (original: OriginalRequestMethod) => WrappedR
109105
function _createWrappedRequestMethodFactory(
110106
breadcrumbsEnabled: boolean,
111107
tracingEnabled: boolean,
112-
tracePropagationTargets: TracePropagationTargets | undefined,
108+
options: NodeClientOptions | undefined,
113109
): WrappedRequestMethodFactory {
114-
// We're caching results so we dont have to recompute regexp everytime we create a request.
115-
const urlMap: Record<string, boolean> = {};
110+
// We're caching results so we don't have to recompute regexp every time we create a request.
111+
const createSpanUrlMap: Record<string, boolean> = {};
112+
const headersUrlMap: Record<string, boolean> = {};
113+
114+
const shouldCreateSpan = (url: string): boolean => {
115+
if (options?.shouldCreateSpanForRequest === undefined) {
116+
return true;
117+
}
118+
119+
if (createSpanUrlMap[url]) {
120+
return createSpanUrlMap[url];
121+
}
122+
123+
createSpanUrlMap[url] = options.shouldCreateSpanForRequest(url);
124+
125+
return createSpanUrlMap[url];
126+
};
127+
116128
const shouldAttachTraceData = (url: string): boolean => {
117-
if (tracePropagationTargets === undefined) {
129+
if (options?.tracePropagationTargets === undefined) {
118130
return true;
119131
}
120132

121-
if (urlMap[url]) {
122-
return urlMap[url];
133+
if (headersUrlMap[url]) {
134+
return headersUrlMap[url];
123135
}
124136

125-
urlMap[url] = tracePropagationTargets.some(tracePropagationTarget =>
137+
headersUrlMap[url] = options.tracePropagationTargets.some(tracePropagationTarget =>
126138
isMatchingPattern(url, tracePropagationTarget),
127139
);
128140

129-
return urlMap[url];
141+
return headersUrlMap[url];
130142
};
131143

132144
return function wrappedRequestMethodFactory(originalRequestMethod: OriginalRequestMethod): WrappedRequestMethod {
@@ -148,7 +160,7 @@ function _createWrappedRequestMethodFactory(
148160

149161
const scope = getCurrentHub().getScope();
150162

151-
if (scope && tracingEnabled) {
163+
if (scope && tracingEnabled && shouldCreateSpan(requestUrl)) {
152164
parentSpan = scope.getSpan();
153165

154166
if (parentSpan) {

packages/node/src/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ export interface BaseNodeOptions {
1919
*/
2020
tracePropagationTargets?: TracePropagationTargets;
2121

22+
/**
23+
* Function determining whether or not to create spans to track outgoing requests to the given URL.
24+
* By default, spans will be created for all outgoing requests.
25+
*/
26+
shouldCreateSpanForRequest?(url: string): boolean;
27+
2228
/** Callback that is executed when a fatal global error occurs. */
2329
onFatalError?(error: Error): void;
2430
}

packages/node/test/integrations/http.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,36 @@ describe('tracing', () => {
221221
addExtensionMethods();
222222
const transaction = hub.startTransaction({ name: 'dogpark' });
223223
hub.getScope()?.setSpan(transaction);
224+
return transaction;
224225
}
225226

227+
it("doesn't create span if shouldCreateSpanForRequest returns false", () => {
228+
const url = 'http://dogs.are.great/api/v1/index/';
229+
nock(url).get(/.*/).reply(200);
230+
231+
const httpIntegration = new HttpIntegration({ tracing: true });
232+
233+
const hub = createHub({ shouldCreateSpanForRequest: () => false });
234+
235+
httpIntegration.setupOnce(
236+
() => undefined,
237+
() => hub,
238+
);
239+
240+
const transaction = createTransactionAndPutOnScope(hub);
241+
const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[];
242+
243+
const request = http.get(url);
244+
245+
// There should be no http spans
246+
const httpSpans = spans.filter(span => span.op?.startsWith('http'));
247+
expect(httpSpans.length).toBe(0);
248+
249+
// And headers are not attached without span creation
250+
expect(request.getHeader('sentry-trace')).toBeUndefined();
251+
expect(request.getHeader('baggage')).toBeUndefined();
252+
});
253+
226254
it.each([
227255
['http://dogs.are.great/api/v1/index/', [/.*/]],
228256
['http://dogs.are.great/api/v1/index/', [/\/api/]],

0 commit comments

Comments
 (0)