Skip to content

Commit 2251ce8

Browse files
committed
propagate incoming tracestate headers
1 parent 8e7d80f commit 2251ce8

File tree

8 files changed

+182
-32
lines changed

8 files changed

+182
-32
lines changed

packages/nextjs/src/utils/instrumentServer.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { captureException, deepReadDirSync, getCurrentHub, Handlers, startTransaction } from '@sentry/node';
2-
import { extractSentrytraceData, getActiveTransaction, hasTracingEnabled } from '@sentry/tracing';
3-
import { fill, isString, logger, stripUrlQueryAndFragment } from '@sentry/utils';
2+
import {
3+
extractSentrytraceData,
4+
extractTracestateData,
5+
getActiveTransaction,
6+
hasTracingEnabled,
7+
} from '@sentry/tracing';
8+
import { fill, logger, stripUrlQueryAndFragment } from '@sentry/utils';
49
import * as domain from 'domain';
510
import * as http from 'http';
611
import { default as createNextServer } from 'next';
@@ -199,10 +204,14 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
199204
// We only want to record page and API requests
200205
if (hasTracingEnabled() && shouldTraceRequest(req.url, publicDirFiles)) {
201206
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
202-
let traceparentData;
203-
if (req.headers && isString(req.headers['sentry-trace'])) {
204-
traceparentData = extractSentrytraceData(req.headers['sentry-trace'] as string);
205-
logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
207+
// Extract data from trace headers
208+
let sentrytraceData, tracestateData;
209+
if (req.headers?.['sentry-trace']) {
210+
sentrytraceData = extractSentrytraceData(req.headers['sentry-trace'] as string);
211+
logger.log(`[Tracing] Continuing trace ${sentrytraceData?.traceId}.`);
212+
}
213+
if (req.headers?.tracestate) {
214+
tracestateData = extractTracestateData(req.headers.tracestate as string);
206215
}
207216

208217
// pull off query string, if any
@@ -217,7 +226,8 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
217226
name: `${namePrefix}${reqPath}`,
218227
op: 'http.server',
219228
metadata: { requestPath: reqPath },
220-
...traceparentData,
229+
...sentrytraceData,
230+
...(tracestateData && { metadata: { tracestate: tracestateData } }),
221231
},
222232
// extra context passed to the `tracesSampler`
223233
{ request: req },

packages/nextjs/src/utils/withSentry.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { captureException, flush, getCurrentHub, Handlers, startTransaction } from '@sentry/node';
2-
import { extractSentrytraceData, hasTracingEnabled } from '@sentry/tracing';
2+
import { extractSentrytraceData, extractTracestateData, hasTracingEnabled } from '@sentry/tracing';
33
import { Transaction } from '@sentry/types';
4-
import { addExceptionMechanism, isString, logger, stripUrlQueryAndFragment } from '@sentry/utils';
4+
import { addExceptionMechanism, logger, stripUrlQueryAndFragment } from '@sentry/utils';
55
import * as domain from 'domain';
66
import { NextApiHandler, NextApiResponse } from 'next';
77

@@ -36,11 +36,14 @@ export const withSentry = (handler: NextApiHandler): WrappedNextApiHandler => {
3636
currentScope.addEventProcessor(event => parseRequest(event, req));
3737

3838
if (hasTracingEnabled()) {
39-
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
40-
let traceparentData;
41-
if (req.headers && isString(req.headers['sentry-trace'])) {
42-
traceparentData = extractSentrytraceData(req.headers['sentry-trace'] as string);
43-
logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
39+
// Extract data from trace headers
40+
let sentrytraceData, tracestateData;
41+
if (req.headers?.['sentry-trace']) {
42+
sentrytraceData = extractSentrytraceData(req.headers['sentry-trace'] as string);
43+
logger.log(`[Tracing] Continuing trace ${sentrytraceData?.traceId}.`);
44+
}
45+
if (req.headers?.tracestate) {
46+
tracestateData = extractTracestateData(req.headers.tracestate as string);
4447
}
4548

4649
const url = `${req.url}`;
@@ -60,7 +63,8 @@ export const withSentry = (handler: NextApiHandler): WrappedNextApiHandler => {
6063
{
6164
name: `${reqMethod}${reqPath}`,
6265
op: 'http.server',
63-
...traceparentData,
66+
...sentrytraceData,
67+
...(tracestateData && { metadata: { tracestate: tracestateData } }),
6468
},
6569
// extra context passed to the `tracesSampler`
6670
{ request: req },

packages/node/src/handlers.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable max-lines */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
33
import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core';
4-
import { extractSentrytraceData, Span } from '@sentry/tracing';
4+
import { extractSentrytraceData, extractTracestateData, Span } from '@sentry/tracing';
55
import { Event, ExtractedNodeRequestData, RequestSessionStatus, Transaction } from '@sentry/types';
66
import { isPlainObject, isString, logger, normalize, stripUrlQueryAndFragment } from '@sentry/utils';
77
import * as cookie from 'cookie';
@@ -53,17 +53,21 @@ export function tracingHandler(): (
5353
res: http.ServerResponse,
5454
next: (error?: any) => void,
5555
): void {
56-
// If there is a trace header set, we extract the data from it (parentSpanId, traceId, and sampling decision)
57-
let sentrytraceData;
58-
if (req.headers && isString(req.headers['sentry-trace'])) {
56+
// Extract data from trace headers
57+
let sentrytraceData, tracestateData;
58+
if (req.headers?.['sentry-trace']) {
5959
sentrytraceData = extractSentrytraceData(req.headers['sentry-trace'] as string);
6060
}
61+
if (req.headers?.tracestate) {
62+
tracestateData = extractTracestateData(req.headers.tracestate as string);
63+
}
6164

6265
const transaction = startTransaction(
6366
{
6467
name: extractExpressTransactionName(req, { path: true, method: true }),
6568
op: 'http.server',
6669
...sentrytraceData,
70+
...(tracestateData && { metadata: { tracestate: tracestateData } }),
6771
},
6872
// extra context passed to the tracesSampler
6973
{ request: extractRequestData(req) },

packages/node/test/handlers.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,11 @@ describe('tracingHandler', () => {
325325
expect(startTransaction).toHaveBeenCalled();
326326
});
327327

328-
it("pulls parent's data from tracing header on the request", () => {
329-
req.headers = { 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0' };
328+
it('pulls data from tracing headers on the request', () => {
329+
req.headers = {
330+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
331+
tracestate: 'sentry=doGsaREgReaT',
332+
};
330333

331334
sentryTracingMiddleware(req, res, next);
332335

@@ -336,6 +339,7 @@ describe('tracingHandler', () => {
336339
expect(transaction.traceId).toEqual('12312012123120121231201212312012');
337340
expect(transaction.parentSpanId).toEqual('1121201211212012');
338341
expect(transaction.sampled).toEqual(false);
342+
expect(transaction.metadata?.tracestate).toEqual({ sentry: 'sentry=doGsaREgReaT' });
339343
});
340344

341345
it('extracts request data for sampling context', () => {

packages/serverless/src/awslambda.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import {
99
withScope,
1010
} from '@sentry/node';
1111
import * as Sentry from '@sentry/node';
12-
import { extractSentrytraceData } from '@sentry/tracing';
12+
import { extractSentrytraceData, extractTracestateData } from '@sentry/tracing';
1313
import { Integration } from '@sentry/types';
14-
import { isString, logger } from '@sentry/utils';
14+
import { logger } from '@sentry/utils';
1515
// NOTE: I have no idea how to fix this right now, and don't want to waste more time, as it builds just fine — Kamil
1616
// eslint-disable-next-line import/no-unresolved
1717
import { Context, Handler } from 'aws-lambda';
@@ -201,7 +201,7 @@ export function wrapHandler<TEvent, TResult>(
201201
};
202202
let timeoutWarningTimer: NodeJS.Timeout;
203203

204-
// AWSLambda is like Express. It makes a distinction about handlers based on it's last argument
204+
// AWSLambda is like Express. It makes a distinction about handlers based on its last argument
205205
// async (event) => async handler
206206
// async (event, context) => async handler
207207
// (event, context, callback) => sync handler
@@ -252,16 +252,22 @@ export function wrapHandler<TEvent, TResult>(
252252
}, timeoutWarningDelay);
253253
}
254254

255-
// Applying `sentry-trace` to context
256-
let sentrytraceData;
255+
// Extract tracing data from headers
256+
let sentrytraceData, tracestateData;
257257
const eventWithHeaders = event as { headers?: { [key: string]: string } };
258-
if (eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])) {
258+
259+
if (eventWithHeaders.headers?.['sentry-trace']) {
259260
sentrytraceData = extractSentrytraceData(eventWithHeaders.headers['sentry-trace'] as string);
260261
}
262+
if (eventWithHeaders.headers?.tracestate) {
263+
tracestateData = extractTracestateData(eventWithHeaders.headers.tracestate as string);
264+
}
265+
261266
const transaction = startTransaction({
262267
name: context.functionName,
263268
op: 'awslambda.handler',
264269
...sentrytraceData,
270+
...(tracestateData && { metadata: { tracestate: tracestateData } }),
265271
});
266272

267273
const hub = getCurrentHub();

packages/serverless/src/gcpfunction/http.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { captureException, flush, getCurrentHub, Handlers, startTransaction } from '@sentry/node';
2-
import { extractSentrytraceData } from '@sentry/tracing';
3-
import { isString, logger, stripUrlQueryAndFragment } from '@sentry/utils';
2+
import { extractSentrytraceData, extractTracestateData } from '@sentry/tracing';
3+
import { logger, stripUrlQueryAndFragment } from '@sentry/utils';
44

55
import { domainify, getActiveDomain, proxyFunction } from './../utils';
66
import { HttpFunction, WrapperOptions } from './general';
@@ -49,16 +49,22 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial<HttpFunctionWr
4949
const reqMethod = (req.method || '').toUpperCase();
5050
const reqUrl = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
5151

52-
// Applying `sentry-trace` to context
53-
let sentrytraceData;
52+
// Extract tracing data from headers
53+
let sentrytraceData, tracestateData;
5454
const reqWithHeaders = req as { headers?: { [key: string]: string } };
55-
if (reqWithHeaders.headers && isString(reqWithHeaders.headers['sentry-trace'])) {
55+
56+
if (reqWithHeaders.headers?.['sentry-trace']) {
5657
sentrytraceData = extractSentrytraceData(reqWithHeaders.headers['sentry-trace'] as string);
5758
}
59+
if (reqWithHeaders.headers?.tracestate) {
60+
tracestateData = extractTracestateData(reqWithHeaders.headers.tracestate as string);
61+
}
62+
5863
const transaction = startTransaction({
5964
name: `${reqMethod} ${reqUrl}`,
6065
op: 'gcp.function.http',
6166
...sentrytraceData,
67+
...(tracestateData && { metadata: { tracestate: tracestateData } }),
6268
});
6369

6470
// getCurrentHub() is expected to use current active domain as a carrier

packages/serverless/test/awslambda.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,35 @@ describe('AWSLambda', () => {
221221
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
222222
});
223223

224+
test('incoming trace headers are correctly parsed and used', async () => {
225+
expect.assertions(1);
226+
227+
fakeEvent.headers = {
228+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
229+
tracestate: 'sentry=doGsaREgReaT,maisey=silly,charlie=goofy',
230+
};
231+
232+
const handler: Handler = (_event, _context, callback) => {
233+
expect(Sentry.startTransaction).toBeCalledWith(
234+
expect.objectContaining({
235+
traceId: '12312012123120121231201212312012',
236+
parentSpanId: '1121201211212012',
237+
parentSampled: false,
238+
metadata: {
239+
tracestate: {
240+
sentry: 'sentry=doGsaREgReaT',
241+
thirdparty: 'maisey=silly,charlie=goofy',
242+
},
243+
},
244+
}),
245+
);
246+
247+
callback(undefined, { its: 'fine' });
248+
};
249+
const wrappedHandler = wrapHandler(handler);
250+
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
251+
});
252+
224253
test('capture error', async () => {
225254
expect.assertions(10);
226255

@@ -278,6 +307,35 @@ describe('AWSLambda', () => {
278307
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
279308
});
280309

310+
test('incoming trace headers are correctly parsed and used', async () => {
311+
expect.assertions(1);
312+
313+
fakeEvent.headers = {
314+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
315+
tracestate: 'sentry=doGsaREgReaT,maisey=silly,charlie=goofy',
316+
};
317+
318+
const handler: Handler = async (_event, _context, callback) => {
319+
expect(Sentry.startTransaction).toBeCalledWith(
320+
expect.objectContaining({
321+
traceId: '12312012123120121231201212312012',
322+
parentSpanId: '1121201211212012',
323+
parentSampled: false,
324+
metadata: {
325+
tracestate: {
326+
sentry: 'sentry=doGsaREgReaT',
327+
thirdparty: 'maisey=silly,charlie=goofy',
328+
},
329+
},
330+
}),
331+
);
332+
333+
callback(undefined, { its: 'fine' });
334+
};
335+
const wrappedHandler = wrapHandler(handler);
336+
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
337+
});
338+
281339
test('capture error', async () => {
282340
expect.assertions(10);
283341

@@ -328,6 +386,35 @@ describe('AWSLambda', () => {
328386
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
329387
});
330388

389+
test('incoming trace headers are correctly parsed and used', async () => {
390+
expect.assertions(1);
391+
392+
fakeEvent.headers = {
393+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
394+
tracestate: 'sentry=doGsaREgReaT,maisey=silly,charlie=goofy',
395+
};
396+
397+
const handler: Handler = async (_event, _context, callback) => {
398+
expect(Sentry.startTransaction).toBeCalledWith(
399+
expect.objectContaining({
400+
traceId: '12312012123120121231201212312012',
401+
parentSpanId: '1121201211212012',
402+
parentSampled: false,
403+
metadata: {
404+
tracestate: {
405+
sentry: 'sentry=doGsaREgReaT',
406+
thirdparty: 'maisey=silly,charlie=goofy',
407+
},
408+
},
409+
}),
410+
);
411+
412+
callback(undefined, { its: 'fine' });
413+
};
414+
const wrappedHandler = wrapHandler(handler);
415+
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
416+
});
417+
331418
test('capture error', async () => {
332419
expect.assertions(10);
333420

packages/serverless/test/gcpfunction.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,35 @@ describe('GCPFunction', () => {
120120
expect(Sentry.flush).toBeCalledWith(2000);
121121
});
122122

123+
test('incoming trace headers are correctly parsed and used', async () => {
124+
expect.assertions(1);
125+
126+
const handler: HttpFunction = (_req, res) => {
127+
res.statusCode = 200;
128+
res.end();
129+
};
130+
const wrappedHandler = wrapHttpFunction(handler);
131+
const traceHeaders = {
132+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
133+
tracestate: 'sentry=doGsaREgReaT,maisey=silly,charlie=goofy',
134+
};
135+
await handleHttp(wrappedHandler, traceHeaders);
136+
137+
expect(Sentry.startTransaction).toBeCalledWith(
138+
expect.objectContaining({
139+
traceId: '12312012123120121231201212312012',
140+
parentSpanId: '1121201211212012',
141+
parentSampled: false,
142+
metadata: {
143+
tracestate: {
144+
sentry: 'sentry=doGsaREgReaT',
145+
thirdparty: 'maisey=silly,charlie=goofy',
146+
},
147+
},
148+
}),
149+
);
150+
});
151+
123152
test('capture error', async () => {
124153
expect.assertions(5);
125154

0 commit comments

Comments
 (0)