Skip to content
Merged
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,42 @@
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';
// @ts-ignore - 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();
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ Sentry.init({
tracesSampleRate: 1.0,
transport: loggingTransport,
traceLifecycle: 'stream',
ignoreSpans: ['middleware - expressInit', /custom-to-drop/, { op: 'ignored-op' }],
ignoreSpans: ['expressInit', /custom-to-drop/, { op: 'ignored-op' }],
clientReportFlushInterval: 1_000,
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ app.get('/test/express', (_req, res) => {
() => {},
);
res.send({ response: 'response 1' });

setTimeout(() => {
// flush to avoid waiting for the span buffer timeout to send spans
// but defer it to the next tick to let the SDK finish the http.server span first.
Sentry.flush();
});
});

Sentry.setupExpressErrorHandler(app);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ app.get('/health', (_req, res) => {

app.get('/ok', (_req, res) => {
res.send({ status: 'ok' });
setTimeout(() => {
// flush to avoid waiting for the span buffer timeout to send spans
// but defer it to the next tick to let the SDK finish the http.server span first.
Sentry.flush();
});
});

Sentry.setupExpressErrorHandler(app);
Expand Down
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 } 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 @@ -34,8 +37,6 @@ export function withStreamedSpan(
* @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
Loading
Loading