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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1,
beforeSendSpan: Sentry.withStreamedSpan(span => {
beforeSendSpan: span => {
if (span.attributes['sentry.op'] === 'pageload') {
span.name = 'customPageloadSpanName';
span.links = [
Expand All @@ -24,5 +24,5 @@ Sentry.init({
span.status = 'something';
}
return span;
}),
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { loggingTransport } from '@sentry-internal/node-integration-tests';
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
traceLifecycle: 'stream',
transport: loggingTransport,
release: '1.0.0',
beforeSendSpan: Sentry.withStreamedSpan(span => {
beforeSendSpan: span => {
if (span.name === 'test-child-span') {
span.name = 'customChildSpanName';
if (!span.attributes) {
Expand All @@ -27,7 +26,7 @@ Sentry.init({
];
}
return span;
}),
},
});

Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
Expand Down
3 changes: 2 additions & 1 deletion dev-packages/rollup-utils/plugins/bundlePlugins.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ export function makeTerserPlugin() {
'_resolveFilename',
// Set on e.g. the shim feedbackIntegration to be able to detect it
'_isShim',
// Marker set by `withStreamedSpan()` to tag streamed `beforeSendSpan` callbacks
// Markers used to distinguish static and explicitly streamed `beforeSendSpan` callbacks
'_static',
'_streamed',
// This is used in metadata integration
'_sentryModuleMetadata',
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ export {
unleashIntegration,
growthbookIntegration,
spanStreamingIntegration,
withStaticSpan,
// eslint-disable-next-line typescript/no-deprecated
withStreamedSpan,
metrics,
} from '@sentry/node';
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export declare function init(options: Options | clientSdk.BrowserOptions | NodeO
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStaticSpan: typeof clientSdk.withStaticSpan;
// eslint-disable-next-line typescript/no-deprecated
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;

export declare const getDefaultIntegrations: (options: Options) => Integration[];
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export {
growthbookIntegration,
metrics,
spanStreamingIntegration,
withStaticSpan,
// eslint-disable-next-line typescript/no-deprecated
withStreamedSpan,
experimentalUseDiagnosticsChannelInjection,
diagnosticsChannelInjectionIntegrations,
Expand Down
2 changes: 2 additions & 0 deletions packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export {
spanToTraceHeader,
spanToBaggageHeader,
updateSpanName,
withStaticSpan,
// eslint-disable-next-line typescript/no-deprecated
withStreamedSpan,
metrics,
} from '@sentry/core/browser';
Expand Down
10 changes: 5 additions & 5 deletions packages/browser/src/integrations/spanstreaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
debug,
defineIntegration,
hasSpanStreamingEnabled,
isStreamedBeforeSendSpanCallback,
isStaticBeforeSendSpanCallback,
SpanBuffer,
spanIsSampled,
} from '@sentry/core/browser';
Expand Down Expand Up @@ -36,12 +36,12 @@ export const spanStreamingIntegration = defineIntegration(() => {
}

const beforeSendSpan = clientOptions.beforeSendSpan;
// If users misconfigure their SDK by opting into span streaming but
// using an incompatible beforeSendSpan callback, we fall back to the static trace lifecycle.
if (beforeSendSpan && !isStreamedBeforeSendSpanCallback(beforeSendSpan)) {
if (isStaticBeforeSendSpanCallback(beforeSendSpan)) {
clientOptions.traceLifecycle = 'static';
DEBUG_BUILD &&
debug.warn(`${initialMessage} a beforeSendSpan callback using \`withStreamedSpan\`! ${fallbackMsg}`);
debug.warn(
`SpanStreaming integration is incompatible with a beforeSendSpan callback using \`withStaticSpan\`! ${fallbackMsg}`,
);
return;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/browser/test/integrations/spanstreaming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,21 @@ describe('spanStreamingIntegration', () => {
},
);

it('falls back to static trace lifecycle if beforeSendSpan is not compatible with span streaming', () => {
it('falls back to static trace lifecycle if beforeSendSpan is marked as static', () => {
const debugSpy = vi.spyOn(debug, 'warn').mockImplementation(() => {});
const client = new BrowserClient({
...getDefaultBrowserClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
traceLifecycle: 'stream',
beforeSendSpan: (span: Span) => span,
beforeSendSpan: SentryCore.withStaticSpan(span => span),
});

SentryCore.setCurrentClient(client);
client.init();

expect(debugSpy).toHaveBeenCalledWith(
'SpanStreaming integration requires a beforeSendSpan callback using `withStreamedSpan`! Falling back to static trace lifecycle.',
'SpanStreaming integration is incompatible with a beforeSendSpan callback using `withStaticSpan`! Falling back to static trace lifecycle.',
);
debugSpy.mockRestore();

Expand Down
2 changes: 2 additions & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ export {
unleashIntegration,
metrics,
spanStreamingIntegration,
withStaticSpan,
// eslint-disable-next-line typescript/no-deprecated
withStreamedSpan,
} from '@sentry/node';

Expand Down
2 changes: 2 additions & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export {
growthbookIntegration,
logger,
metrics,
withStaticSpan,
// eslint-disable-next-line typescript/no-deprecated
withStreamedSpan,
spanStreamingIntegration,
instrumentStateGraph,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { _INTERNAL_flushMetricsBuffer } from './metrics/internal';
import type { Scope } from './scope';
import { updateSession } from './session';
import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext';
import { isStreamedBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan';
import { isStaticBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan';
import { extractGenAiSpansFromEvent } from './tracing/spans/extractGenAiSpans';
import { DEFAULT_TRANSPORT_BUFFER_SIZE } from './transports/base';
import type { Breadcrumb, BreadcrumbHint, FetchBreadcrumbHint, XhrBreadcrumbHint } from './types/breadcrumb';
Expand Down Expand Up @@ -1654,7 +1654,7 @@ function processBeforeSend(
hint: EventHint,
): PromiseLike<Event | null> | Event | null {
const { beforeSend, beforeSendTransaction, ignoreSpans } = options;
const beforeSendSpan = !isStreamedBeforeSendSpanCallback(options.beforeSendSpan) && options.beforeSendSpan;
const beforeSendSpan = isStaticBeforeSendSpanCallback(options.beforeSendSpan) && options.beforeSendSpan;

let processedEvent = event;

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/envelope.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Client } from './client';
import { getDynamicSamplingContextFromSpan } from './tracing/dynamicSamplingContext';
import type { SentrySpan } from './tracing/sentrySpan';
import { isStreamedBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan';
import { isStaticBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan';
import type { LegacyCSPReport } from './types/csp';
import type { DsnComponents } from './types/dsn';
import type {
Expand Down Expand Up @@ -157,7 +157,7 @@ export function createSpanEnvelope(spans: [SentrySpan, ...SentrySpan[]], client?
const convertToSpanJSON = beforeSendSpan
? (span: SentrySpan) => {
const spanJson = spanToJSON(span);
const processedSpan = !isStreamedBeforeSendSpanCallback(beforeSendSpan) ? beforeSendSpan(spanJson) : spanJson;
const processedSpan = isStaticBeforeSendSpanCallback(beforeSendSpan) ? beforeSendSpan(spanJson) : spanJson;

if (!processedSpan) {
showSpanDropWarning();
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/integrations/spanStreaming.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IntegrationFn } from '../types/integration';
import { DEBUG_BUILD } from '../debug-build';
import { defineIntegration } from '../integration';
import { isStreamedBeforeSendSpanCallback } from '../tracing/spans/beforeSendSpan';
import { isStaticBeforeSendSpanCallback } from '../tracing/spans/beforeSendSpan';
import { captureSpan } from '../tracing/spans/captureSpan';
import { hasSpanStreamingEnabled } from '../tracing/spans/hasSpanStreamingEnabled';
import { SpanBuffer } from '../tracing/spans/spanBuffer';
Expand All @@ -24,10 +24,12 @@ export const spanStreamingIntegration = defineIntegration(() => {
}

const beforeSendSpan = clientOptions.beforeSendSpan;
if (beforeSendSpan && !isStreamedBeforeSendSpanCallback(beforeSendSpan)) {
if (isStaticBeforeSendSpanCallback(beforeSendSpan)) {
clientOptions.traceLifecycle = 'static';
DEBUG_BUILD &&
debug.warn(`${initialMessage} a beforeSendSpan callback using \`withStreamedSpan\`! ${fallbackMsg}`);
debug.warn(
`SpanStreaming integration is incompatible with a beforeSendSpan callback using \`withStaticSpan\`! ${fallbackMsg}`,
);
return;
}

Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/shared-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,12 @@ export { prepareEvent } from './utils/prepareEvent';
export type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
export { createCheckInEnvelope } from './checkin';
export { hasSpansEnabled } from './utils/hasSpansEnabled';
export { withStreamedSpan } from './tracing/spans/beforeSendSpan';
export { isStreamedBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan';
export {
withStaticSpan,
// eslint-disable-next-line typescript/no-deprecated
withStreamedSpan,
} from './tracing/spans/beforeSendSpan';
export { isStaticBeforeSendSpanCallback, isStreamedBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan';
export { safeSetSpanJSONAttributes } from './tracing/spans/captureSpan';
export { isSentryRequestUrl } from './utils/isSentryRequestUrl';
export { handleCallbackErrors } from './utils/handleCallbackErrors';
Expand Down
53 changes: 37 additions & 16 deletions packages/core/src/tracing/spans/beforeSendSpan.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,63 @@
import type { CoreOptions } from '../../types/options';
import type { BeforeSendStreamedSpanCallback } from '../../types/options';
import type { StreamedSpanJSON } from '../../types/span';
import type { BeforeSendStaticSpanCallback, BeforeSendStreamedSpanCallback } from '../../types/options';
import type { SpanJSON, StreamedSpanJSON } from '../../types/span';
import { addNonEnumerableProperty } from '../../utils/object';

type StaticBeforeSendSpanCallback = CoreOptions['beforeSendSpan'];

/**
* A wrapper to use the new span format in your `beforeSendSpan` callback.
* A wrapper to use the legacy transaction span format in your `beforeSendSpan` callback.
*
* When using `traceLifecycle: 'stream'`, wrap your callback with this function
* to receive and return {@link StreamedSpanJSON} instead of the standard {@link SpanJSON}.
* When using `traceLifecycle: 'static'`, wrap your callback with this function
* to receive and return {@link SpanJSON} instead of {@link StreamedSpanJSON}.
*
* @example
*
* Sentry.init({
* traceLifecycle: 'stream',
* beforeSendSpan: withStreamedSpan((span) => {
* // span is of type StreamedSpanJSON
* traceLifecycle: 'static',
* beforeSendSpan: withStaticSpan((span) => {
* // span is of type SpanJSON
* return span;
* }),
* });
*
* @param callback - The callback function that receives and returns a {@link SpanJSON}.
* @returns A callback that is compatible with the `beforeSendSpan` option when using `traceLifecycle: 'static'`.
*/
export function withStaticSpan(callback: (span: SpanJSON) => SpanJSON): BeforeSendStaticSpanCallback {
addNonEnumerableProperty(callback, '_static', true);
return callback as BeforeSendStaticSpanCallback;
}

/**
* A wrapper to explicitly use the streamed span format in your `beforeSendSpan` callback.
*
* @deprecated `beforeSendSpan` callbacks receive {@link StreamedSpanJSON} by default.
* This function will be removed in SDK version 12.
*
* @param callback - The callback function that receives and returns a {@link StreamedSpanJSON}.
* @returns A callback that is compatible with the `beforeSendSpan` option when using `traceLifecycle: 'stream'`.
* @returns The provided callback.
*/
export function withStreamedSpan(
callback: (span: StreamedSpanJSON) => StreamedSpanJSON,
): StaticBeforeSendSpanCallback & { _streamed: true } {
): BeforeSendStreamedSpanCallback & { _streamed: true } {
addNonEnumerableProperty(callback, '_streamed', true);
return callback as unknown as StaticBeforeSendSpanCallback & { _streamed: true };
return callback as BeforeSendStreamedSpanCallback & { _streamed: true };
}

/**
* Typesafe check to identify if a `beforeSendSpan` callback expects the static span JSON format.
*
* @param callback - The `beforeSendSpan` callback to check.
* @returns `true` if the callback was wrapped with {@link withStaticSpan}.
*/
export function isStaticBeforeSendSpanCallback(callback: unknown): callback is BeforeSendStaticSpanCallback {
return !!callback && typeof callback === 'function' && '_static' in callback && !!callback._static;
}

/**
* Typesafe check to identify if a `beforeSendSpan` callback expects the streamed span JSON format.
*
* @param callback - The `beforeSendSpan` callback to check.
* @returns `true` if the callback was wrapped with {@link withStreamedSpan}.
* @returns `true` unless the callback was wrapped with {@link withStaticSpan}.
*/
export function isStreamedBeforeSendSpanCallback(callback: unknown): callback is BeforeSendStreamedSpanCallback {
return !!callback && typeof callback === 'function' && '_streamed' in callback && !!callback._streamed;
return !!callback && typeof callback === 'function' && !isStaticBeforeSendSpanCallback(callback);
}
21 changes: 16 additions & 5 deletions packages/core/src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,14 +663,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.
* When using `traceLifecycle: 'static'`, wrap your callback with {@link withStaticSpan}
* to receive and return {@link SpanJSON} instead.
*
* @param span The span generated by the SDK.
*
* @returns The modified span payload that will be sent.
*/
beforeSendSpan?: ((span: SpanJSON) => SpanJSON) & { _streamed?: true };
beforeSendSpan?: BeforeSendStreamedSpanCallback | BeforeSendStaticSpanCallbackMarker;

/**
* An event-processing callback for transaction events, guaranteed to be invoked after all other event
Expand Down Expand Up @@ -709,12 +709,23 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
*/
export type BeforeSendStreamedSpanCallback = ((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`.
* @deprecated `beforeSendSpan` callbacks use {@link StreamedSpanJSON} by default.
*/
_streamed?: true;
};

declare const beforeSendStaticSpanCallback: unique symbol;

type BeforeSendStaticSpanCallbackMarker = {
readonly [beforeSendStaticSpanCallback]: true;
};

/** A callback for processing spans sent as part of transaction events. */
export type BeforeSendStaticSpanCallback = ((span: SpanJSON) => SpanJSON) &
BeforeSendStaticSpanCallbackMarker & {
_static: true;
};

/** Base configuration options for every SDK. */
export interface CoreOptions<TO extends BaseTransportOptions = BaseTransportOptions> extends Omit<
Partial<ClientOptions<TO>>,
Expand Down
6 changes: 3 additions & 3 deletions packages/core/test/integrations/spanStreaming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,21 @@ describe('spanStreamingIntegration (core)', () => {
},
);

it('falls back to static trace lifecycle if beforeSendSpan is not compatible with span streaming', () => {
it('falls back to static trace lifecycle if beforeSendSpan is marked as static', () => {
const debugSpy = vi.spyOn(debug, 'warn').mockImplementation(() => {});
const client = new TestClient({
...getDefaultTestClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
traceLifecycle: 'stream',
beforeSendSpan: (span: SentryCore.SpanJSON) => span,
beforeSendSpan: SentryCore.withStaticSpan(span => span),
});

SentryCore.setCurrentClient(client);
client.init();

expect(debugSpy).toHaveBeenCalledWith(
'SpanStreaming integration requires a beforeSendSpan callback using `withStreamedSpan`! Falling back to static trace lifecycle.',
'SpanStreaming integration is incompatible with a beforeSendSpan callback using `withStaticSpan`! Falling back to static trace lifecycle.',
);
debugSpy.mockRestore();

Expand Down
Loading
Loading