Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'),
gzip: true,
limit: '85.55 KB',
limit: '86 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay, Feedback)',
Expand All @@ -103,7 +103,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'sendFeedback'),
gzip: true,
limit: '31 KB',
limit: '32 KB',
},
{
name: '@sentry/browser (incl. FeedbackAsync)',
Expand Down Expand Up @@ -148,43 +148,43 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '45 KB',
},
// Vue SDK (ESM)
{
name: '@sentry/vue',
path: 'packages/vue/build/esm/index.js',
import: createImport('init'),
gzip: true,
limit: '30 KB',
limit: '31 KB',
},
{
name: '@sentry/vue (incl. Tracing)',
path: 'packages/vue/build/esm/index.js',
import: createImport('init', 'browserTracingIntegration'),
gzip: true,
limit: '44.1 KB',
limit: '45 KB',
},
// Svelte SDK (ESM)
{
name: '@sentry/svelte',
path: 'packages/svelte/build/esm/index.js',
import: createImport('init'),
gzip: true,
limit: '25.5 KB',
limit: '26 KB',
},
// Browser CDN bundles
{
name: 'CDN Bundle',
path: createCDNPath('bundle.min.js'),
gzip: true,
limit: '28 KB',
limit: '29 KB',
},
{
name: 'CDN Bundle (incl. Tracing)',
path: createCDNPath('bundle.tracing.min.js'),
gzip: true,
limit: '43 KB',
limit: '44 KB',
},
{
name: 'CDN Bundle (incl. Logs, Metrics)',
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type { SeverityLevel } from './types-hoist/severity';
import type { Span, SpanAttributes, SpanContextData, SpanJSON } from './types-hoist/span';
import type { StartSpanOptions } from './types-hoist/startSpanOptions';
import type { Transport, TransportMakeRequestResponse } from './types-hoist/transport';
import { isStreamedBeforeSendSpanCallback } from './utils/beforeSendSpan';
import { createClientReportEnvelope } from './utils/clientreport';
import { debug } from './utils/debug-logger';
import { dsnToString, makeDsn } from './utils/dsn';
Expand Down Expand Up @@ -1498,7 +1499,9 @@ function processBeforeSend(
event: Event,
hint: EventHint,
): PromiseLike<Event | null> | Event | null {
const { beforeSend, beforeSendTransaction, beforeSendSpan, ignoreSpans } = options;
const { beforeSend, beforeSendTransaction, ignoreSpans } = options;
const beforeSendSpan = !isStreamedBeforeSendSpanCallback(options.beforeSendSpan) && options.beforeSendSpan;

let processedEvent = event;

if (isErrorEvent(processedEvent) && beforeSend) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { Event } from './types-hoist/event';
import type { SdkInfo } from './types-hoist/sdkinfo';
import type { SdkMetadata } from './types-hoist/sdkmetadata';
import type { Session, SessionAggregates } from './types-hoist/session';
import { isStreamedBeforeSendSpanCallback } from './utils/beforeSendSpan';
import { dsnToString } from './utils/dsn';
import {
createEnvelope,
Expand Down Expand Up @@ -152,7 +153,7 @@ export function createSpanEnvelope(spans: [SentrySpan, ...SentrySpan[]], client?
const convertToSpanJSON = beforeSendSpan
? (span: SentrySpan) => {
const spanJson = spanToJSON(span);
const processedSpan = beforeSendSpan(spanJson);
const processedSpan = !isStreamedBeforeSendSpanCallback(beforeSendSpan) ? beforeSendSpan(spanJson) : spanJson;

if (!processedSpan) {
showSpanDropWarning();
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export { prepareEvent } from './utils/prepareEvent';
export type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
export { createCheckInEnvelope } from './checkin';
export { hasSpansEnabled } from './utils/hasSpansEnabled';
export { withStreamedSpan } from './utils/beforeSendSpan';
export { isSentryRequestUrl } from './utils/isSentryRequestUrl';
export { handleCallbackErrors } from './utils/handleCallbackErrors';
export { parameterize, fmt } from './utils/parameterize';
Expand All @@ -78,11 +79,13 @@ export {
convertSpanLinksForEnvelope,
spanToTraceHeader,
spanToJSON,
spanToStreamedSpanJSON,
spanIsSampled,
spanToTraceContext,
getSpanDescendants,
getStatusMessage,
getRootSpan,
INTERNAL_getSegmentSpan,
getActiveSpan,
addChildSpanToSpan,
spanTimeInputToSeconds,
Expand Down Expand Up @@ -384,6 +387,7 @@ export type {
ProfileChunkEnvelope,
ProfileChunkItem,
SpanEnvelope,
StreamedSpanEnvelope,
SpanItem,
LogEnvelope,
MetricEnvelope,
Expand Down Expand Up @@ -451,6 +455,7 @@ export type {
SpanJSON,
SpanContextData,
TraceFlag,
StreamedSpanJSON,
} from './types-hoist/span';
export type { SpanStatus } from './types-hoist/spanStatus';
export type { Log, LogSeverityLevel } from './types-hoist/log';
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/tracing/sentrySpan.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
import { getClient, getCurrentScope } from '../currentScopes';
import { DEBUG_BUILD } from '../debug-build';
import { createSpanEnvelope } from '../envelope';
Expand All @@ -21,6 +22,7 @@ import type {
SpanJSON,
SpanOrigin,
SpanTimeInput,
StreamedSpanJSON,
} from '../types-hoist/span';
import type { SpanStatus } from '../types-hoist/spanStatus';
import type { TimedEvent } from '../types-hoist/timedEvent';
Expand All @@ -29,8 +31,10 @@ import { generateSpanId, generateTraceId } from '../utils/propagationContext';
import {
convertSpanLinksForEnvelope,
getRootSpan,
getSimpleStatusMessage,
getSpanDescendants,
getStatusMessage,
getStreamedSpanLinks,
spanTimeInputToSeconds,
spanToJSON,
spanToTransactionTraceContext,
Expand Down Expand Up @@ -241,6 +245,30 @@ export class SentrySpan implements Span {
};
}

/**
* Get {@link StreamedSpanJSON} representation of this span.
*
* @hidden
* @internal This method is purely for internal purposes and should not be used outside
* of SDK code. If you need to get a JSON representation of a span,
* use `spanToStreamedSpanJSON(span)` instead.
*/
public getStreamedSpanJSON(): StreamedSpanJSON {
return {
name: this._name ?? '',
span_id: this._spanId,
trace_id: this._traceId,
parent_span_id: this._parentSpanId,
start_timestamp: this._startTime,
// just in case _endTime is not set, we use the start time (i.e. duration 0)
end_timestamp: this._endTime ?? this._startTime,
is_segment: this._isStandaloneSpan || this === getRootSpan(this),
status: getSimpleStatusMessage(this._status),
attributes: this._attributes,
links: getStreamedSpanLinks(this._links),
};
}

/** @inheritdoc */
public isRecording(): boolean {
return !this._endTime && !!this._sampled;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/spans/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
For now, all span streaming related tracing code is in this sub directory.
Once we get rid of transaction-based tracing, we can clean up and flatten the entire tracing directory.
36 changes: 36 additions & 0 deletions packages/core/src/tracing/spans/envelope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Client } from '../../client';
import type { DynamicSamplingContext, SpanContainerItem, StreamedSpanEnvelope } from '../../types-hoist/envelope';
import type { SerializedStreamedSpan } from '../../types-hoist/span';
import { dsnToString } from '../../utils/dsn';
import { createEnvelope, getSdkMetadataForEnvelopeHeader } from '../../utils/envelope';

/**
* Creates a span v2 span streaming envelope
*/
export function createStreamedSpanEnvelope(
serializedSpans: Array<SerializedStreamedSpan>,
dsc: Partial<DynamicSamplingContext>,
client: Client,
): StreamedSpanEnvelope {
const dsn = client.getDsn();
const tunnel = client.getOptions().tunnel;
const sdk = getSdkMetadataForEnvelopeHeader(client.getOptions()._metadata);

const headers: StreamedSpanEnvelope[0] = {
sent_at: new Date().toISOString(),
...(dscHasRequiredProps(dsc) && { trace: dsc }),
...(sdk && { sdk }),
...(!!tunnel && dsn && { dsn: dsnToString(dsn) }),
};

const spanContainer: SpanContainerItem = [
{ type: 'span', item_count: serializedSpans.length, content_type: 'application/vnd.sentry.items.span.v2+json' },
{ items: serializedSpans },
];

return createEnvelope<StreamedSpanEnvelope>(headers, [spanContainer]);
}

function dscHasRequiredProps(dsc: Partial<DynamicSamplingContext>): dsc is DynamicSamplingContext {
return !!dsc.trace_id && !!dsc.public_key;
}
21 changes: 20 additions & 1 deletion packages/core/src/types-hoist/envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { SerializedStreamedSpanContainer, SpanJSON } from './span';

// Based on: https://develop.sentry.dev/sdk/envelopes/

Expand Down Expand Up @@ -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';
/**
Expand Down Expand Up @@ -123,6 +138,7 @@ export type FeedbackItem = BaseEnvelopeItem<FeedbackItemHeaders, FeedbackEvent>;
export type ProfileItem = BaseEnvelopeItem<ProfileItemHeaders, Profile>;
export type ProfileChunkItem = BaseEnvelopeItem<ProfileChunkItemHeaders, ProfileChunk>;
export type SpanItem = BaseEnvelopeItem<SpanItemHeaders, Partial<SpanJSON>>;
export type SpanContainerItem = BaseEnvelopeItem<SpanContainerItemHeaders, SerializedStreamedSpanContainer>;
export type LogContainerItem = BaseEnvelopeItem<LogContainerItemHeaders, SerializedLogContainer>;
export type MetricContainerItem = BaseEnvelopeItem<MetricContainerItemHeaders, SerializedMetricContainer>;
export type RawSecurityItem = BaseEnvelopeItem<RawSecurityHeaders, LegacyCSPReport>;
Expand All @@ -133,6 +149,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext };
type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders;
type ReplayEnvelopeHeaders = BaseEnvelopeHeaders;
type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
type StreamedSpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
type LogEnvelopeHeaders = BaseEnvelopeHeaders;
type MetricEnvelopeHeaders = BaseEnvelopeHeaders;
export type EventEnvelope = BaseEnvelope<
Expand All @@ -144,6 +161,7 @@ export type ClientReportEnvelope = BaseEnvelope<ClientReportEnvelopeHeaders, Cli
export type ReplayEnvelope = [ReplayEnvelopeHeaders, [ReplayEventItem, ReplayRecordingItem]];
export type CheckInEnvelope = BaseEnvelope<CheckInEnvelopeHeaders, CheckInItem>;
export type SpanEnvelope = BaseEnvelope<SpanEnvelopeHeaders, SpanItem>;
export type StreamedSpanEnvelope = BaseEnvelope<StreamedSpanEnvelopeHeaders, SpanContainerItem>;
export type ProfileChunkEnvelope = BaseEnvelope<BaseEnvelopeHeaders, ProfileChunkItem>;
export type RawSecurityEnvelope = BaseEnvelope<BaseEnvelopeHeaders, RawSecurityItem>;
export type LogEnvelope = BaseEnvelope<LogEnvelopeHeaders, LogContainerItem>;
Expand All @@ -157,6 +175,7 @@ export type Envelope =
| ReplayEnvelope
| CheckInEnvelope
| SpanEnvelope
| StreamedSpanEnvelope
| RawSecurityEnvelope
| LogEnvelope
| MetricEnvelope;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/types-hoist/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> {
export interface SpanLinkJSON<TAttributes = SpanLinkAttributes> extends Record<string, unknown> {
span_id: string;
trace_id: string;
sampled?: boolean;
attributes?: SpanLinkAttributes;
attributes?: TAttributes;
}
28 changes: 26 additions & 2 deletions packages/core/src/types-hoist/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Log } from './log';
import type { Metric } from './metric';
import type { TracesSamplerSamplingContext } from './samplingcontext';
import type { SdkMetadata } from './sdkmetadata';
import type { SpanJSON } from './span';
import type { SpanJSON, StreamedSpanJSON } from './span';
import type { StackLineParser, StackParser } from './stacktrace';
import type { TracePropagationTargets } from './tracing';
import type { BaseTransportOptions, Transport } from './transport';
Expand Down Expand Up @@ -500,6 +500,14 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
*/
strictTraceContinuation?: boolean;

/**
* The trace lifecycle, determining whether spans are sent statically when the entire local span tree is complete,
* or streamed in batches, following interval- and action-based triggers.
*
* @default 'static'
*/
traceLifecycle?: 'static' | 'stream';

/**
* The organization ID for your Sentry project.
*
Expand Down Expand Up @@ -579,11 +587,14 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
/**
* This function can be defined to modify a child span before it's sent.
*
* When using `traceLifecycle: 'stream'`, wrap your callback with {@link withStreamedSpan}
* to receive and return {@link StreamedSpanJSON} instead.
*
* @param span The span generated by the SDK.
*
* @returns The modified span payload that will be sent.
*/
beforeSendSpan?: (span: SpanJSON) => SpanJSON;
beforeSendSpan?: ((span: SpanJSON) => SpanJSON) | BeforeSendStramedSpanCallback;

/**
* An event-processing callback for transaction events, guaranteed to be invoked after all other event
Expand Down Expand Up @@ -615,6 +626,19 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | null;
}

/**
* A callback for processing streamed spans before they are sent.
*
* @see {@link StreamedSpanJSON} for the streamed span format used with `traceLifecycle: 'stream'`
*/
export type BeforeSendStramedSpanCallback = ((span: StreamedSpanJSON) => StreamedSpanJSON) & {
/**
* When true, indicates this callback is designed to handle the {@link StreamedSpanJSON} format
* used with `traceLifecycle: 'stream'`. Set this by wrapping your callback with `withStreamedSpan`.
*/
_streamed?: true;
};

/** Base configuration options for every SDK. */
export interface CoreOptions<TO extends BaseTransportOptions = BaseTransportOptions>
extends Omit<Partial<ClientOptions<TO>>, 'integrations' | 'transport' | 'stackParser'> {
Expand Down
Loading