Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 () => {
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();
});
8 changes: 8 additions & 0 deletions packages/angular/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,11 @@ function checkAndSetAngularVersion(): void {
setContext('angular', { version: angularVersion });
}
}

init({
dsn: '__DSN__',
traceLifecycle: 'stream',
beforeSendSpan: span => {
return span;
},
});
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: 9 additions & 6 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';
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 @@ import { addNonEnumerableProperty } from '../../utils/object';
*/
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 @@ -35,7 +38,7 @@ export function withStreamedSpan(
* @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;
callback: (ClientOptions['beforeSendSpan'] & { _streamed?: true }) | unknown,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: is this not equivalent to callback: unknown? 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, good call, thanks!

): 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
Loading
Loading