UNPKG

7.54 kBJavaScriptView Raw
1Object.defineProperty(exports, '__esModule', { value: true });
2
3const utils = require('@sentry/utils');
4const constants = require('../constants.js');
5const hub = require('../hub.js');
6const span = require('./span.js');
7
8/** JSDoc */
9class Transaction extends span.Span {
10
11 /**
12 * The reference to the current hub.
13 */
14
15 __init() {this._measurements = {};}
16
17 __init2() {this._contexts = {};}
18
19 __init3() {this._frozenDynamicSamplingContext = undefined;}
20
21 /**
22 * This constructor should never be called manually. Those instrumenting tracing should use
23 * `Sentry.startTransaction()`, and internal methods should use `hub.startTransaction()`.
24 * @internal
25 * @hideconstructor
26 * @hidden
27 */
28 constructor(transactionContext, hub$1) {
29 super(transactionContext);Transaction.prototype.__init.call(this);Transaction.prototype.__init2.call(this);Transaction.prototype.__init3.call(this);
30 this._hub = hub$1 || hub.getCurrentHub();
31
32 this._name = transactionContext.name || '';
33
34 this.metadata = {
35 source: 'custom',
36 ...transactionContext.metadata,
37 spanMetadata: {},
38 };
39
40 this._trimEnd = transactionContext.trimEnd;
41
42 // this is because transactions are also spans, and spans have a transaction pointer
43 this.transaction = this;
44
45 // If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
46 // there is incoming Dynamic Sampling Context. (Either through an incoming request, a baggage meta-tag, or other means)
47 const incomingDynamicSamplingContext = this.metadata.dynamicSamplingContext;
48 if (incomingDynamicSamplingContext) {
49 // We shallow copy this in case anything writes to the original reference of the passed in `dynamicSamplingContext`
50 this._frozenDynamicSamplingContext = { ...incomingDynamicSamplingContext };
51 }
52 }
53
54 /** Getter for `name` property */
55 get name() {
56 return this._name;
57 }
58
59 /** Setter for `name` property, which also sets `source` as custom */
60 set name(newName) {
61 this.setName(newName);
62 }
63
64 /**
65 * JSDoc
66 */
67 setName(name, source = 'custom') {
68 this._name = name;
69 this.metadata.source = source;
70 }
71
72 /**
73 * Attaches SpanRecorder to the span itself
74 * @param maxlen maximum number of spans that can be recorded
75 */
76 initSpanRecorder(maxlen = 1000) {
77 if (!this.spanRecorder) {
78 this.spanRecorder = new span.SpanRecorder(maxlen);
79 }
80 this.spanRecorder.add(this);
81 }
82
83 /**
84 * @inheritDoc
85 */
86 setContext(key, context) {
87 if (context === null) {
88 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
89 delete this._contexts[key];
90 } else {
91 this._contexts[key] = context;
92 }
93 }
94
95 /**
96 * @inheritDoc
97 */
98 setMeasurement(name, value, unit = '') {
99 this._measurements[name] = { value, unit };
100 }
101
102 /**
103 * @inheritDoc
104 */
105 setMetadata(newMetadata) {
106 this.metadata = { ...this.metadata, ...newMetadata };
107 }
108
109 /**
110 * @inheritDoc
111 */
112 finish(endTimestamp) {
113 // This transaction is already finished, so we should not flush it again.
114 if (this.endTimestamp !== undefined) {
115 return undefined;
116 }
117
118 if (!this.name) {
119 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.warn('Transaction has no name, falling back to `<unlabeled transaction>`.');
120 this.name = '<unlabeled transaction>';
121 }
122
123 // just sets the end timestamp
124 super.finish(endTimestamp);
125
126 const client = this._hub.getClient();
127 if (client && client.emit) {
128 client.emit('finishTransaction', this);
129 }
130
131 if (this.sampled !== true) {
132 // At this point if `sampled !== true` we want to discard the transaction.
133 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.');
134
135 if (client) {
136 client.recordDroppedEvent('sample_rate', 'transaction');
137 }
138
139 return undefined;
140 }
141
142 const finishedSpans = this.spanRecorder ? this.spanRecorder.spans.filter(s => s !== this && s.endTimestamp) : [];
143
144 if (this._trimEnd && finishedSpans.length > 0) {
145 this.endTimestamp = finishedSpans.reduce((prev, current) => {
146 if (prev.endTimestamp && current.endTimestamp) {
147 return prev.endTimestamp > current.endTimestamp ? prev : current;
148 }
149 return prev;
150 }).endTimestamp;
151 }
152
153 const metadata = this.metadata;
154
155 const transaction = {
156 contexts: {
157 ...this._contexts,
158 // We don't want to override trace context
159 trace: this.getTraceContext(),
160 },
161 spans: finishedSpans,
162 start_timestamp: this.startTimestamp,
163 tags: this.tags,
164 timestamp: this.endTimestamp,
165 transaction: this.name,
166 type: 'transaction',
167 sdkProcessingMetadata: {
168 ...metadata,
169 dynamicSamplingContext: this.getDynamicSamplingContext(),
170 },
171 ...(metadata.source && {
172 transaction_info: {
173 source: metadata.source,
174 },
175 }),
176 };
177
178 const hasMeasurements = Object.keys(this._measurements).length > 0;
179
180 if (hasMeasurements) {
181 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
182 utils.logger.log(
183 '[Measurements] Adding measurements to transaction',
184 JSON.stringify(this._measurements, undefined, 2),
185 );
186 transaction.measurements = this._measurements;
187 }
188
189 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Tracing] Finishing ${this.op} transaction: ${this.name}.`);
190
191 return this._hub.captureEvent(transaction);
192 }
193
194 /**
195 * @inheritDoc
196 */
197 toContext() {
198 const spanContext = super.toContext();
199
200 return utils.dropUndefinedKeys({
201 ...spanContext,
202 name: this.name,
203 trimEnd: this._trimEnd,
204 });
205 }
206
207 /**
208 * @inheritDoc
209 */
210 updateWithContext(transactionContext) {
211 super.updateWithContext(transactionContext);
212
213 this.name = transactionContext.name || '';
214
215 this._trimEnd = transactionContext.trimEnd;
216
217 return this;
218 }
219
220 /**
221 * @inheritdoc
222 *
223 * @experimental
224 */
225 getDynamicSamplingContext() {
226 if (this._frozenDynamicSamplingContext) {
227 return this._frozenDynamicSamplingContext;
228 }
229
230 const hub$1 = this._hub || hub.getCurrentHub();
231 const client = hub$1 && hub$1.getClient();
232
233 if (!client) return {};
234
235 const { environment, release } = client.getOptions() || {};
236 const { publicKey: public_key } = client.getDsn() || {};
237
238 const maybeSampleRate = this.metadata.sampleRate;
239 const sample_rate = maybeSampleRate !== undefined ? maybeSampleRate.toString() : undefined;
240
241 const scope = hub$1.getScope();
242 const { segment: user_segment } = (scope && scope.getUser()) || {};
243
244 const source = this.metadata.source;
245
246 // We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
247 const transaction = source && source !== 'url' ? this.name : undefined;
248
249 const dsc = utils.dropUndefinedKeys({
250 environment: environment || constants.DEFAULT_ENVIRONMENT,
251 release,
252 transaction,
253 user_segment,
254 public_key,
255 trace_id: this.traceId,
256 sample_rate,
257 });
258
259 // Uncomment if we want to make DSC immutable
260 // this._frozenDynamicSamplingContext = dsc;
261
262 return dsc;
263 }
264}
265
266exports.Transaction = Transaction;
267//# sourceMappingURL=transaction.js.map