From 9fa2fd70e3b04b23f223dcc429059a1cd16fb0fb Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 30 Jan 2026 10:33:55 +0100 Subject: [PATCH 1/5] feat(core): Add SpanV2JSON and envelope type definitions Add type definitions for the new v2 span format: - `SpanV2JSON`: JSON representation of a v2 span - `SpanV2JSONWithSegmentRef`: SpanV2JSON with segment span reference for DSC - `SerializedSpanContainer`: Container for serialized spans - `SpanContainerItem`: Envelope item for span containers - `SpanV2Envelope`: Envelope type for v2 spans - Make `SpanLinkJSON` generic to support different attribute types - Export `AttributeUnit` type for serialized attributes - Add `SerializedAttribute` types for typed attribute serialization These types form the foundation for the upcoming span streaming feature. --- packages/core/src/attributes.ts | 2 +- packages/core/src/index.ts | 4 +++ packages/core/src/types-hoist/attributes.ts | 22 +++++++++++++++ packages/core/src/types-hoist/envelope.ts | 21 ++++++++++++++- packages/core/src/types-hoist/link.ts | 4 +-- packages/core/src/types-hoist/span.ts | 30 +++++++++++++++++++++ 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/types-hoist/attributes.ts diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index d3255d76b0e9..c684ce1861e7 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -40,7 +40,7 @@ export type AttributeObject = { // Unfortunately, we loose type safety if we did something like Exclude // so therefore we unionize between the three supported unit categories. -type AttributeUnit = DurationUnit | InformationUnit | FractionUnit; +export type AttributeUnit = DurationUnit | InformationUnit | FractionUnit; /* If an attribute has either a 'value' or 'unit' property, we use the ValidAttributeObject type. */ export type ValidatedAttributes = { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 30ace1803b1a..28b3cb71516d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -384,6 +384,7 @@ export type { ProfileChunkEnvelope, ProfileChunkItem, SpanEnvelope, + SpanV2Envelope, SpanItem, LogEnvelope, MetricEnvelope, @@ -451,6 +452,9 @@ export type { SpanJSON, SpanContextData, TraceFlag, + SpanV2JSON, + SpanV2JSONWithSegmentRef, + SerializedSpanContainer, } from './types-hoist/span'; export type { SpanStatus } from './types-hoist/spanStatus'; export type { Log, LogSeverityLevel } from './types-hoist/log'; diff --git a/packages/core/src/types-hoist/attributes.ts b/packages/core/src/types-hoist/attributes.ts new file mode 100644 index 000000000000..ca5bce15f0a6 --- /dev/null +++ b/packages/core/src/types-hoist/attributes.ts @@ -0,0 +1,22 @@ +import type { AttributeUnit } from '../attributes'; + +export type SerializedAttributes = Record; +export type SerializedAttribute = ( + | { + type: 'string'; + value: string; + } + | { + type: 'integer'; + value: number; + } + | { + type: 'double'; + value: number; + } + | { + type: 'boolean'; + value: boolean; + } +) & { unit?: AttributeUnit }; +export type SerializedAttributeType = 'string' | 'integer' | 'double' | 'boolean'; diff --git a/packages/core/src/types-hoist/envelope.ts b/packages/core/src/types-hoist/envelope.ts index 272f8cde9f62..7251f85b5df0 100644 --- a/packages/core/src/types-hoist/envelope.ts +++ b/packages/core/src/types-hoist/envelope.ts @@ -11,7 +11,7 @@ import type { Profile, ProfileChunk } from './profiling'; import type { ReplayEvent, ReplayRecordingData } from './replay'; import type { SdkInfo } from './sdkinfo'; import type { SerializedSession, SessionAggregates } from './session'; -import type { SpanJSON } from './span'; +import type { SerializedSpanContainer, SpanJSON } from './span'; // Based on: https://develop.sentry.dev/sdk/envelopes/ @@ -91,6 +91,21 @@ type CheckInItemHeaders = { type: 'check_in' }; type ProfileItemHeaders = { type: 'profile' }; type ProfileChunkItemHeaders = { type: 'profile_chunk' }; type SpanItemHeaders = { type: 'span' }; +type SpanContainerItemHeaders = { + /** + * Same as v1 span item type but this envelope is distinguished by {@link SpanContainerItemHeaders.content_type}. + */ + type: 'span'; + /** + * The number of span items in the container. This must be the same as the number of span items in the payload. + */ + item_count: number; + /** + * The content type of the span items. This must be `application/vnd.sentry.items.span.v2+json`. + * (the presence of this field also distinguishes the span item from the v1 span item) + */ + content_type: 'application/vnd.sentry.items.span.v2+json'; +}; type LogContainerItemHeaders = { type: 'log'; /** @@ -123,6 +138,7 @@ export type FeedbackItem = BaseEnvelopeItem; export type ProfileItem = BaseEnvelopeItem; export type ProfileChunkItem = BaseEnvelopeItem; export type SpanItem = BaseEnvelopeItem>; +export type SpanContainerItem = BaseEnvelopeItem; export type LogContainerItem = BaseEnvelopeItem; export type MetricContainerItem = BaseEnvelopeItem; export type RawSecurityItem = BaseEnvelopeItem; @@ -133,6 +149,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext }; type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders; type ReplayEnvelopeHeaders = BaseEnvelopeHeaders; type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext }; +type SpanV2EnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext }; type LogEnvelopeHeaders = BaseEnvelopeHeaders; type MetricEnvelopeHeaders = BaseEnvelopeHeaders; export type EventEnvelope = BaseEnvelope< @@ -144,6 +161,7 @@ export type ClientReportEnvelope = BaseEnvelope; export type SpanEnvelope = BaseEnvelope; +export type SpanV2Envelope = BaseEnvelope; export type ProfileChunkEnvelope = BaseEnvelope; export type RawSecurityEnvelope = BaseEnvelope; export type LogEnvelope = BaseEnvelope; @@ -157,6 +175,7 @@ export type Envelope = | ReplayEnvelope | CheckInEnvelope | SpanEnvelope + | SpanV2Envelope | RawSecurityEnvelope | LogEnvelope | MetricEnvelope; diff --git a/packages/core/src/types-hoist/link.ts b/packages/core/src/types-hoist/link.ts index a330dc108b00..9a117258200b 100644 --- a/packages/core/src/types-hoist/link.ts +++ b/packages/core/src/types-hoist/link.ts @@ -22,9 +22,9 @@ export interface SpanLink { * Link interface for the event envelope item. It's a flattened representation of `SpanLink`. * Can include additional fields defined by OTel. */ -export interface SpanLinkJSON extends Record { +export interface SpanLinkJSON extends Record { span_id: string; trace_id: string; sampled?: boolean; - attributes?: SpanLinkAttributes; + attributes?: TAttributes; } diff --git a/packages/core/src/types-hoist/span.ts b/packages/core/src/types-hoist/span.ts index d82463768b7f..cabbec50da6d 100644 --- a/packages/core/src/types-hoist/span.ts +++ b/packages/core/src/types-hoist/span.ts @@ -1,3 +1,4 @@ +import type { Attributes } from '../attributes'; import type { SpanLink, SpanLinkJSON } from './link'; import type { Measurements } from './measurement'; import type { HrTime } from './opentelemetry'; @@ -34,6 +35,35 @@ export type SpanAttributes = Partial<{ /** This type is aligned with the OpenTelemetry TimeInput type. */ export type SpanTimeInput = HrTime | number | Date; +/** + * JSON representation of a v2 span, as it should be sent to Sentry. + */ +export interface SpanV2JSON { + trace_id: string; + parent_span_id?: string; + span_id: string; + name: string; + start_timestamp: number; + end_timestamp: number; + status: 'ok' | 'error'; + is_segment: boolean; + attributes?: Attributes; + links?: SpanLinkJSON[]; +} + +/** + * A SpanV2JSON with an attached reference to the segment span. + * This reference is used to compute dynamic sampling context before sending. + * The reference MUST be removed before sending the span envelope. + */ +export interface SpanV2JSONWithSegmentRef extends SpanV2JSON { + _segmentSpan: Span; +} + +export type SerializedSpanContainer = { + items: Array; +}; + /** A JSON representation of a span. */ export interface SpanJSON { data: SpanAttributes; From d8bde46dd524a434fa3d601c0abc8fc44ed87c73 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 30 Jan 2026 12:08:59 +0100 Subject: [PATCH 2/5] rework intermediate representation --- packages/core/src/index.ts | 2 +- packages/core/src/types-hoist/attributes.ts | 22 ---------------- packages/core/src/types-hoist/span.ts | 28 +++++++++++++-------- 3 files changed, 19 insertions(+), 33 deletions(-) delete mode 100644 packages/core/src/types-hoist/attributes.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 28b3cb71516d..c08722327494 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -453,8 +453,8 @@ export type { SpanContextData, TraceFlag, SpanV2JSON, - SpanV2JSONWithSegmentRef, SerializedSpanContainer, + SerializedSpan, } from './types-hoist/span'; export type { SpanStatus } from './types-hoist/spanStatus'; export type { Log, LogSeverityLevel } from './types-hoist/log'; diff --git a/packages/core/src/types-hoist/attributes.ts b/packages/core/src/types-hoist/attributes.ts deleted file mode 100644 index ca5bce15f0a6..000000000000 --- a/packages/core/src/types-hoist/attributes.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { AttributeUnit } from '../attributes'; - -export type SerializedAttributes = Record; -export type SerializedAttribute = ( - | { - type: 'string'; - value: string; - } - | { - type: 'integer'; - value: number; - } - | { - type: 'double'; - value: number; - } - | { - type: 'boolean'; - value: boolean; - } -) & { unit?: AttributeUnit }; -export type SerializedAttributeType = 'string' | 'integer' | 'double' | 'boolean'; diff --git a/packages/core/src/types-hoist/span.ts b/packages/core/src/types-hoist/span.ts index cabbec50da6d..2f6db60bfe57 100644 --- a/packages/core/src/types-hoist/span.ts +++ b/packages/core/src/types-hoist/span.ts @@ -1,4 +1,4 @@ -import type { Attributes } from '../attributes'; +import type { Attributes, RawAttributes } from '../attributes'; import type { SpanLink, SpanLinkJSON } from './link'; import type { Measurements } from './measurement'; import type { HrTime } from './opentelemetry'; @@ -36,7 +36,10 @@ export type SpanAttributes = Partial<{ export type SpanTimeInput = HrTime | number | Date; /** - * JSON representation of a v2 span, as it should be sent to Sentry. + * Intermediate JSON reporesentation of a v2 span, which users and our SDK integrations will interact with. + * This is NOT the final serialized JSON span, but an intermediate step still holding raw attributes. + * The final, serialized span is a {@link SerializedSpan}. + * Main reason: Make it easier and safer for users to work with attributes. */ export interface SpanV2JSON { trace_id: string; @@ -47,19 +50,24 @@ export interface SpanV2JSON { end_timestamp: number; status: 'ok' | 'error'; is_segment: boolean; - attributes?: Attributes; - links?: SpanLinkJSON[]; + attributes?: RawAttributes>; + links?: SpanLinkJSON>>[]; } /** - * A SpanV2JSON with an attached reference to the segment span. - * This reference is used to compute dynamic sampling context before sending. - * The reference MUST be removed before sending the span envelope. + * Serialized span item. + * This is the final, serialized span format that is sent to Sentry. + * The intermediate representation is {@link SpanV2JSON}. + * Main difference: Attributes are converted to {@link Attributes}, thus including the `type` annotation. */ -export interface SpanV2JSONWithSegmentRef extends SpanV2JSON { - _segmentSpan: Span; -} +export type SerializedSpan = Omit & { + attributes: Attributes; + links: SpanLinkJSON[]; +}; +/** + * Envelope span item container. + */ export type SerializedSpanContainer = { items: Array; }; From fd5bfa7e89bb3f5f816e4d1b5d7eab6c0bfbd07f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 30 Jan 2026 12:09:37 +0100 Subject: [PATCH 3/5] fix SerializedSpanContainer --- packages/core/src/types-hoist/span.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/types-hoist/span.ts b/packages/core/src/types-hoist/span.ts index 2f6db60bfe57..05db124ffeef 100644 --- a/packages/core/src/types-hoist/span.ts +++ b/packages/core/src/types-hoist/span.ts @@ -69,7 +69,7 @@ export type SerializedSpan = Omit & { * Envelope span item container. */ export type SerializedSpanContainer = { - items: Array; + items: Array; }; /** A JSON representation of a span. */ From b89549d36a0cdd0a89413ff07fd022b2aae33033 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 30 Jan 2026 12:29:27 +0100 Subject: [PATCH 4/5] you're absolutely right --- packages/core/src/types-hoist/span.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/types-hoist/span.ts b/packages/core/src/types-hoist/span.ts index 05db124ffeef..d5cff749c68e 100644 --- a/packages/core/src/types-hoist/span.ts +++ b/packages/core/src/types-hoist/span.ts @@ -61,8 +61,8 @@ export interface SpanV2JSON { * Main difference: Attributes are converted to {@link Attributes}, thus including the `type` annotation. */ export type SerializedSpan = Omit & { - attributes: Attributes; - links: SpanLinkJSON[]; + attributes?: Attributes; + links?: SpanLinkJSON[]; }; /** From a2215c64b3f813d6a2ba2204d7d969f8fe790841 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 30 Jan 2026 13:47:25 +0100 Subject: [PATCH 5/5] Apply suggestion from @Lms24 --- packages/core/src/attributes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index c684ce1861e7..d3255d76b0e9 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -40,7 +40,7 @@ export type AttributeObject = { // Unfortunately, we loose type safety if we did something like Exclude // so therefore we unionize between the three supported unit categories. -export type AttributeUnit = DurationUnit | InformationUnit | FractionUnit; +type AttributeUnit = DurationUnit | InformationUnit | FractionUnit; /* If an attribute has either a 'value' or 'unit' property, we use the ValidAttributeObject type. */ export type ValidatedAttributes = {