11import { getAsyncContextStrategy } from '../asyncContext' ;
2+ import type { RawAttributes } from '../attributes' ;
3+ import { serializeAttributes } from '../attributes' ;
24import { getMainCarrier } from '../carrier' ;
35import { getCurrentScope } from '../currentScopes' ;
46import {
@@ -12,7 +14,15 @@ import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from '../tracing/spanstatus';
1214import { getCapturedScopesOnSpan } from '../tracing/utils' ;
1315import type { TraceContext } from '../types-hoist/context' ;
1416import type { SpanLink , SpanLinkJSON } from '../types-hoist/link' ;
15- import type { Span , SpanAttributes , SpanJSON , SpanOrigin , SpanTimeInput } from '../types-hoist/span' ;
17+ import type {
18+ SerializedSpan ,
19+ Span ,
20+ SpanAttributes ,
21+ SpanJSON ,
22+ SpanOrigin ,
23+ SpanTimeInput ,
24+ StreamedSpanJSON ,
25+ } from '../types-hoist/span' ;
1626import type { SpanStatus } from '../types-hoist/spanStatus' ;
1727import { addNonEnumerableProperty } from '../utils/object' ;
1828import { generateSpanId } from '../utils/propagationContext' ;
@@ -105,6 +115,27 @@ export function convertSpanLinksForEnvelope(links?: SpanLink[]): SpanLinkJSON[]
105115 }
106116}
107117
118+ /**
119+ * Converts the span links array to a flattened version with serialized attributes for V2 spans.
120+ *
121+ * If the links array is empty, it returns `undefined` so the empty value can be dropped before it's sent.
122+ */
123+ export function getStreamedSpanLinks (
124+ links ?: SpanLink [ ] ,
125+ ) : SpanLinkJSON < RawAttributes < Record < string , unknown > > > [ ] | undefined {
126+ if ( links ?. length ) {
127+ return links . map ( ( { context : { spanId, traceId, traceFlags, ...restContext } , attributes } ) => ( {
128+ span_id : spanId ,
129+ trace_id : traceId ,
130+ sampled : traceFlags === TRACE_FLAG_SAMPLED ,
131+ attributes,
132+ ...restContext ,
133+ } ) ) ;
134+ } else {
135+ return undefined ;
136+ }
137+ }
138+
108139/**
109140 * Convert a span time input into a timestamp in seconds.
110141 */
@@ -150,23 +181,12 @@ export function spanToJSON(span: Span): SpanJSON {
150181 if ( spanIsOpenTelemetrySdkTraceBaseSpan ( span ) ) {
151182 const { attributes, startTime, name, endTime, status, links } = span ;
152183
153- // In preparation for the next major of OpenTelemetry, we want to support
154- // looking up the parent span id according to the new API
155- // In OTel v1, the parent span id is accessed as `parentSpanId`
156- // In OTel v2, the parent span id is accessed as `spanId` on the `parentSpanContext`
157- const parentSpanId =
158- 'parentSpanId' in span
159- ? span . parentSpanId
160- : 'parentSpanContext' in span
161- ? ( span . parentSpanContext as { spanId ?: string } | undefined ) ?. spanId
162- : undefined ;
163-
164184 return {
165185 span_id,
166186 trace_id,
167187 data : attributes ,
168188 description : name ,
169- parent_span_id : parentSpanId ,
189+ parent_span_id : getOtelParentSpanId ( span ) ,
170190 start_timestamp : spanTimeInputToSeconds ( startTime ) ,
171191 // This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
172192 timestamp : spanTimeInputToSeconds ( endTime ) || undefined ,
@@ -187,6 +207,77 @@ export function spanToJSON(span: Span): SpanJSON {
187207 } ;
188208}
189209
210+ /**
211+ * Convert a span to the intermediate {@link StreamedSpanJSON} representation.
212+ */
213+ export function spanToStreamedSpanJSON ( span : Span ) : StreamedSpanJSON {
214+ if ( spanIsSentrySpan ( span ) ) {
215+ return span . getStreamedSpanJSON ( ) ;
216+ }
217+
218+ const { spanId : span_id , traceId : trace_id } = span . spanContext ( ) ;
219+
220+ // Handle a span from @opentelemetry /sdk-base-trace's `Span` class
221+ if ( spanIsOpenTelemetrySdkTraceBaseSpan ( span ) ) {
222+ const { attributes, startTime, name, endTime, status, links } = span ;
223+
224+ return {
225+ name,
226+ span_id,
227+ trace_id,
228+ parent_span_id : getOtelParentSpanId ( span ) ,
229+ start_timestamp : spanTimeInputToSeconds ( startTime ) ,
230+ end_timestamp : spanTimeInputToSeconds ( endTime ) ,
231+ is_segment : span === INTERNAL_getSegmentSpan ( span ) ,
232+ status : getSimpleStatusMessage ( status ) ,
233+ attributes,
234+ links : getStreamedSpanLinks ( links ) ,
235+ } ;
236+ }
237+
238+ // Finally, as a fallback, at least we have `spanContext()`....
239+ // This should not actually happen in reality, but we need to handle it for type safety.
240+ return {
241+ span_id,
242+ trace_id,
243+ start_timestamp : 0 ,
244+ name : '' ,
245+ end_timestamp : 0 ,
246+ status : 'ok' ,
247+ is_segment : span === INTERNAL_getSegmentSpan ( span ) ,
248+ } ;
249+ }
250+
251+ /**
252+ * In preparation for the next major of OpenTelemetry, we want to support
253+ * looking up the parent span id according to the new API
254+ * In OTel v1, the parent span id is accessed as `parentSpanId`
255+ * In OTel v2, the parent span id is accessed as `spanId` on the `parentSpanContext`
256+ */
257+ function getOtelParentSpanId ( span : OpenTelemetrySdkTraceBaseSpan ) : string | undefined {
258+ return 'parentSpanId' in span
259+ ? span . parentSpanId
260+ : 'parentSpanContext' in span
261+ ? ( span . parentSpanContext as { spanId ?: string } | undefined ) ?. spanId
262+ : undefined ;
263+ }
264+
265+ /**
266+ * Converts a {@link StreamedSpanJSON} to a {@link SerializedSpan}.
267+ * This is the final serialized span format that is sent to Sentry.
268+ * The returned serilaized spans must not be consumed by users or SDK integrations.
269+ */
270+ export function streamedSpanJsonToSerializedSpan ( spanJson : StreamedSpanJSON ) : SerializedSpan {
271+ return {
272+ ...spanJson ,
273+ attributes : serializeAttributes ( spanJson . attributes ) ,
274+ links : spanJson . links ?. map ( link => ( {
275+ ...link ,
276+ attributes : serializeAttributes ( link . attributes ) ,
277+ } ) ) ,
278+ } ;
279+ }
280+
190281function spanIsOpenTelemetrySdkTraceBaseSpan ( span : Span ) : span is OpenTelemetrySdkTraceBaseSpan {
191282 const castSpan = span as Partial < OpenTelemetrySdkTraceBaseSpan > ;
192283 return ! ! castSpan . attributes && ! ! castSpan . startTime && ! ! castSpan . name && ! ! castSpan . endTime && ! ! castSpan . status ;
@@ -237,6 +328,13 @@ export function getStatusMessage(status: SpanStatus | undefined): string | undef
237328 return status . message || 'internal_error' ;
238329}
239330
331+ /**
332+ * Convert the various statuses to the simple onces expected by Sentry for steamed spans ('ok' is default).
333+ */
334+ export function getSimpleStatusMessage ( status : SpanStatus | undefined ) : 'ok' | 'error' {
335+ return ! status || status . code === SPAN_STATUS_OK || status . code === SPAN_STATUS_UNSET ? 'ok' : 'error' ;
336+ }
337+
240338const CHILD_SPANS_FIELD = '_sentryChildSpans' ;
241339const ROOT_SPAN_FIELD = '_sentryRootSpan' ;
242340
@@ -298,7 +396,12 @@ export function getSpanDescendants(span: SpanWithPotentialChildren): Span[] {
298396/**
299397 * Returns the root span of a given span.
300398 */
301- export function getRootSpan ( span : SpanWithPotentialChildren ) : Span {
399+ export const getRootSpan = INTERNAL_getSegmentSpan ;
400+
401+ /**
402+ * Returns the segment span of a given span.
403+ */
404+ export function INTERNAL_getSegmentSpan ( span : SpanWithPotentialChildren ) : Span {
302405 return span [ ROOT_SPAN_FIELD ] || span ;
303406}
304407
0 commit comments