1
1
import { getCurrentHub , Hub } from '@sentry/hub' ;
2
2
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' ;
4
4
5
5
import { Span as SpanClass , SpanRecorder } from './span' ;
6
6
7
7
/** JSDoc */
8
8
export class Transaction extends SpanClass implements TransactionInterface {
9
9
public name : string ;
10
+
11
+ public readonly tracestate : string ;
12
+
10
13
private _measurements : Measurements = { } ;
11
14
12
15
/**
13
16
* The reference to the current hub.
14
17
*/
15
- private readonly _hub : Hub = ( getCurrentHub ( ) as unknown ) as Hub ;
18
+ private readonly _hub : Hub ;
16
19
17
20
private readonly _trimEnd ?: boolean ;
18
21
@@ -26,14 +29,16 @@ export class Transaction extends SpanClass implements TransactionInterface {
26
29
public constructor ( transactionContext : TransactionContext , hub ?: Hub ) {
27
30
super ( transactionContext ) ;
28
31
29
- if ( isInstanceOf ( hub , Hub ) ) {
30
- this . _hub = hub as Hub ;
31
- }
32
+ this . _hub = hub && isInstanceOf ( hub , Hub ) ? hub : getCurrentHub ( ) ;
32
33
33
- this . name = transactionContext . name ? transactionContext . name : '' ;
34
+ this . name = transactionContext . name || '' ;
34
35
35
36
this . _trimEnd = transactionContext . trimEnd ;
36
37
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
+
37
42
// this is because transactions are also spans, and spans have a transaction pointer
38
43
this . transaction = this ;
39
44
}
@@ -98,14 +103,15 @@ export class Transaction extends SpanClass implements TransactionInterface {
98
103
} ) . endTimestamp ;
99
104
}
100
105
101
- const transaction : Event = {
106
+ const transactionEvent : Event = {
102
107
contexts : {
103
108
trace : this . getTraceContext ( ) ,
104
109
} ,
105
110
spans : finishedSpans ,
106
111
start_timestamp : this . startTimestamp ,
107
112
tags : this . tags ,
108
113
timestamp : this . endTimestamp ,
114
+ tracestate : this . tracestate ,
109
115
transaction : this . name ,
110
116
type : 'transaction' ,
111
117
} ;
@@ -114,9 +120,44 @@ export class Transaction extends SpanClass implements TransactionInterface {
114
120
115
121
if ( hasMeasurements ) {
116
122
logger . log ( '[Measurements] Adding measurements to transaction' , JSON . stringify ( this . _measurements , undefined , 2 ) ) ;
117
- transaction . measurements = this . _measurements ;
123
+ transactionEvent . measurements = this . _measurements ;
124
+ }
125
+
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 ;
118
142
}
119
143
120
- return this . _hub . captureEvent ( transaction ) ;
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
+ return UnicodeToBase64 ( dataStr ) . replace ( / = { 1 , 2 } $ / , '.' ) ;
121
162
}
122
163
}
0 commit comments