Skip to content

Commit 55f87b7

Browse files
committed
add extractTracestateData() function
1 parent 678d601 commit 55f87b7

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

packages/tracing/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export { addExtensionMethods };
2424

2525
export {
2626
extractSentrytraceData,
27+
extractTracestateData,
2728
getActiveTransaction,
2829
hasTracingEnabled,
2930
SENTRY_TRACE_REGEX,

packages/tracing/src/utils.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,40 @@ export const SENTRY_TRACE_REGEX = new RegExp(
1010
'[ \\t]*$', // whitespace
1111
);
1212

13+
// This is a normal base64 regex, modified to reflect that fact that we strip the trailing = or == off
14+
const BASE64_STRIPPED_REGEX = new RegExp(
15+
// for our purposes, we want to test against the entire string, so enforce that there's nothing before the main regex
16+
`^` +
17+
// any of the characters in the base64 "alphabet", in multiples of 4
18+
'([a-zA-Z0-9+/]{4})*' +
19+
// either nothing or 2 or 3 base64-alphabet characters (see
20+
// https://en.wikipedia.org/wiki/Base64#Decoding_Base64_without_padding
21+
// for why there's never only 1 extra character)
22+
'([a-zA-Z0-9+/]{2,3})?' +
23+
// see above re: matching entire string
24+
`$`,
25+
);
26+
27+
// comma-delimited list of entries of the form `xxx=yyy`
28+
const tracestateEntry = '[^=]+=[^=]+';
29+
const TRACESTATE_ENTRIES_REGEX = new RegExp(
30+
// one or more xxxxx=yyyy entries
31+
`^(${tracestateEntry})+` +
32+
// each entry except the last must be followed by a comma
33+
'(,|$)',
34+
);
35+
36+
// this doesn't check that the value is valid, just that there's something there of the form `sentry=xxxx`
37+
const SENTRY_TRACESTATE_ENTRY_REGEX = new RegExp(
38+
// either sentry is the first entry or there's stuff immediately before it, ending in a commma (this prevents matching
39+
// something like `coolsentry=xxx`)
40+
'(?:^|.+,)' +
41+
// sentry's part, not including the potential comma
42+
'(sentry=[^,]*)' +
43+
// either there's a comma and another vendor's entry or we end
44+
'(?:,.+|$)',
45+
);
46+
1347
/**
1448
* Determines if tracing is currently enabled.
1549
*
@@ -53,6 +87,46 @@ export function extractSentrytraceData(header: string): TraceparentData | undefi
5387
return undefined;
5488
}
5589

90+
/**
91+
* Extract data from an incoming `tracestate` header
92+
*
93+
* @param header
94+
* @returns Object containing data from the header
95+
*/
96+
export function extractTracestateData(header: string): { sentry?: string; thirdparty?: string } {
97+
let sentryEntry, thirdPartyEntry, before, after;
98+
99+
// find sentry's entry, if any
100+
const sentryMatch = SENTRY_TRACESTATE_ENTRY_REGEX.exec(header);
101+
102+
if (sentryMatch !== null) {
103+
sentryEntry = sentryMatch[1];
104+
105+
// remove the commas after the split so we don't end up with `xxx=yyy,,zzz=qqq` (double commas) when we put them
106+
// back together
107+
[before, after] = header.split(sentryEntry).map(s => s.replace(/^,*|,*$/g, ''));
108+
109+
// extract sentry's value from its entry and test to make sure it's valid; if it isn't, discard the entire entry
110+
// so that a new one will be created by the Transaction constructor
111+
const sentryValue = sentryEntry.replace('sentry=', '');
112+
if (!BASE64_STRIPPED_REGEX.test(sentryValue)) {
113+
sentryEntry = undefined;
114+
}
115+
} else {
116+
// this could just as well be `before`; we just need to get the thirdparty data into one or the other since
117+
// there's no valid Sentry entry
118+
after = header;
119+
}
120+
121+
// if either thirdparty part is invalid or empty, remove it before gluing them together
122+
const validThirdpartyEntries = [before, after].filter(x => TRACESTATE_ENTRIES_REGEX.test(x || ''));
123+
if (validThirdpartyEntries.length) {
124+
thirdPartyEntry = validThirdpartyEntries.join(',');
125+
}
126+
127+
return { sentry: sentryEntry, thirdparty: thirdPartyEntry };
128+
}
129+
56130
/** Grabs active transaction off scope, if any */
57131
export function getActiveTransaction<T extends Transaction>(hub: Hub = getCurrentHub()): T | undefined {
58132
return hub?.getScope()?.getTransaction() as T | undefined;

packages/tracing/test/utils.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extractSentrytraceData } from '../src/utils';
1+
import { extractSentrytraceData, extractTracestateData } from '../src/utils';
22

33
describe('extractSentrytraceData', () => {
44
test('no sample', () => {
@@ -62,3 +62,59 @@ describe('extractSentrytraceData', () => {
6262
expect(extractSentrytraceData('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-x')).toBeUndefined();
6363
});
6464
});
65+
66+
describe('extractTracestateData', () => {
67+
it.each([
68+
// sentry only
69+
['sentry only', 'sentry=doGsaREgReaT', 'sentry=doGsaREgReaT', undefined],
70+
// sentry only, invalid (`!` isn't a valid base64 character)
71+
['sentry only, invalid', 'sentry=doGsaREgReaT!', undefined, undefined],
72+
// stuff before
73+
['stuff before', 'maisey=silly,sentry=doGsaREgReaT', 'sentry=doGsaREgReaT', 'maisey=silly'],
74+
// stuff after
75+
['stuff after', 'sentry=doGsaREgReaT,maisey=silly', 'sentry=doGsaREgReaT', 'maisey=silly'],
76+
// stuff before and after
77+
[
78+
'stuff before and after',
79+
'charlie=goofy,sentry=doGsaREgReaT,maisey=silly',
80+
'sentry=doGsaREgReaT',
81+
'charlie=goofy,maisey=silly',
82+
],
83+
// multiple before
84+
[
85+
'multiple before',
86+
'charlie=goofy,maisey=silly,sentry=doGsaREgReaT',
87+
'sentry=doGsaREgReaT',
88+
'charlie=goofy,maisey=silly',
89+
],
90+
// multiple after
91+
[
92+
'multiple after',
93+
'sentry=doGsaREgReaT,charlie=goofy,maisey=silly',
94+
'sentry=doGsaREgReaT',
95+
'charlie=goofy,maisey=silly',
96+
],
97+
// multiple before and after
98+
[
99+
'multiple before and after',
100+
'charlie=goofy,maisey=silly,sentry=doGsaREgReaT,bodhi=floppy,cory=loyal',
101+
'sentry=doGsaREgReaT',
102+
'charlie=goofy,maisey=silly,bodhi=floppy,cory=loyal',
103+
],
104+
// only third-party data
105+
['only third-party data', 'maisey=silly', undefined, 'maisey=silly'],
106+
// invalid third-party data, valid sentry data
107+
[
108+
'invalid third-party data, valid sentry data',
109+
'maisey_is_silly,sentry=doGsaREgReaT',
110+
'sentry=doGsaREgReaT',
111+
undefined,
112+
],
113+
// valid third party data, invalid sentry data
114+
['valid third-party data, invalid sentry data', 'maisey=silly,sentry=doGsaREgReaT!', undefined, 'maisey=silly'],
115+
// nothing valid at all
116+
['nothing valid at all', 'maisey_is_silly,sentry=doGsaREgReaT!', undefined, undefined],
117+
])('%s', (_testTitle: string, header: string, sentry?: string, thirdparty?: string): void => {
118+
expect(extractTracestateData(header)).toEqual({ sentry, thirdparty });
119+
});
120+
});

0 commit comments

Comments
 (0)