Skip to content

Commit 68331b9

Browse files
committed
feat(tracing): Add user data to tracestate header (#3343)
1 parent 4dbc5cc commit 68331b9

File tree

9 files changed

+312
-86
lines changed

9 files changed

+312
-86
lines changed

packages/core/test/lib/request.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('eventToSentryRequest', () => {
2828
environment: 'dogpark',
2929
event_id: '0908201304152013',
3030
release: 'off.leash.park',
31-
user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' },
31+
user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12', segment: 'bigs' },
3232
};
3333

3434
describe('error/message events', () => {
@@ -65,13 +65,15 @@ describe('eventToSentryRequest', () => {
6565
// computeTracestateValue({
6666
// trace_id: '1231201211212012',
6767
// environment: 'dogpark',
68-
// release: 'off.leash.park',
6968
// public_key: 'dogsarebadatkeepingsecrets',
69+
// release: 'off.leash.park',
70+
// user: { id: '1121', segment: 'bigs' },
7071
// }),
7172
tracestate: {
7273
sentry:
73-
'sentry=eyJ0cmFjZV9pZCI6IjEyMzEyMDEyMTEyMTIwMTIiLCJlbnZpcm9ubWVudCI6ImRvZ3BhcmsiLCJyZWxlYXNlIjoib2ZmLmxlYXNo' +
74-
'LnBhcmsiLCJwdWJsaWNfa2V5IjoiZG9nc2FyZWJhZGF0a2VlcGluZ3NlY3JldHMifQ',
74+
'sentry=eyJ0cmFjZV9pZCI6IjEyMzEyMDEyMTEyMTIwMTIiLCJlbnZpcm9ubWVudCI6ImRvZ3BhcmsiLCJwdWJsaWNfa2V5Ijo' +
75+
'iZG9nc2FyZWJhZGF0a2VlcGluZ3NlY3JldHMiLCJyZWxlYXNlIjoib2ZmLmxlYXNoLnBhcmsiLCJ1c2VyIjp7ImlkIjoiMTEyM' +
76+
'SIsInNlZ21lbnQiOiJiaWdzIn19',
7577
},
7678
},
7779
spans: [],
@@ -160,10 +162,11 @@ describe('eventToSentryRequest', () => {
160162

161163
expect(envelope.envelopeHeader.trace).toBeDefined();
162164
expect(envelope.envelopeHeader.trace).toEqual({
165+
trace_id: '1231201211212012',
163166
environment: 'dogpark',
164167
public_key: 'dogsarebadatkeepingsecrets',
165168
release: 'off.leash.park',
166-
trace_id: '1231201211212012',
169+
user: { id: '1121', segment: 'bigs' },
167170
});
168171
});
169172
});

packages/tracing/src/span.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-lines */
2-
import { getCurrentHub } from '@sentry/hub';
3-
import { Hub, Primitive, Span as SpanInterface, SpanContext, TraceHeaders, Transaction } from '@sentry/types';
2+
import { getCurrentHub, Hub } from '@sentry/hub';
3+
import { Primitive, Span as SpanInterface, SpanContext, TraceHeaders, Transaction } from '@sentry/types';
44
import { dropUndefinedKeys, logger, timestampWithMs, uuid4 } from '@sentry/utils';
55

66
import { SpanStatus } from './spanstatus';
@@ -288,6 +288,14 @@ export class Span implements SpanInterface {
288288
* @inheritDoc
289289
*/
290290
public getTraceHeaders(): TraceHeaders {
291+
// if this span is part of a transaction, but that transaction doesn't yet have a tracestate value, create one
292+
if (this.transaction && !this.transaction?.metadata.tracestate?.sentry) {
293+
this.transaction.metadata.tracestate = {
294+
...this.transaction.metadata.tracestate,
295+
sentry: this._getNewTracestate(),
296+
};
297+
}
298+
291299
const tracestate = this._toTracestate();
292300

293301
return {
@@ -357,8 +365,11 @@ export class Span implements SpanInterface {
357365
*
358366
* @returns The new Sentry tracestate entry, or undefined if there's no client or no dsn
359367
*/
360-
protected _getNewTracestate(hub: Hub = getCurrentHub()): string | undefined {
368+
protected _getNewTracestate(): string | undefined {
369+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
370+
const hub = ((this.transaction as any)?._hub as Hub) || getCurrentHub();
361371
const client = hub.getClient();
372+
const { id: userId, segment: userSegment } = hub.getScope()?.getUser() || {};
362373
const dsn = client?.getDsn();
363374

364375
if (!client || !dsn) {
@@ -372,11 +383,12 @@ export class Span implements SpanInterface {
372383
// `dsn.publicKey` required and remove the `!`.
373384

374385
return `sentry=${computeTracestateValue({
375-
traceId: this.traceId,
386+
trace_id: this.traceId,
376387
environment,
377388
release,
378389
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
379-
publicKey: dsn.publicKey!,
390+
public_key: dsn.publicKey!,
391+
user: { id: userId, segment: userSegment },
380392
})}`;
381393
}
382394

@@ -392,9 +404,11 @@ export class Span implements SpanInterface {
392404
}
393405

394406
/**
395-
* Return a tracestate-compatible header string. Returns undefined if there is no client or no DSN.
407+
* Return a tracestate-compatible header string, including both sentry and third-party data (if any). Returns
408+
* undefined if there is no client or no DSN.
396409
*/
397410
private _toTracestate(): string | undefined {
411+
// if this is an orphan span, create a new tracestate value
398412
const sentryTracestate = this.transaction?.metadata?.tracestate?.sentry || this._getNewTracestate();
399413
let thirdpartyTracestate = this.transaction?.metadata?.tracestate?.thirdparty;
400414

packages/tracing/src/transaction.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ export class Transaction extends SpanClass implements TransactionInterface {
4040
this._trimEnd = transactionContext.trimEnd;
4141
this._hub = hub || getCurrentHub();
4242

43-
// create a new sentry tracestate value if we didn't inherit one
44-
if (!this.metadata.tracestate?.sentry) {
45-
this.metadata.tracestate = { ...this.metadata.tracestate, sentry: this._getNewTracestate(this._hub) };
46-
}
47-
4843
// this is because transactions are also spans, and spans have a transaction pointer
4944
this.transaction = this;
5045
}
@@ -117,6 +112,11 @@ export class Transaction extends SpanClass implements TransactionInterface {
117112
}).endTimestamp;
118113
}
119114

115+
// ensure that we have a tracestate to attach to the envelope header
116+
if (!this.metadata.tracestate?.sentry) {
117+
this.metadata.tracestate = { ...this.metadata.tracestate, sentry: this._getNewTracestate() };
118+
}
119+
120120
const transaction: Event = {
121121
contexts: {
122122
trace: this.getTraceContext(),

packages/tracing/src/utils.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,11 @@ export function secToMs(time: number): number {
145145
export { stripUrlQueryAndFragment } from '@sentry/utils';
146146

147147
type SentryTracestateData = {
148-
traceId: string;
148+
trace_id: string;
149149
environment: string | undefined | null;
150150
release: string | undefined | null;
151-
publicKey: string;
151+
public_key: string;
152+
user: { id: string | undefined | null; segment: string | undefined | null };
152153
};
153154

154155
/**
@@ -159,9 +160,11 @@ type SentryTracestateData = {
159160
*/
160161
export function computeTracestateValue(data: SentryTracestateData): string {
161162
// `JSON.stringify` will drop keys with undefined values, but not ones with null values, so this prevents
162-
// `environment` and `release` from being dropped if they haven't been set by `Sentry.init`
163+
// these values from being dropped if they haven't been set by `Sentry.init`
163164
data.environment = data.environment || null;
164165
data.release = data.release || null;
166+
data.user.id = data.user.id || null;
167+
data.user.segment = data.user.segment || null;
165168

166169
// See https://www.w3.org/TR/trace-context/#tracestate-header-field-values
167170
// The spec for tracestate header values calls for a string of the form

0 commit comments

Comments
 (0)