Skip to content
Open
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
@@ -0,0 +1,28 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
tracesSampleRate: 1,
beforeSendSpan: Sentry.withStreamedSpan(span => {
if (span.attributes['sentry.op'] === 'pageload') {
span.name = 'customPageloadSpanName';
span.links = [
{
context: {
traceId: '123',
spanId: '456',
},
attributes: {
'sentry.link.type': 'custom_link',
},
},
];
span.attributes['sentry.custom_attribute'] = 'customAttributeValue';
span.status = 'something';
}
return span;
}),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../utils/fixtures';
import { shouldSkipTracingTest, testingCdnBundle } from '../../../utils/helpers';
import { getSpanOp, waitForStreamedSpan } from '../../../utils/spanUtils';

sentryTest('beforeSendSpan applies changes to streamed span', async ({ getLocalTestUrl, page }) => {
sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle());

const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload');

await page.goto(url);

const pageloadSpan = await pageloadSpanPromise;

expect(pageloadSpan.name).toBe('customPageloadSpanName');
expect(pageloadSpan.links).toEqual([
{
context: {
traceId: '123',
spanId: '456',
},
attributes: {
'sentry.link.type': { type: 'string', value: 'custom_link' },
},
},
]);
expect(pageloadSpan.attributes?.['sentry.custom_attribute']).toEqual({
type: 'string',
value: 'customAttributeValue',
});
// we allow overriding any kinds of fields on the span, so we have to expect invalid values
expect(pageloadSpan.status).toBe('something');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as Sentry from '@sentry/node-core';
import { loggingTransport } from '@sentry-internal/node-integration-tests';
import { setupOtel } from '../../../utils/setupOtel';

const client = 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 => {
if (span.name === 'test-child-span') {
span.name = 'customChildSpanName';
if (!span.attributes) {
span.attributes = {};
}
span.attributes['sentry.custom_attribute'] = 'customAttributeValue';
// oxlint-disable-next-line typescript/ban-ts-comment
// @ts-expect-error - technically this is something we have to expect, despite types saying it's invalid
span.status = 'something';
span.links = [
{
trace_id: '123',
span_id: '456',
attributes: {
'sentry.link.type': 'custom_link',
},
},
];
}
return span;
}),
});

setupOtel(client);

Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => {
// noop
});
});

void Sentry.flush();
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { expect, test } from 'vitest';
import { createRunner } from '../../../utils/runner';

test('beforeSendSpan applies changes to streamed span', async () => {

Check failure on line 4 in dev-packages/node-core-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Node-Core Integration Tests

suites/public-api/beforeSendSpan-streamed/test.ts > beforeSendSpan applies changes to streamed span

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ suites/public-api/beforeSendSpan-streamed/test.ts:4:1
await createRunner(__dirname, 'scenario.ts')
.expect({
span: container => {
const spans = container.items;
expect(spans.length).toBe(2);

const customChildSpan = spans.find(s => s.name === 'customChildSpanName');

expect(customChildSpan).toBeDefined();
expect(customChildSpan!.attributes?.['sentry.custom_attribute']).toEqual({
type: 'string',
value: 'customAttributeValue',
});
expect(customChildSpan!.status).toBe('something');
expect(customChildSpan!.links).toEqual([
{
trace_id: '123',
span_id: '456',
attributes: {
'sentry.link.type': { type: 'string', value: 'custom_link' },
},
},
]);
},
})
.start()
.completed();
});
1 change: 1 addition & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export {
unleashIntegration,
growthbookIntegration,
spanStreamingIntegration,
withStreamedSpan,
metrics,
} from '@sentry/node';

Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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 withStreamedSpan: typeof clientSdk.withStreamedSpan;

export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
Expand Down
1 change: 1 addition & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export {
growthbookIntegration,
metrics,
spanStreamingIntegration,
withStreamedSpan,
} from '@sentry/node';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export {
spanToTraceHeader,
spanToBaggageHeader,
updateSpanName,
withStreamedSpan,
metrics,
} from '@sentry/core';

Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/spanstreaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const spanStreamingIntegration = defineIntegration(() => {
// This avoids the classic double-opt-in problem we'd otherwise have in the browser SDK.
const clientOptions = client.getOptions();
if (!clientOptions.traceLifecycle) {
DEBUG_BUILD && debug.warn('[SpanStreaming] set `traceLifecycle` to "stream"');
DEBUG_BUILD && debug.log('[SpanStreaming] set `traceLifecycle` to "stream"');
clientOptions.traceLifecycle = 'stream';
}
},
Expand Down
1 change: 1 addition & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export {
unleashIntegration,
metrics,
spanStreamingIntegration,
withStreamedSpan,
} from '@sentry/node';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export {
growthbookIntegration,
logger,
metrics,
withStreamedSpan,
instrumentLangGraph,
} from '@sentry/core';

Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/tracing/spans/beforeSendSpan.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { BeforeSendStramedSpanCallback, ClientOptions } from '../../types-hoist/options';
import type { CoreOptions } from '../../types-hoist/options';
import type { BeforeSendStreamedSpanCallback, ClientOptions } from '../../types-hoist/options';

Check warning on line 2 in packages/core/src/tracing/spans/beforeSendSpan.ts

View workflow job for this annotation

GitHub Actions / Lint

eslint(no-unused-vars)

Type 'ClientOptions' is imported but never used.
import type { StreamedSpanJSON } from '../../types-hoist/span';
import { addNonEnumerableProperty } from '../../utils/object';

type StaticBeforeSendSpanCallback = CoreOptions['beforeSendSpan'];

/**
* A wrapper to use the new span format in your `beforeSendSpan` callback.
*
Expand All @@ -23,9 +26,9 @@
*/
export function withStreamedSpan(
callback: (span: StreamedSpanJSON) => StreamedSpanJSON,
): BeforeSendStramedSpanCallback {
): StaticBeforeSendSpanCallback & { _streamed: true } {
addNonEnumerableProperty(callback, '_streamed', true);
return callback;
return callback as unknown as StaticBeforeSendSpanCallback & { _streamed: true };
}

/**
Expand All @@ -34,8 +37,6 @@
* @param callback - The `beforeSendSpan` callback to check.
* @returns `true` if the callback was wrapped with {@link withStreamedSpan}.
*/
export function isStreamedBeforeSendSpanCallback(
callback: ClientOptions['beforeSendSpan'],
): callback is BeforeSendStramedSpanCallback {
return !!callback && '_streamed' in callback && !!callback._streamed;
export function isStreamedBeforeSendSpanCallback(callback: unknown): callback is BeforeSendStreamedSpanCallback {
return !!callback && typeof callback === 'function' && '_streamed' in callback && !!callback._streamed;
}
4 changes: 2 additions & 2 deletions packages/core/src/types-hoist/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
*
* @returns The modified span payload that will be sent.
*/
beforeSendSpan?: ((span: SpanJSON) => SpanJSON) | BeforeSendStramedSpanCallback;
beforeSendSpan?: ((span: SpanJSON) => SpanJSON) & { _streamed?: true };

/**
* An event-processing callback for transaction events, guaranteed to be invoked after all other event
Expand Down Expand Up @@ -631,7 +631,7 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
*
* @see {@link StreamedSpanJSON} for the streamed span format used with `traceLifecycle: 'stream'`
*/
export type BeforeSendStramedSpanCallback = ((span: StreamedSpanJSON) => StreamedSpanJSON) & {
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`.
Expand Down
1 change: 1 addition & 0 deletions packages/deno/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export {
wrapMcpServerWithSentry,
featureFlagsIntegration,
metrics,
withStreamedSpan,
logger,
consoleLoggingIntegration,
} from '@sentry/core';
Expand Down
1 change: 1 addition & 0 deletions packages/effect/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
2 changes: 2 additions & 0 deletions packages/elysia/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export {
statsigIntegration,
unleashIntegration,
metrics,
spanStreamingIntegration,
withStreamedSpan,
bunServerIntegration,
makeFetchTransport,
} from '@sentry/bun';
Expand Down
1 change: 1 addition & 0 deletions packages/google-cloud-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export {
unleashIntegration,
metrics,
spanStreamingIntegration,
withStreamedSpan,
} from '@sentry/node';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export declare function init(
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;

// Different implementation in server and worker
export declare const vercelAIIntegration: typeof serverSdk.vercelAIIntegration;
Expand Down
1 change: 1 addition & 0 deletions packages/node-core/src/common-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export {
wrapMcpServerWithSentry,
featureFlagsIntegration,
spanStreamingIntegration,
withStreamedSpan,
metrics,
envToBool,
} from '@sentry/core';
Expand Down
1 change: 1 addition & 0 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,6 @@ export {
cron,
NODE_VERSION,
validateOpenTelemetrySetup,
withStreamedSpan,
_INTERNAL_normalizeCollectionInterval,
} from '@sentry/node-core';
1 change: 1 addition & 0 deletions packages/nuxt/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export declare function init(options: Options | SentryNuxtClientOptions | Sentry
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;

Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@ export { SentrySampler, wrapSamplingDecision } from './sampler';

export { openTelemetrySetupCheck } from './utils/setupCheck';

export { withStreamedSpan } from '@sentry/core';

// Legacy
export { getClient } from '@sentry/core';
1 change: 1 addition & 0 deletions packages/react-router/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;
export declare const defaultStackParser: StackParser;
export declare const getDefaultIntegrations: (options: Options) => Integration[];

Expand Down
1 change: 1 addition & 0 deletions packages/remix/src/cloudflare/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,6 @@ export {
spanToTraceHeader,
spanToBaggageHeader,
updateSpanName,
withStreamedSpan,
featureFlagsIntegration,
} from '@sentry/core';
1 change: 1 addition & 0 deletions packages/remix/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export declare const browserTracingIntegration: typeof clientSdk.browserTracingI
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;

export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
Expand Down
1 change: 1 addition & 0 deletions packages/remix/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export {
createConsolaReporter,
createSentryWinstonTransport,
spanStreamingIntegration,
withStreamedSpan,
} from '@sentry/node';

// Keeping the `*` exports for backwards compatibility and types
Expand Down
1 change: 1 addition & 0 deletions packages/solidstart/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;

export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
Expand Down
1 change: 1 addition & 0 deletions packages/solidstart/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export {
createConsolaReporter,
createSentryWinstonTransport,
spanStreamingIntegration,
withStreamedSpan,
} from '@sentry/node';

// We can still leave this for the carrier init and type exports
Expand Down
1 change: 1 addition & 0 deletions packages/sveltekit/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export declare function wrapLoadWithSentry<T extends (...args: any) => any>(orig
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;

// Different implementation in server and worker
export declare const vercelAIIntegration: typeof serverSdk.vercelAIIntegration;
Expand Down
1 change: 1 addition & 0 deletions packages/sveltekit/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export {
vercelAIIntegration,
metrics,
spanStreamingIntegration,
withStreamedSpan,
} from '@sentry/node';

// We can still leave this for the carrier init and type exports
Expand Down
1 change: 1 addition & 0 deletions packages/sveltekit/src/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export {
withIsolationScope,
withMonitor,
withScope,
withStreamedSpan,
supabaseIntegration,
instrumentSupabaseClient,
zodErrorsIntegration,
Expand Down
Loading
Loading