Skip to content

Commit 3442afa

Browse files
committed
feat(browser): Use idle span for browser tracing
And remove idle transaction.
1 parent b636963 commit 3442afa

File tree

17 files changed

+371
-1374
lines changed

17 files changed

+371
-1374
lines changed

packages/core/src/semanticAttributes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ export const SEMANTIC_ATTRIBUTE_SENTRY_OP = 'sentry.op';
1919
* Use this attribute to represent the origin of a span.
2020
*/
2121
export const SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'sentry.origin';
22+
23+
/** The reason why an idle span finished. */
24+
export const SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON = 'sentry.idle_span_finish_reason';

packages/core/src/tracing/hubextensions.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { ClientOptions, CustomSamplingContext, Hub, TransactionContext } fr
22
import { getMainCarrier } from '../asyncContext';
33

44
import { registerErrorInstrumentation } from './errors';
5-
import { IdleTransaction } from './idletransaction';
65
import { sampleTransaction } from './sampling';
76
import { Transaction } from './transaction';
87

@@ -53,54 +52,6 @@ function _startTransaction(
5352
return transaction;
5453
}
5554

56-
/**
57-
* Create new idle transaction.
58-
*/
59-
export function startIdleTransaction(
60-
hub: Hub,
61-
transactionContext: TransactionContext,
62-
idleTimeout: number,
63-
finalTimeout: number,
64-
onScope?: boolean,
65-
customSamplingContext?: CustomSamplingContext,
66-
heartbeatInterval?: number,
67-
delayAutoFinishUntilSignal: boolean = false,
68-
): IdleTransaction {
69-
// eslint-disable-next-line deprecation/deprecation
70-
const client = hub.getClient();
71-
const options: Partial<ClientOptions> = (client && client.getOptions()) || {};
72-
73-
// eslint-disable-next-line deprecation/deprecation
74-
let transaction = new IdleTransaction(
75-
transactionContext,
76-
hub,
77-
idleTimeout,
78-
finalTimeout,
79-
heartbeatInterval,
80-
onScope,
81-
delayAutoFinishUntilSignal,
82-
);
83-
transaction = sampleTransaction(transaction, options, {
84-
name: transactionContext.name,
85-
parentSampled: transactionContext.parentSampled,
86-
transactionContext,
87-
attributes: {
88-
// eslint-disable-next-line deprecation/deprecation
89-
...transactionContext.data,
90-
...transactionContext.attributes,
91-
},
92-
...customSamplingContext,
93-
});
94-
if (transaction.isRecording()) {
95-
transaction.initSpanRecorder();
96-
}
97-
if (client) {
98-
client.emit('startTransaction', transaction);
99-
client.emit('spanStart', transaction);
100-
}
101-
return transaction;
102-
}
103-
10455
/**
10556
* Adds tracing extensions to the global hub.
10657
*/

packages/core/src/tracing/idleSpan.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type { Span, StartSpanOptions } from '@sentry/types';
1+
import type { Span, SpanAttributes, StartSpanOptions } from '@sentry/types';
22
import { logger, timestampInSeconds } from '@sentry/utils';
33
import { getClient, getCurrentScope } from '../currentScopes';
44

55
import { DEBUG_BUILD } from '../debug-build';
6+
import { SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON } from '../semanticAttributes';
67
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
7-
import { getSpanDescendants, removeChildSpanFromSpan, spanToJSON } from '../utils/spanUtils';
8+
import { getSpanDescendants, removeChildSpanFromSpan, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
89
import { SentryNonRecordingSpan } from './sentryNonRecordingSpan';
910
import { SPAN_STATUS_ERROR } from './spanstatus';
1011
import { startInactiveSpan } from './trace';
11-
import { getActiveSpan } from './utils';
12+
import { getActiveSpan, getCapturedScopesOnSpan } from './utils';
1213

1314
export const TRACING_DEFAULTS = {
1415
idleTimeout: 1_000,
@@ -108,6 +109,20 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
108109
const previousActiveSpan = getActiveSpan();
109110
const span = _startIdleSpan(startSpanOptions);
110111

112+
function _endSpan(timestamp: number = timestampInSeconds()): void {
113+
// Ensure we end with the last span timestamp, if possible
114+
const spans = getSpanDescendants(span).filter(child => child !== span);
115+
116+
const latestEndTimestamp = spans.length ? Math.max(...spans.map(span => spanToJSON(span).timestamp || 0)) : 0;
117+
118+
// If the timestamp is smaller than the latest end timestamp of a span, we still want to use it
119+
const endTimestamp = latestEndTimestamp
120+
? Math.min(spanTimeInputToSeconds(timestamp), latestEndTimestamp)
121+
: timestamp;
122+
123+
span.end(endTimestamp);
124+
}
125+
111126
/**
112127
* Cancels the existing idle timeout, if there is one.
113128
*/
@@ -136,7 +151,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
136151
_idleTimeoutID = setTimeout(() => {
137152
if (!_finished && activities.size === 0 && _autoFinishAllowed) {
138153
_finishReason = FINISH_REASON_IDLE_TIMEOUT;
139-
span.end(endTimestamp);
154+
_endSpan(endTimestamp);
140155
}
141156
}, idleTimeout);
142157
}
@@ -149,7 +164,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
149164
_idleTimeoutID = setTimeout(() => {
150165
if (!_finished && _autoFinishAllowed) {
151166
_finishReason = FINISH_REASON_HEARTBEAT_FAILED;
152-
span.end(endTimestamp);
167+
_endSpan(endTimestamp);
153168
}
154169
}, childSpanTimeout);
155170
}
@@ -190,7 +205,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
190205
}
191206
}
192207

193-
function endIdleSpan(): void {
208+
function onIdleSpanEnded(): void {
194209
_finished = true;
195210
activities.clear();
196211

@@ -209,9 +224,25 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
209224
return;
210225
}
211226

212-
const attributes = spanJSON.data || {};
213-
if (spanJSON.op === 'ui.action.click' && !attributes[FINISH_REASON_TAG]) {
214-
span.setAttribute(FINISH_REASON_TAG, _finishReason);
227+
const attributes: SpanAttributes = spanJSON.data || {};
228+
if (spanJSON.op === 'ui.action.click' && !attributes[SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON]) {
229+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, _finishReason);
230+
}
231+
232+
// Save finish reason as tag, in addition to attribute, to maintain backwards compatibility
233+
const scopes = getCapturedScopesOnSpan(span);
234+
235+
// Make sure to fetch up-to-date data here, as it may have been updated above
236+
const finalAttributes: SpanAttributes = spanToJSON(span).data || {};
237+
const finalFinishReason = finalAttributes[SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON];
238+
if (scopes.scope && typeof finalFinishReason === 'string') {
239+
// We only want to add the tag to the transaction event itself
240+
scopes.scope.addEventProcessor(event => {
241+
if (event.type === 'transaction') {
242+
event.tags = { ...event.tags, [FINISH_REASON_TAG]: finalFinishReason };
243+
}
244+
return event;
245+
});
215246
}
216247

217248
DEBUG_BUILD &&
@@ -279,7 +310,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
279310
_popActivity(endedSpan.spanContext().spanId);
280311

281312
if (endedSpan === span) {
282-
endIdleSpan();
313+
onIdleSpanEnded();
283314
}
284315
});
285316

@@ -303,7 +334,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
303334
if (!_finished) {
304335
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' });
305336
_finishReason = FINISH_REASON_FINAL_TIMEOUT;
306-
span.end();
337+
_endSpan();
307338
}
308339
}, finalTimeout);
309340

0 commit comments

Comments
 (0)