Skip to content

Commit e02c27f

Browse files
authored
feat(node): Populate propagation context using env variables (#8422)
Requires #8421 to be merged ref #8352 This PR adds support for https://github.com/getsentry/rfcs/blob/main/text/0071-continue-trace-over-process-boundaries.md via propagation context. When the Node SDK initializes, it grabs `process.env.SENTRY_TRACE` and `process.env.SENTRY_BAGGAGE` and uses them to populate the existing propagation context. In future PRs (tracked by #8352), we will be adding support for transactions/outgoing requests to use propagation context, but for now we are only storing in propagation context, not doing anything with it.
1 parent f1ede57 commit e02c27f

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

packages/node/src/sdk.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
logger,
1414
nodeStackLineParser,
1515
stackParserFromStackParserOptions,
16+
tracingContextFromHeaders,
1617
} from '@sentry/utils';
1718

1819
import { setNodeAsyncContextStrategy } from './async';
@@ -129,8 +130,9 @@ export function init(options: NodeOptions = {}): void {
129130
options.dsn = process.env.SENTRY_DSN;
130131
}
131132

132-
if (options.tracesSampleRate === undefined && process.env.SENTRY_TRACES_SAMPLE_RATE) {
133-
const tracesSampleRate = parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE);
133+
const sentryTracesSampleRate = process.env.SENTRY_TRACES_SAMPLE_RATE;
134+
if (options.tracesSampleRate === undefined && sentryTracesSampleRate) {
135+
const tracesSampleRate = parseFloat(sentryTracesSampleRate);
134136
if (isFinite(tracesSampleRate)) {
135137
options.tracesSampleRate = tracesSampleRate;
136138
}
@@ -171,6 +173,8 @@ export function init(options: NodeOptions = {}): void {
171173
if (options.autoSessionTracking) {
172174
startSessionTracking();
173175
}
176+
177+
updateScopeFromEnvVariables();
174178
}
175179

176180
/**
@@ -285,3 +289,19 @@ function startSessionTracking(): void {
285289
if (session && !terminalStates.includes(session.status)) hub.endSession();
286290
});
287291
}
292+
293+
/**
294+
* Update scope and propagation context based on environmental variables.
295+
*
296+
* See https://github.com/getsentry/rfcs/blob/main/text/0071-continue-trace-over-process-boundaries.md
297+
* for more details.
298+
*/
299+
function updateScopeFromEnvVariables(): void {
300+
const sentryUseEnvironment = (process.env.SENTRY_USE_ENVIRONMENT || '').toLowerCase();
301+
if (!['false', 'n', 'no', 'off', '0'].includes(sentryUseEnvironment)) {
302+
const sentryTraceEnv = process.env.SENTRY_TRACE;
303+
const baggageEnv = process.env.SENTRY_BAGGAGE;
304+
const { propagationContext } = tracingContextFromHeaders(sentryTraceEnv, baggageEnv);
305+
getCurrentHub().getScope().setPropagationContext(propagationContext);
306+
}
307+
}

packages/node/test/index.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,4 +488,47 @@ describe('SentryNode initialization', () => {
488488
expect(instrumenter).toEqual('otel');
489489
});
490490
});
491+
492+
describe('propagation context', () => {
493+
beforeEach(() => {
494+
process.env.SENTRY_TRACE = '12312012123120121231201212312012-1121201211212012-0';
495+
process.env.SENTRY_BAGGAGE = 'sentry-release=1.0.0,sentry-environment=production';
496+
497+
getCurrentHub().getScope().clear();
498+
});
499+
500+
afterEach(() => {
501+
delete process.env.SENTRY_TRACE;
502+
delete process.env.SENTRY_BAGGAGE;
503+
});
504+
505+
it('reads from environmental variables', () => {
506+
init({ dsn });
507+
508+
// @ts-expect-error accessing private method for test
509+
expect(getCurrentHub().getScope()._propagationContext).toEqual({
510+
traceId: '12312012123120121231201212312012',
511+
parentSpanId: '1121201211212012',
512+
spanId: expect.any(String),
513+
sampled: false,
514+
dsc: {
515+
release: '1.0.0',
516+
environment: 'production',
517+
},
518+
});
519+
});
520+
521+
it.each(['false', 'False', 'FALSE', 'n', 'no', 'No', 'NO', 'off', 'Off', 'OFF', '0'])(
522+
'does not read from environmental variable if SENTRY_USE_ENVIRONMENT is set to %s',
523+
useEnvValue => {
524+
process.env.SENTRY_USE_ENVIRONMENT = useEnvValue;
525+
init({ dsn });
526+
527+
// @ts-expect-error accessing private method for test
528+
expect(getCurrentHub().getScope()._propagationContext.traceId).not.toEqual('12312012123120121231201212312012');
529+
530+
delete process.env.SENTRY_USE_ENVIRONMENT;
531+
},
532+
);
533+
});
491534
});

packages/utils/src/tracing.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { TraceparentData } from '@sentry/types';
1+
import type { DynamicSamplingContext, PropagationContext, TraceparentData } from '@sentry/types';
2+
3+
import { baggageHeaderToDynamicSamplingContext } from './baggage';
4+
import { uuid4 } from './misc';
25

36
export const TRACEPARENT_REGEXP = new RegExp(
47
'^[ \\t]*' + // whitespace
@@ -36,3 +39,40 @@ export function extractTraceparentData(traceparent: string): TraceparentData | u
3639
parentSpanId: matches[2],
3740
};
3841
}
42+
43+
/**
44+
* Create tracing context from incoming headers.
45+
*/
46+
export function tracingContextFromHeaders(
47+
sentryTrace: Parameters<typeof extractTraceparentData>[0] = '',
48+
baggage: Parameters<typeof baggageHeaderToDynamicSamplingContext>[0] = '',
49+
): {
50+
traceparentData: ReturnType<typeof extractTraceparentData>;
51+
dynamicSamplingContext: ReturnType<typeof baggageHeaderToDynamicSamplingContext>;
52+
propagationContext: PropagationContext;
53+
} {
54+
const traceparentData = extractTraceparentData(sentryTrace);
55+
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggage);
56+
57+
const { traceId, parentSpanId, parentSampled } = traceparentData || {};
58+
59+
const propagationContext: PropagationContext = {
60+
traceId: traceId || uuid4(),
61+
spanId: uuid4().substring(16),
62+
sampled: parentSampled === undefined ? false : parentSampled,
63+
};
64+
65+
if (parentSpanId) {
66+
propagationContext.parentSpanId = parentSpanId;
67+
}
68+
69+
if (dynamicSamplingContext) {
70+
propagationContext.dsc = dynamicSamplingContext as DynamicSamplingContext;
71+
}
72+
73+
return {
74+
traceparentData,
75+
dynamicSamplingContext,
76+
propagationContext,
77+
};
78+
}

0 commit comments

Comments
 (0)