Skip to content

Commit 4eff471

Browse files
committed
add tracestate string to transactions and transaction events
1 parent 99821e1 commit 4eff471

File tree

3 files changed

+62
-9
lines changed

3 files changed

+62
-9
lines changed

packages/tracing/src/transaction.ts

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { getCurrentHub, Hub } from '@sentry/hub';
22
import { Event, Measurements, Transaction as TransactionInterface, TransactionContext } from '@sentry/types';
3-
import { isInstanceOf, logger } from '@sentry/utils';
3+
import { isInstanceOf, logger, unicodeToBase64 } from '@sentry/utils';
44

55
import { Span as SpanClass, SpanRecorder } from './span';
66

77
/** JSDoc */
88
export class Transaction extends SpanClass implements TransactionInterface {
99
public name: string;
10+
11+
public readonly tracestate: string;
12+
1013
private _measurements: Measurements = {};
1114

1215
/**
1316
* The reference to the current hub.
1417
*/
15-
private readonly _hub: Hub = (getCurrentHub() as unknown) as Hub;
18+
private readonly _hub: Hub;
1619

1720
private readonly _trimEnd?: boolean;
1821

@@ -26,14 +29,16 @@ export class Transaction extends SpanClass implements TransactionInterface {
2629
public constructor(transactionContext: TransactionContext, hub?: Hub) {
2730
super(transactionContext);
2831

29-
if (isInstanceOf(hub, Hub)) {
30-
this._hub = hub as Hub;
31-
}
32+
this._hub = hub && isInstanceOf(hub, Hub) ? hub : getCurrentHub();
3233

33-
this.name = transactionContext.name ? transactionContext.name : '';
34+
this.name = transactionContext.name || '';
3435

3536
this._trimEnd = transactionContext.trimEnd;
3637

38+
// _getNewTracestate only returns undefined in the absence of a client or dsn, in which case it doesn't matter what
39+
// the header values are - nothing can be sent anyway - so the third alternative here is just to make TS happy
40+
this.tracestate = transactionContext.tracestate || this._getNewTracestate() || 'things are broken';
41+
3742
// this is because transactions are also spans, and spans have a transaction pointer
3843
this.transaction = this;
3944
}
@@ -98,14 +103,15 @@ export class Transaction extends SpanClass implements TransactionInterface {
98103
}).endTimestamp;
99104
}
100105

101-
const transaction: Event = {
106+
const transactionEvent: Event = {
102107
contexts: {
103108
trace: this.getTraceContext(),
104109
},
105110
spans: finishedSpans,
106111
start_timestamp: this.startTimestamp,
107112
tags: this.tags,
108113
timestamp: this.endTimestamp,
114+
tracestate: this.tracestate,
109115
transaction: this.name,
110116
type: 'transaction',
111117
};
@@ -114,9 +120,49 @@ export class Transaction extends SpanClass implements TransactionInterface {
114120

115121
if (hasMeasurements) {
116122
logger.log('[Measurements] Adding measurements to transaction', JSON.stringify(this._measurements, undefined, 2));
117-
transaction.measurements = this._measurements;
123+
transactionEvent.measurements = this._measurements;
118124
}
119125

120-
return this._hub.captureEvent(transaction);
126+
transactionEvent.tracestate = this.tracestate;
127+
128+
return this._hub.captureEvent(transactionEvent);
129+
}
130+
131+
/**
132+
* Create a new tracestate header value
133+
*
134+
* @returns The new tracestate value, or undefined if there's no client or no dsn
135+
*/
136+
private _getNewTracestate(): string | undefined {
137+
const client = this._hub.getClient();
138+
const dsn = client?.getDsn();
139+
140+
if (!client || !dsn) {
141+
return;
142+
}
143+
144+
const { environment, release } = client.getOptions() || {};
145+
146+
const dataStr = JSON.stringify({
147+
trace_id: this.traceId,
148+
public_key: dsn.user,
149+
environment: environment || 'no environment specified',
150+
release: release || 'no release specified',
151+
});
152+
153+
// See https://www.w3.org/TR/trace-context/#tracestate-header-field-values
154+
// The spec for tracestate header values calls for a string of the form
155+
//
156+
// identifier1=value1,identifier2=value2,...
157+
//
158+
// which means the value can't include any equals signs, since they already have meaning. Equals signs are commonly
159+
// used to pad the end of base64 values though, so we have to make a substitution (periods are legal in the header
160+
// but not used in base64).
161+
try {
162+
return unicodeToBase64(dataStr).replace(/={1,2}$/, '.');
163+
} catch (err) {
164+
logger.warn(err);
165+
return '';
166+
}
121167
}
122168
}

packages/types/src/event.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface Event {
4141
type?: EventType;
4242
spans?: Span[];
4343
measurements?: Measurements;
44+
tracestate?: string;
4445
}
4546

4647
/** JSDoc */

packages/types/src/transaction.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export interface TransactionContext extends SpanContext {
2222
* If this transaction has a parent, the parent's sampling decision
2323
*/
2424
parentSampled?: boolean;
25+
26+
/**
27+
* The tracestate header value associated with this transaction, potentially inherited from a parent transaction,
28+
* which will be propagated across services to all child transactions
29+
*/
30+
tracestate?: string;
2531
}
2632

2733
/**

0 commit comments

Comments
 (0)