@@ -4,6 +4,7 @@ import type {
4
4
SpanAttributeValue ,
5
5
SpanAttributes ,
6
6
SpanContextData ,
7
+ SpanEnvelope ,
7
8
SpanJSON ,
8
9
SpanOrigin ,
9
10
SpanStatus ,
@@ -16,6 +17,7 @@ import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/ut
16
17
import { getClient , getCurrentScope } from '../currentScopes' ;
17
18
import { DEBUG_BUILD } from '../debug-build' ;
18
19
20
+ import { createSpanEnvelope } from '../envelope' ;
19
21
import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary' ;
20
22
import {
21
23
SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME ,
@@ -58,6 +60,9 @@ export class SentrySpan implements Span {
58
60
/** The timed events added to this span. */
59
61
protected _events : TimedEvent [ ] ;
60
62
63
+ /** if true, treat span as a standalone span (not part of a transaction) */
64
+ private _isStandaloneSpan ?: boolean ;
65
+
61
66
/**
62
67
* You should never call the constructor manually, always use `Sentry.startSpan()`
63
68
* or other span methods.
@@ -96,6 +101,8 @@ export class SentrySpan implements Span {
96
101
if ( this . _endTime ) {
97
102
this . _onSpanEnded ( ) ;
98
103
}
104
+
105
+ this . _isStandaloneSpan = spanContext . isStandalone ;
99
106
}
100
107
101
108
/** @inheritdoc */
@@ -188,6 +195,8 @@ export class SentrySpan implements Span {
188
195
profile_id : this . _attributes [ SEMANTIC_ATTRIBUTE_PROFILE_ID ] as string | undefined ,
189
196
exclusive_time : this . _attributes [ SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME ] as number | undefined ,
190
197
measurements : timedEventsToMeasurements ( this . _events ) ,
198
+ is_segment : ( this . _isStandaloneSpan && getRootSpan ( this ) === this ) || undefined ,
199
+ segment_id : this . _isStandaloneSpan ? getRootSpan ( this ) . spanContext ( ) . spanId : undefined ,
191
200
} ) ;
192
201
}
193
202
@@ -220,20 +229,44 @@ export class SentrySpan implements Span {
220
229
return this ;
221
230
}
222
231
232
+ /**
233
+ * This method should generally not be used,
234
+ * but for now we need a way to publicly check if the `_isStandaloneSpan` flag is set.
235
+ * USE THIS WITH CAUTION!
236
+ * @internal
237
+ * @hidden
238
+ * @experimental
239
+ */
240
+ public isStandaloneSpan ( ) : boolean {
241
+ return ! ! this . _isStandaloneSpan ;
242
+ }
243
+
223
244
/** Emit `spanEnd` when the span is ended. */
224
245
private _onSpanEnded ( ) : void {
225
246
const client = getClient ( ) ;
226
247
if ( client ) {
227
248
client . emit ( 'spanEnd' , this ) ;
228
249
}
229
250
230
- // If this is a root span, send it when it is endedf
231
- if ( this === getRootSpan ( this ) ) {
232
- const transactionEvent = this . _convertSpanToTransaction ( ) ;
233
- if ( transactionEvent ) {
234
- const scope = getCapturedScopesOnSpan ( this ) . scope || getCurrentScope ( ) ;
235
- scope . captureEvent ( transactionEvent ) ;
236
- }
251
+ // A segment span is basically the root span of a local span tree.
252
+ // So for now, this is either what we previously refer to as the root span,
253
+ // or a standalone span.
254
+ const isSegmentSpan = this . _isStandaloneSpan || this === getRootSpan ( this ) ;
255
+
256
+ if ( ! isSegmentSpan ) {
257
+ return ;
258
+ }
259
+
260
+ // if this is a standalone span, we send it immediately
261
+ if ( this . _isStandaloneSpan ) {
262
+ sendSpanEnvelope ( createSpanEnvelope ( [ this ] ) ) ;
263
+ return ;
264
+ }
265
+
266
+ const transactionEvent = this . _convertSpanToTransaction ( ) ;
267
+ if ( transactionEvent ) {
268
+ const scope = getCapturedScopesOnSpan ( this ) . scope || getCurrentScope ( ) ;
269
+ scope . captureEvent ( transactionEvent ) ;
237
270
}
238
271
}
239
272
@@ -266,8 +299,8 @@ export class SentrySpan implements Span {
266
299
return undefined ;
267
300
}
268
301
269
- // The transaction span itself should be filtered out
270
- const finishedSpans = getSpanDescendants ( this ) . filter ( span => span !== this ) ;
302
+ // The transaction span itself as well as any potential standalone spans should be filtered out
303
+ const finishedSpans = getSpanDescendants ( this ) . filter ( span => span !== this && ! isStandaloneSpan ( span ) ) ;
271
304
272
305
const spans = finishedSpans . map ( span => spanToJSON ( span ) ) . filter ( isFullFinishedSpan ) ;
273
306
@@ -318,3 +351,22 @@ function isSpanTimeInput(value: undefined | SpanAttributes | SpanTimeInput): val
318
351
function isFullFinishedSpan ( input : Partial < SpanJSON > ) : input is SpanJSON {
319
352
return ! ! input . start_timestamp && ! ! input . timestamp && ! ! input . span_id && ! ! input . trace_id ;
320
353
}
354
+
355
+ /** `SentrySpan`s can be sent as a standalone span rather than belonging to a transaction */
356
+ function isStandaloneSpan ( span : Span ) : boolean {
357
+ return span instanceof SentrySpan && span . isStandaloneSpan ( ) ;
358
+ }
359
+
360
+ function sendSpanEnvelope ( envelope : SpanEnvelope ) : void {
361
+ const client = getClient ( ) ;
362
+ if ( ! client ) {
363
+ return ;
364
+ }
365
+
366
+ const transport = client . getTransport ( ) ;
367
+ if ( transport ) {
368
+ transport . send ( envelope ) . then ( null , reason => {
369
+ DEBUG_BUILD && logger . error ( 'Error while sending span:' , reason ) ;
370
+ } ) ;
371
+ }
372
+ }
0 commit comments