1- import type { Span , SpanJSON , SpanTimeInput , TraceContext } from '@sentry/types' ;
1+ import type {
2+ Span ,
3+ SpanAttributes ,
4+ SpanJSON ,
5+ SpanOrigin ,
6+ SpanStatus ,
7+ SpanTimeInput ,
8+ TraceContext ,
9+ } from '@sentry/types' ;
210import { dropUndefinedKeys , generateSentryTraceHeader , timestampInSeconds } from '@sentry/utils' ;
11+ import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary' ;
12+ import { SEMANTIC_ATTRIBUTE_SENTRY_OP , SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes' ;
13+ import { SPAN_STATUS_OK , SPAN_STATUS_UNSET } from '../tracing' ;
314import type { SentrySpan } from '../tracing/sentrySpan' ;
415
516// These are aligned with OpenTelemetry trace flags
@@ -62,8 +73,6 @@ function ensureTimestampInSeconds(timestamp: number): number {
6273 return isMs ? timestamp / 1000 : timestamp ;
6374}
6475
65- type SpanWithToJSON = Span & { toJSON : ( ) => SpanJSON } ;
66-
6776/**
6877 * Convert a span to a JSON representation.
6978 * Note that all fields returned here are optional and need to be guarded against.
@@ -77,14 +86,52 @@ export function spanToJSON(span: Span): Partial<SpanJSON> {
7786 return span . getSpanJSON ( ) ;
7887 }
7988
80- // Fallback: We also check for `.toJSON()` here...
81- if ( typeof ( span as SpanWithToJSON ) . toJSON === 'function' ) {
82- return ( span as SpanWithToJSON ) . toJSON ( ) ;
89+ try {
90+ const { spanId : span_id , traceId : trace_id } = span . spanContext ( ) ;
91+
92+ // Handle a span from @opentelemetry /sdk-base-trace's `Span` class
93+ if ( spanIsOpenTelemetrySdkTraceBaseSpan ( span ) ) {
94+ const { attributes, startTime, name, endTime, parentSpanId, status } = span ;
95+
96+ return dropUndefinedKeys ( {
97+ span_id,
98+ trace_id,
99+ data : attributes ,
100+ description : name ,
101+ parent_span_id : parentSpanId ,
102+ start_timestamp : spanTimeInputToSeconds ( startTime ) ,
103+ // This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
104+ timestamp : spanTimeInputToSeconds ( endTime ) || undefined ,
105+ status : getStatusMessage ( status ) ,
106+ op : attributes [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] ,
107+ origin : attributes [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] as SpanOrigin | undefined ,
108+ _metrics_summary : getMetricSummaryJsonForSpan ( span ) ,
109+ } ) ;
110+ }
111+
112+ // Finally, at least we have `spanContext()`....
113+ return {
114+ span_id,
115+ trace_id,
116+ } ;
117+ } catch {
118+ return { } ;
83119 }
120+ }
84121
85- // TODO: Also handle OTEL spans here!
122+ function spanIsOpenTelemetrySdkTraceBaseSpan ( span : Span ) : span is OpenTelemetrySdkTraceBaseSpan {
123+ const castSpan = span as OpenTelemetrySdkTraceBaseSpan ;
124+ return ! ! castSpan . attributes && ! ! castSpan . startTime && ! ! castSpan . name && ! ! castSpan . endTime && ! ! castSpan . status ;
125+ }
86126
87- return { } ;
127+ /** Exported only for tests. */
128+ export interface OpenTelemetrySdkTraceBaseSpan extends Span {
129+ attributes : SpanAttributes ;
130+ startTime : SpanTimeInput ;
131+ name : string ;
132+ status : SpanStatus ;
133+ endTime : SpanTimeInput ;
134+ parentSpanId ?: string ;
88135}
89136
90137/**
@@ -108,3 +155,16 @@ export function spanIsSampled(span: Span): boolean {
108155 // eslint-disable-next-line no-bitwise
109156 return Boolean ( traceFlags & TRACE_FLAG_SAMPLED ) ;
110157}
158+
159+ /** Get the status message to use for a JSON representation of a span. */
160+ export function getStatusMessage ( status : SpanStatus | undefined ) : string | undefined {
161+ if ( ! status || status . code === SPAN_STATUS_UNSET ) {
162+ return undefined ;
163+ }
164+
165+ if ( status . code === SPAN_STATUS_OK ) {
166+ return 'ok' ;
167+ }
168+
169+ return status . message || 'unknown_error' ;
170+ }
0 commit comments