1 | Object.defineProperty(exports, '__esModule', { value: true });
|
2 |
|
3 | const utils = require('@sentry/utils');
|
4 | const span = require('./span.js');
|
5 | const transaction = require('./transaction.js');
|
6 |
|
7 | const TRACING_DEFAULTS = {
|
8 | idleTimeout: 1000,
|
9 | finalTimeout: 30000,
|
10 | heartbeatInterval: 5000,
|
11 | };
|
12 |
|
13 | const FINISH_REASON_TAG = 'finishReason';
|
14 |
|
15 | const IDLE_TRANSACTION_FINISH_REASONS = [
|
16 | 'heartbeatFailed',
|
17 | 'idleTimeout',
|
18 | 'documentHidden',
|
19 | 'finalTimeout',
|
20 | 'externalFinish',
|
21 | 'cancelled',
|
22 | ];
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | class IdleTransactionSpanRecorder extends span.SpanRecorder {
|
28 | constructor(
|
29 | _pushActivity,
|
30 | _popActivity,
|
31 | transactionSpanId,
|
32 | maxlen,
|
33 | ) {
|
34 | super(maxlen);this._pushActivity = _pushActivity;this._popActivity = _popActivity;this.transactionSpanId = transactionSpanId; }
|
35 |
|
36 | |
37 |
|
38 |
|
39 | add(span) {
|
40 |
|
41 |
|
42 | if (span.spanId !== this.transactionSpanId) {
|
43 |
|
44 | span.finish = (endTimestamp) => {
|
45 | span.endTimestamp = typeof endTimestamp === 'number' ? endTimestamp : utils.timestampWithMs();
|
46 | this._popActivity(span.spanId);
|
47 | };
|
48 |
|
49 |
|
50 | if (span.endTimestamp === undefined) {
|
51 | this._pushActivity(span.spanId);
|
52 | }
|
53 | }
|
54 |
|
55 | super.add(span);
|
56 | }
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | class IdleTransaction extends transaction.Transaction {
|
65 |
|
66 | __init() {this.activities = {};}
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | __init2() {this._heartbeatCounter = 0;}
|
72 |
|
73 |
|
74 | __init3() {this._finished = false;}
|
75 |
|
76 |
|
77 | __init4() {this._idleTimeoutCanceledPermanently = false;}
|
78 |
|
79 | __init5() {this._beforeFinishCallbacks = [];}
|
80 |
|
81 | |
82 |
|
83 |
|
84 |
|
85 | __init6() {this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[4];}
|
86 |
|
87 | constructor(
|
88 | transactionContext,
|
89 | _idleHub,
|
90 | /**
|
91 | * The time to wait in ms until the idle transaction will be finished. This timer is started each time
|
92 | * there are no active spans on this transaction.
|
93 | */
|
94 | _idleTimeout = TRACING_DEFAULTS.idleTimeout,
|
95 | /**
|
96 | * The final value in ms that a transaction cannot exceed
|
97 | */
|
98 | _finalTimeout = TRACING_DEFAULTS.finalTimeout,
|
99 | _heartbeatInterval = TRACING_DEFAULTS.heartbeatInterval,
|
100 | // Whether or not the transaction should put itself on the scope when it starts and pop itself off when it ends
|
101 | _onScope = false,
|
102 | ) {
|
103 | super(transactionContext, _idleHub);this._idleHub = _idleHub;this._idleTimeout = _idleTimeout;this._finalTimeout = _finalTimeout;this._heartbeatInterval = _heartbeatInterval;this._onScope = _onScope;IdleTransaction.prototype.__init.call(this);IdleTransaction.prototype.__init2.call(this);IdleTransaction.prototype.__init3.call(this);IdleTransaction.prototype.__init4.call(this);IdleTransaction.prototype.__init5.call(this);IdleTransaction.prototype.__init6.call(this);
|
104 | if (_onScope) {
|
105 |
|
106 | clearActiveTransaction(_idleHub);
|
107 |
|
108 |
|
109 |
|
110 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`Setting idle transaction on scope. Span ID: ${this.spanId}`);
|
111 | _idleHub.configureScope(scope => scope.setSpan(this));
|
112 | }
|
113 |
|
114 | this._restartIdleTimeout();
|
115 | setTimeout(() => {
|
116 | if (!this._finished) {
|
117 | this.setStatus('deadline_exceeded');
|
118 | this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[3];
|
119 | this.finish();
|
120 | }
|
121 | }, this._finalTimeout);
|
122 | }
|
123 |
|
124 |
|
125 | finish(endTimestamp = utils.timestampWithMs()) {
|
126 | this._finished = true;
|
127 | this.activities = {};
|
128 |
|
129 | if (this.op === 'ui.action.click') {
|
130 | this.setTag(FINISH_REASON_TAG, this._finishReason);
|
131 | }
|
132 |
|
133 | if (this.spanRecorder) {
|
134 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
135 | utils.logger.log('[Tracing] finishing IdleTransaction', new Date(endTimestamp * 1000).toISOString(), this.op);
|
136 |
|
137 | for (const callback of this._beforeFinishCallbacks) {
|
138 | callback(this, endTimestamp);
|
139 | }
|
140 |
|
141 | this.spanRecorder.spans = this.spanRecorder.spans.filter((span) => {
|
142 |
|
143 | if (span.spanId === this.spanId) {
|
144 | return true;
|
145 | }
|
146 |
|
147 |
|
148 | if (!span.endTimestamp) {
|
149 | span.endTimestamp = endTimestamp;
|
150 | span.setStatus('cancelled');
|
151 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
152 | utils.logger.log('[Tracing] cancelling span since transaction ended early', JSON.stringify(span, undefined, 2));
|
153 | }
|
154 |
|
155 | const keepSpan = span.startTimestamp < endTimestamp;
|
156 | if (!keepSpan) {
|
157 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
158 | utils.logger.log(
|
159 | '[Tracing] discarding Span since it happened after Transaction was finished',
|
160 | JSON.stringify(span, undefined, 2),
|
161 | );
|
162 | }
|
163 | return keepSpan;
|
164 | });
|
165 |
|
166 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Tracing] flushing IdleTransaction');
|
167 | } else {
|
168 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Tracing] No active IdleTransaction');
|
169 | }
|
170 |
|
171 |
|
172 | if (this._onScope) {
|
173 | clearActiveTransaction(this._idleHub);
|
174 | }
|
175 |
|
176 | return super.finish(endTimestamp);
|
177 | }
|
178 |
|
179 | |
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | registerBeforeFinishCallback(callback) {
|
187 | this._beforeFinishCallbacks.push(callback);
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 | initSpanRecorder(maxlen) {
|
194 | if (!this.spanRecorder) {
|
195 | const pushActivity = (id) => {
|
196 | if (this._finished) {
|
197 | return;
|
198 | }
|
199 | this._pushActivity(id);
|
200 | };
|
201 | const popActivity = (id) => {
|
202 | if (this._finished) {
|
203 | return;
|
204 | }
|
205 | this._popActivity(id);
|
206 | };
|
207 |
|
208 | this.spanRecorder = new IdleTransactionSpanRecorder(pushActivity, popActivity, this.spanId, maxlen);
|
209 |
|
210 |
|
211 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('Starting heartbeat');
|
212 | this._pingHeartbeat();
|
213 | }
|
214 | this.spanRecorder.add(this);
|
215 | }
|
216 |
|
217 | |
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | cancelIdleTimeout(
|
224 | endTimestamp,
|
225 | {
|
226 | restartOnChildSpanChange,
|
227 | }
|
228 |
|
229 | = {
|
230 | restartOnChildSpanChange: true,
|
231 | },
|
232 | ) {
|
233 | this._idleTimeoutCanceledPermanently = restartOnChildSpanChange === false;
|
234 | if (this._idleTimeoutID) {
|
235 | clearTimeout(this._idleTimeoutID);
|
236 | this._idleTimeoutID = undefined;
|
237 |
|
238 | if (Object.keys(this.activities).length === 0 && this._idleTimeoutCanceledPermanently) {
|
239 | this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[5];
|
240 | this.finish(endTimestamp);
|
241 | }
|
242 | }
|
243 | }
|
244 |
|
245 | |
246 |
|
247 |
|
248 | _restartIdleTimeout(endTimestamp) {
|
249 | this.cancelIdleTimeout();
|
250 | this._idleTimeoutID = setTimeout(() => {
|
251 | if (!this._finished && Object.keys(this.activities).length === 0) {
|
252 | this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[1];
|
253 | this.finish(endTimestamp);
|
254 | }
|
255 | }, this._idleTimeout);
|
256 | }
|
257 |
|
258 | |
259 |
|
260 |
|
261 |
|
262 | _pushActivity(spanId) {
|
263 | this.cancelIdleTimeout(undefined, { restartOnChildSpanChange: !this._idleTimeoutCanceledPermanently });
|
264 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Tracing] pushActivity: ${spanId}`);
|
265 | this.activities[spanId] = true;
|
266 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Tracing] new activities count', Object.keys(this.activities).length);
|
267 | }
|
268 |
|
269 | |
270 |
|
271 |
|
272 |
|
273 | _popActivity(spanId) {
|
274 | if (this.activities[spanId]) {
|
275 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Tracing] popActivity ${spanId}`);
|
276 |
|
277 | delete this.activities[spanId];
|
278 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Tracing] new activities count', Object.keys(this.activities).length);
|
279 | }
|
280 |
|
281 | if (Object.keys(this.activities).length === 0) {
|
282 | const endTimestamp = utils.timestampWithMs();
|
283 | if (this._idleTimeoutCanceledPermanently) {
|
284 | this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[5];
|
285 | this.finish(endTimestamp);
|
286 | } else {
|
287 |
|
288 |
|
289 | this._restartIdleTimeout(endTimestamp + this._idleTimeout / 1000);
|
290 | }
|
291 | }
|
292 | }
|
293 |
|
294 | |
295 |
|
296 |
|
297 |
|
298 | _beat() {
|
299 |
|
300 | if (this._finished) {
|
301 | return;
|
302 | }
|
303 |
|
304 | const heartbeatString = Object.keys(this.activities).join('');
|
305 |
|
306 | if (heartbeatString === this._prevHeartbeatString) {
|
307 | this._heartbeatCounter++;
|
308 | } else {
|
309 | this._heartbeatCounter = 1;
|
310 | }
|
311 |
|
312 | this._prevHeartbeatString = heartbeatString;
|
313 |
|
314 | if (this._heartbeatCounter >= 3) {
|
315 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Tracing] Transaction finished because of no change for 3 heart beats');
|
316 | this.setStatus('deadline_exceeded');
|
317 | this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[0];
|
318 | this.finish();
|
319 | } else {
|
320 | this._pingHeartbeat();
|
321 | }
|
322 | }
|
323 |
|
324 | |
325 |
|
326 |
|
327 | _pingHeartbeat() {
|
328 | (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`pinging Heartbeat -> current counter: ${this._heartbeatCounter}`);
|
329 | setTimeout(() => {
|
330 | this._beat();
|
331 | }, this._heartbeatInterval);
|
332 | }
|
333 | }
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | function clearActiveTransaction(hub) {
|
339 | const scope = hub.getScope();
|
340 | if (scope) {
|
341 | const transaction = scope.getTransaction();
|
342 | if (transaction) {
|
343 | scope.setSpan(undefined);
|
344 | }
|
345 | }
|
346 | }
|
347 |
|
348 | exports.IdleTransaction = IdleTransaction;
|
349 | exports.IdleTransactionSpanRecorder = IdleTransactionSpanRecorder;
|
350 | exports.TRACING_DEFAULTS = TRACING_DEFAULTS;
|
351 |
|