Skip to content

Commit 2950ca6

Browse files
committed
feat(node): Add tracing without performance to Node http integration
1 parent 86ffdf4 commit 2950ca6

File tree

15 files changed

+225
-108
lines changed

15 files changed

+225
-108
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ jobs:
661661
needs: [job_get_metadata, job_build]
662662
if: needs.job_get_metadata.outputs.changed_node == 'true' || github.event_name != 'pull_request'
663663
runs-on: ubuntu-20.04
664-
timeout-minutes: 10
664+
timeout-minutes: 15
665665
strategy:
666666
fail-fast: false
667667
matrix:

packages/nextjs/test/integration/sentry.client.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Integrations } from '@sentry/tracing';
33

44
Sentry.init({
55
dsn: 'https://[email protected]/1337',
6-
tracesSampleRate: 1,
6+
tracesSampler: () => true,
77
debug: process.env.SDK_DEBUG,
88

99
integrations: [

packages/nextjs/test/integration/sentry.edge.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as Sentry from '@sentry/nextjs';
22

33
Sentry.init({
44
dsn: 'https://[email protected]/1337',
5-
tracesSampleRate: 1,
5+
tracesSampleRate: 1.0,
6+
tracePropagationTargets: ['http://example.com'],
67
debug: process.env.SDK_DEBUG,
78
});

packages/nextjs/test/integration/sentry.server.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as Sentry from '@sentry/nextjs';
22

33
Sentry.init({
44
dsn: 'https://[email protected]/1337',
5-
tracesSampleRate: 1,
5+
tracesSampleRate: 1.0,
6+
tracePropagationTargets: ['http://example.com'],
67
debug: process.env.SDK_DEBUG,
78

89
integrations: defaults => [

packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Sentry.init({
1212
dsn: 'https://[email protected]/1337',
1313
release: '1.0',
1414
environment: 'prod',
15+
tracePropagationTargets: [/^(?!.*express).*$/],
1516
// eslint-disable-next-line deprecation/deprecation
1617
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
1718
tracesSampleRate: 1.0,

packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Sentry.init({
1212
dsn: 'https://[email protected]/1337',
1313
release: '1.0',
1414
environment: 'prod',
15+
// disable requests to /express
16+
tracePropagationTargets: [/^(?!.*express).*$/],
1517
// eslint-disable-next-line deprecation/deprecation
1618
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
1719
tracesSampleRate: 1.0,

packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Sentry.init({
1212
dsn: 'https://[email protected]/1337',
1313
release: '1.0',
1414
environment: 'prod',
15+
// disable requests to /express
16+
tracePropagationTargets: [/^(?!.*express).*$/],
1517
// eslint-disable-next-line deprecation/deprecation
1618
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
1719
tracesSampleRate: 1.0,

packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Sentry.init({
1212
dsn: 'https://[email protected]/1337',
1313
release: '1.0',
1414
environment: 'prod',
15+
// disable requests to /express
16+
tracePropagationTargets: [/^(?!.*express).*$/],
1517
// eslint-disable-next-line deprecation/deprecation
1618
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
1719
tracesSampleRate: 1.0,

packages/node-integration-tests/suites/express/sentry-trace/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Sentry.init({
1212
dsn: 'https://[email protected]/1337',
1313
release: '1.0',
1414
environment: 'prod',
15+
tracePropagationTargets: [/^(?!.*express).*$/],
1516
// eslint-disable-next-line deprecation/deprecation
1617
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
1718
tracesSampleRate: 1.0,

packages/node-integration-tests/suites/express/tracing/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const app = express();
77
Sentry.init({
88
dsn: 'https://[email protected]/1337',
99
release: '1.0',
10+
// disable attaching headers to /test/* endpoints
11+
tracePropagationTargets: [/^(?!.*test).*$/],
1012
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app })],
1113
tracesSampleRate: 1.0,
1214
});

packages/node-integration-tests/utils/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,11 @@ export class TestEnv {
202202
*/
203203
public async getAPIResponse(
204204
url?: string,
205-
headers?: Record<string, string>,
205+
headers: Record<string, string> = {},
206206
endServer: boolean = true,
207207
): Promise<unknown> {
208208
try {
209-
const { data } = await axios.get(url || this.url, { headers: headers || {} });
209+
const { data } = await axios.get(url || this.url, { headers });
210210
return data;
211211
} finally {
212212
await Sentry.flush();

packages/node/src/integrations/http.ts

Lines changed: 125 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import type { Hub } from '@sentry/core';
2-
import { getCurrentHub } from '@sentry/core';
3-
import type { EventProcessor, Integration, SanitizedRequestData, Span, TracePropagationTargets } from '@sentry/types';
4-
import { dynamicSamplingContextToSentryBaggageHeader, fill, logger, stringMatchesSomePattern } from '@sentry/utils';
2+
import { getCurrentHub, getDynamicSamplingContextFromClient } from '@sentry/core';
3+
import type {
4+
DynamicSamplingContext,
5+
EventProcessor,
6+
Integration,
7+
SanitizedRequestData,
8+
TracePropagationTargets,
9+
} from '@sentry/types';
10+
import {
11+
dynamicSamplingContextToSentryBaggageHeader,
12+
fill,
13+
generateSentryTraceHeader,
14+
logger,
15+
stringMatchesSomePattern,
16+
} from '@sentry/utils';
517
import type * as http from 'http';
618
import type * as https from 'https';
719
import { LRUMap } from 'lru_map';
820

921
import type { NodeClient } from '../client';
1022
import { NODE_VERSION } from '../nodeVersion';
11-
import type { RequestMethod, RequestMethodArgs } from './utils/http';
23+
import type { RequestMethod, RequestMethodArgs, RequestOptions } from './utils/http';
1224
import { cleanSpanDescription, extractRawUrl, extractUrl, isSentryRequest, normalizeRequestArgs } from './utils/http';
1325

1426
interface TracingOptions {
@@ -178,6 +190,36 @@ function _createWrappedRequestMethodFactory(
178190
return decision;
179191
};
180192

193+
/**
194+
* Captures Breadcrumb based on provided request/response pair
195+
*/
196+
function addRequestBreadcrumb(
197+
event: string,
198+
requestSpanData: SanitizedRequestData,
199+
req: http.ClientRequest,
200+
res?: http.IncomingMessage,
201+
): void {
202+
if (!getCurrentHub().getIntegration(Http)) {
203+
return;
204+
}
205+
206+
getCurrentHub().addBreadcrumb(
207+
{
208+
category: 'http',
209+
data: {
210+
status_code: res && res.statusCode,
211+
...requestSpanData,
212+
},
213+
type: 'http',
214+
},
215+
{
216+
event,
217+
request: req,
218+
response: res,
219+
},
220+
);
221+
}
222+
181223
return function wrappedRequestMethodFactory(originalRequestMethod: OriginalRequestMethod): WrappedRequestMethod {
182224
return function wrappedMethod(this: unknown, ...args: RequestMethodArgs): http.ClientRequest {
183225
const requestArgs = normalizeRequestArgs(httpModule, args);
@@ -191,74 +233,38 @@ function _createWrappedRequestMethodFactory(
191233
return originalRequestMethod.apply(httpModule, requestArgs);
192234
}
193235

194-
let requestSpan: Span | undefined;
195-
const parentSpan = getCurrentHub().getScope().getSpan();
196-
197-
const method = requestOptions.method || 'GET';
198-
const requestSpanData: SanitizedRequestData = {
199-
url: requestUrl,
200-
'http.method': method,
201-
};
202-
if (requestOptions.hash) {
203-
// strip leading "#"
204-
requestSpanData['http.fragment'] = requestOptions.hash.substring(1);
205-
}
206-
if (requestOptions.search) {
207-
// strip leading "?"
208-
requestSpanData['http.query'] = requestOptions.search.substring(1);
209-
}
236+
const hub = getCurrentHub();
237+
const scope = hub.getScope();
238+
const parentSpan = scope.getSpan();
239+
240+
const data = getRequestSpanData(requestUrl, requestOptions);
210241

211-
if (tracingOptions && shouldCreateSpan(rawRequestUrl)) {
212-
if (parentSpan) {
213-
requestSpan = parentSpan.startChild({
214-
description: `${method} ${requestSpanData.url}`,
242+
const requestSpan = shouldCreateSpan(rawRequestUrl)
243+
? parentSpan?.startChild({
215244
op: 'http.client',
216-
data: requestSpanData,
217-
});
218-
219-
if (shouldAttachTraceData(rawRequestUrl)) {
220-
const sentryTraceHeader = requestSpan.toTraceparent();
221-
__DEBUG_BUILD__ &&
222-
logger.log(
223-
`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `,
224-
);
225-
226-
requestOptions.headers = {
227-
...requestOptions.headers,
228-
'sentry-trace': sentryTraceHeader,
229-
};
230-
231-
if (parentSpan.transaction) {
232-
const dynamicSamplingContext = parentSpan.transaction.getDynamicSamplingContext();
233-
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
234-
235-
let newBaggageHeaderField;
236-
if (!requestOptions.headers || !requestOptions.headers.baggage) {
237-
newBaggageHeaderField = sentryBaggageHeader;
238-
} else if (!sentryBaggageHeader) {
239-
newBaggageHeaderField = requestOptions.headers.baggage;
240-
} else if (Array.isArray(requestOptions.headers.baggage)) {
241-
newBaggageHeaderField = [...requestOptions.headers.baggage, sentryBaggageHeader];
242-
} else {
243-
// Type-cast explanation:
244-
// Technically this the following could be of type `(number | string)[]` but for the sake of simplicity
245-
// we say this is undefined behaviour, since it would not be baggage spec conform if the user did this.
246-
newBaggageHeaderField = [requestOptions.headers.baggage, sentryBaggageHeader] as string[];
247-
}
248-
249-
requestOptions.headers = {
250-
...requestOptions.headers,
251-
// Setting a hader to `undefined` will crash in node so we only set the baggage header when it's defined
252-
...(newBaggageHeaderField && { baggage: newBaggageHeaderField }),
253-
};
254-
}
255-
} else {
256-
__DEBUG_BUILD__ &&
257-
logger.log(
258-
`[Tracing] Not adding sentry-trace header to outgoing request (${requestUrl}) due to mismatching tracePropagationTargets option.`,
259-
);
260-
}
245+
description: `${data['http.method']} ${data.url}`,
246+
data,
247+
})
248+
: undefined;
249+
250+
if (shouldAttachTraceData(rawRequestUrl)) {
251+
if (requestSpan) {
252+
const sentryTraceHeader = requestSpan.toTraceparent();
253+
const dynamicSamplingContext = requestSpan?.transaction?.getDynamicSamplingContext();
254+
addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext);
255+
} else {
256+
const client = hub.getClient();
257+
const { traceId, sampled, dsc } = scope.getPropagationContext();
258+
const sentryTraceHeader = generateSentryTraceHeader(traceId, undefined, sampled);
259+
const dynamicSamplingContext =
260+
dsc || (client ? getDynamicSamplingContextFromClient(traceId, client, scope) : undefined);
261+
addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext);
261262
}
263+
} else {
264+
__DEBUG_BUILD__ &&
265+
logger.log(
266+
`[Tracing] Not adding sentry-trace header to outgoing request (${requestUrl}) due to mismatching tracePropagationTargets option.`,
267+
);
262268
}
263269

264270
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@@ -268,7 +274,7 @@ function _createWrappedRequestMethodFactory(
268274
// eslint-disable-next-line @typescript-eslint/no-this-alias
269275
const req = this;
270276
if (breadcrumbsEnabled) {
271-
addRequestBreadcrumb('response', requestSpanData, req, res);
277+
addRequestBreadcrumb('response', data, req, res);
272278
}
273279
if (requestSpan) {
274280
if (res.statusCode) {
@@ -283,7 +289,7 @@ function _createWrappedRequestMethodFactory(
283289
const req = this;
284290

285291
if (breadcrumbsEnabled) {
286-
addRequestBreadcrumb('error', requestSpanData, req);
292+
addRequestBreadcrumb('error', data, req);
287293
}
288294
if (requestSpan) {
289295
requestSpan.setHttpStatus(500);
@@ -295,32 +301,55 @@ function _createWrappedRequestMethodFactory(
295301
};
296302
}
297303

298-
/**
299-
* Captures Breadcrumb based on provided request/response pair
300-
*/
301-
function addRequestBreadcrumb(
302-
event: string,
303-
requestSpanData: SanitizedRequestData,
304-
req: http.ClientRequest,
305-
res?: http.IncomingMessage,
304+
function addHeadersToRequestOptions(
305+
requestOptions: RequestOptions,
306+
requestUrl: string,
307+
sentryTraceHeader: string,
308+
dynamicSamplingContext: Partial<DynamicSamplingContext> | undefined,
306309
): void {
307-
if (!getCurrentHub().getIntegration(Http)) {
308-
return;
310+
__DEBUG_BUILD__ &&
311+
logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `);
312+
const sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
313+
const sentryBaggageHeader = normalizeBaggageHeader(requestOptions, sentryBaggage);
314+
requestOptions.headers = {
315+
...requestOptions.headers,
316+
'sentry-trace': sentryTraceHeader,
317+
// Setting a header to `undefined` will crash in node so we only set the baggage header when it's defined
318+
...(sentryBaggageHeader && { baggage: sentryBaggageHeader }),
319+
};
320+
}
321+
322+
function getRequestSpanData(requestUrl: string, requestOptions: RequestOptions): SanitizedRequestData {
323+
const method = requestOptions.method || 'GET';
324+
const data: SanitizedRequestData = {
325+
url: requestUrl,
326+
'http.method': method,
327+
};
328+
if (requestOptions.hash) {
329+
// strip leading "#"
330+
data['http.fragment'] = requestOptions.hash.substring(1);
331+
}
332+
if (requestOptions.search) {
333+
// strip leading "?"
334+
data['http.query'] = requestOptions.search.substring(1);
335+
}
336+
return data;
337+
}
338+
339+
function normalizeBaggageHeader(
340+
requestOptions: RequestOptions,
341+
sentryBaggageHeader: string | undefined,
342+
): string | string[] | undefined {
343+
if (!requestOptions.headers || !requestOptions.headers.baggage) {
344+
return sentryBaggageHeader;
345+
} else if (!sentryBaggageHeader) {
346+
return requestOptions.headers.baggage as string | string[];
347+
} else if (Array.isArray(requestOptions.headers.baggage)) {
348+
return [...requestOptions.headers.baggage, sentryBaggageHeader];
309349
}
310350

311-
getCurrentHub().addBreadcrumb(
312-
{
313-
category: 'http',
314-
data: {
315-
status_code: res && res.statusCode,
316-
...requestSpanData,
317-
},
318-
type: 'http',
319-
},
320-
{
321-
event,
322-
request: req,
323-
response: res,
324-
},
325-
);
351+
// Type-cast explanation:
352+
// Technically this the following could be of type `(number | string)[]` but for the sake of simplicity
353+
// we say this is undefined behaviour, since it would not be baggage spec conform if the user did this.
354+
return [requestOptions.headers.baggage, sentryBaggageHeader] as string[];
326355
}

0 commit comments

Comments
 (0)