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
14 changes: 7 additions & 7 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'metrics'),
gzip: true,
limit: '27 KB',
limit: '28 KB',
},
{
name: '@sentry/browser (incl. Logs)',
Expand Down Expand Up @@ -220,13 +220,13 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing, Replay, Feedback)',
path: createCDNPath('bundle.tracing.replay.feedback.min.js'),
gzip: true,
limit: '86 KB',
limit: '87 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)',
path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'),
gzip: true,
limit: '87 KB',
limit: '88 KB',
},
// browser CDN bundles (non-gzipped)
{
Expand Down Expand Up @@ -317,7 +317,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '53 KB',
limit: '55 KB',
},
// Node SDK (ESM)
{
Expand All @@ -326,14 +326,14 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '175 KB',
limit: '177 KB',
},
{
name: '@sentry/node - without tracing',
path: 'packages/node/build/esm/index.js',
import: createImport('initWithoutDefaultIntegrations', 'getDefaultIntegrationsWithoutPerformance'),
gzip: true,
limit: '98 KB',
limit: '100 KB',
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
modifyWebpackConfig: function (config) {
const webpack = require('webpack');
Expand All @@ -356,7 +356,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '114 KB',
limit: '116 KB',
},
];

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 @@ -170,6 +170,7 @@ export {
statsigIntegration,
unleashIntegration,
growthbookIntegration,
spanStreamingIntegration,
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 @@ -20,6 +20,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 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 @@ -157,6 +157,7 @@ export {
unleashIntegration,
growthbookIntegration,
metrics,
spanStreamingIntegration,
} from '@sentry/node';

export {
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-serverless/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ function shouldDisableLayerExtensionForProxy(): boolean {
*/
// NOTE: in awslambda-auto.ts, we also call the original `getDefaultIntegrations` from `@sentry/node` to load performance integrations.
// If at some point we need to filter a node integration out for good, we need to make sure to also filter it out there.
export function getDefaultIntegrations(_options: Options): Integration[] {
return [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()];
export function getDefaultIntegrations(options: Options): Integration[] {
return [...getDefaultIntegrationsWithoutPerformance(options), awsIntegration(), awsLambdaIntegration()];
}

export interface AwsServerlessOptions extends NodeOptions {
Expand Down
6 changes: 4 additions & 2 deletions packages/browser/src/integrations/spanstreaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ export const spanStreamingIntegration = defineIntegration(() => {
setup(client) {
const initialMessage = 'SpanStreaming integration requires';
const fallbackMsg = 'Falling back to static trace lifecycle.';
const clientOptions = client.getOptions();

if (!hasSpanStreamingEnabled(client)) {
clientOptions.traceLifecycle = 'static';
DEBUG_BUILD && debug.warn(`${initialMessage} \`traceLifecycle\` to be set to "stream"! ${fallbackMsg}`);
return;
}

const beforeSendSpan = client.getOptions().beforeSendSpan;
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)) {
client.getOptions().traceLifecycle = 'static';
clientOptions.traceLifecycle = 'static';
DEBUG_BUILD &&
debug.warn(`${initialMessage} a beforeSendSpan callback using \`withStreamedSpan\`! ${fallbackMsg}`);
return;
Expand Down
42 changes: 23 additions & 19 deletions packages/browser/test/integrations/spanstreaming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,29 @@ describe('spanStreamingIntegration', () => {
expect(client.getOptions().traceLifecycle).toBe('stream');
});

it('logs a warning if traceLifecycle is not set to "stream"', () => {
const debugSpy = vi.spyOn(debug, 'warn').mockImplementation(() => {});
const client = new BrowserClient({
...getDefaultBrowserClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
traceLifecycle: 'static',
});

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

expect(debugSpy).toHaveBeenCalledWith(
'SpanStreaming integration requires `traceLifecycle` to be set to "stream"! Falling back to static trace lifecycle.',
);
debugSpy.mockRestore();

expect(client.getOptions().traceLifecycle).toBe('static');
});
it.each(['static', 'somethingElse'])(
'logs a warning if traceLifecycle is not set to "stream" but to %s',
traceLifecycle => {
const debugSpy = vi.spyOn(debug, 'warn').mockImplementation(() => {});
const client = new BrowserClient({
...getDefaultBrowserClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
// @ts-expect-error - we want to test the warning for invalid traceLifecycle values
traceLifecycle,
});

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

expect(debugSpy).toHaveBeenCalledWith(
'SpanStreaming integration requires `traceLifecycle` to be set to "stream"! Falling back to static trace lifecycle.',
);
debugSpy.mockRestore();

expect(client.getOptions().traceLifecycle).toBe('static');
},
);

it('falls back to static trace lifecycle if beforeSendSpan is not compatible with span streaming', () => {
const debugSpy = vi.spyOn(debug, 'warn').mockImplementation(() => {});
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 @@ -177,6 +177,7 @@ export {
statsigIntegration,
unleashIntegration,
metrics,
spanStreamingIntegration,
} from '@sentry/node';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export type {

export { SpanBuffer } from './tracing/spans/spanBuffer';
export { hasSpanStreamingEnabled } from './tracing/spans/hasSpanStreamingEnabled';
export { spanStreamingIntegration } from './integrations/spanStreaming';

export type { FeatureFlag } from './utils/featureFlags';

Expand Down
44 changes: 44 additions & 0 deletions packages/core/src/integrations/spanStreaming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { IntegrationFn } from '../types-hoist/integration';
import { DEBUG_BUILD } from '../debug-build';
import { defineIntegration } from '../integration';
import { isStreamedBeforeSendSpanCallback } from '../tracing/spans/beforeSendSpan';
import { captureSpan } from '../tracing/spans/captureSpan';
import { hasSpanStreamingEnabled } from '../tracing/spans/hasSpanStreamingEnabled';
import { SpanBuffer } from '../tracing/spans/spanBuffer';
import { debug } from '../utils/debug-logger';
import { spanIsSampled } from '../utils/spanUtils';

export const spanStreamingIntegration = defineIntegration(() => {
return {
name: 'SpanStreaming',

setup(client) {
const initialMessage = 'SpanStreaming integration requires';
const fallbackMsg = 'Falling back to static trace lifecycle.';
const clientOptions = client.getOptions();

if (!hasSpanStreamingEnabled(client)) {
clientOptions.traceLifecycle = 'static';
DEBUG_BUILD && debug.warn(`${initialMessage} \`traceLifecycle\` to be set to "stream"! ${fallbackMsg}`);
return;
}

const beforeSendSpan = clientOptions.beforeSendSpan;
if (beforeSendSpan && !isStreamedBeforeSendSpanCallback(beforeSendSpan)) {
clientOptions.traceLifecycle = 'static';
DEBUG_BUILD &&
debug.warn(`${initialMessage} a beforeSendSpan callback using \`withStreamedSpan\`! ${fallbackMsg}`);
return;
}

const buffer = new SpanBuffer(client);

client.on('afterSpanEnd', span => {
if (!spanIsSampled(span)) {
return;
}
buffer.add(captureSpan(span, client));
});
},
};
}) satisfies IntegrationFn;
139 changes: 139 additions & 0 deletions packages/core/test/integrations/spanStreaming.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as SentryCore from '../../src';
import { debug } from '../../src';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { spanStreamingIntegration } from '../../src/integrations/spanStreaming';
import { TestClient, getDefaultTestClientOptions } from '../mocks/client';

const mockSpanBufferInstance = vi.hoisted(() => ({
flush: vi.fn(),
add: vi.fn(),
drain: vi.fn(),
}));

const MockSpanBuffer = vi.hoisted(() => {
return vi.fn(() => mockSpanBufferInstance);
});

vi.mock('../../src/tracing/spans/spanBuffer', async () => {
const original = await vi.importActual('../../src/tracing/spans/spanBuffer');
return {
...original,
SpanBuffer: MockSpanBuffer,
};
});

describe('spanStreamingIntegration (core)', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('has the correct name and setup hook', () => {
const integration = spanStreamingIntegration();
expect(integration.name).toBe('SpanStreaming');
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(integration.setup).toBeDefined();
});

it.each(['static', 'somethingElse'])(
'logs a warning if traceLifecycle is not set to "stream" but to %s',
traceLifecycle => {
const debugSpy = vi.spyOn(debug, 'warn').mockImplementation(() => {});
const client = new TestClient({
...getDefaultTestClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
// @ts-expect-error - we want to test the warning for invalid traceLifecycle values
traceLifecycle,
});

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

expect(debugSpy).toHaveBeenCalledWith(
'SpanStreaming integration requires `traceLifecycle` to be set to "stream"! Falling back to static trace lifecycle.',
);
debugSpy.mockRestore();

expect(client.getOptions().traceLifecycle).toBe('static');
},
);

it('falls back to static trace lifecycle if beforeSendSpan is not compatible with span streaming', () => {
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,
});

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

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

expect(client.getOptions().traceLifecycle).toBe('static');
});

it('sets up buffer when traceLifecycle is "stream"', () => {
const client = new TestClient({
...getDefaultTestClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
traceLifecycle: 'stream',
});

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

expect(MockSpanBuffer).toHaveBeenCalledWith(client);
expect(client.getOptions().traceLifecycle).toBe('stream');
});

it('enqueues a span into the buffer when the span ends', () => {
const client = new TestClient({
...getDefaultTestClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
traceLifecycle: 'stream',
tracesSampleRate: 1,
});

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

const span = new SentryCore.SentrySpan({ name: 'test', sampled: true });
client.emit('afterSpanEnd', span);

expect(mockSpanBufferInstance.add).toHaveBeenCalledWith(
expect.objectContaining({
_segmentSpan: span,
trace_id: span.spanContext().traceId,
span_id: span.spanContext().spanId,
name: 'test',
}),
);
});

it('does not enqueue a span into the buffer when the span is not sampled', () => {
const client = new TestClient({
...getDefaultTestClientOptions(),
dsn: 'https://username@domain/123',
integrations: [spanStreamingIntegration()],
traceLifecycle: 'stream',
tracesSampleRate: 1,
});

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

const span = new SentryCore.SentrySpan({ name: 'test', sampled: false });
client.emit('afterSpanEnd', span);

expect(mockSpanBufferInstance.add).not.toHaveBeenCalled();
});
});
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 @@ -157,6 +157,7 @@ export {
statsigIntegration,
unleashIntegration,
metrics,
spanStreamingIntegration,
} from '@sentry/node';

export {
Expand Down
4 changes: 2 additions & 2 deletions packages/google-cloud-serverless/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ function getCjsOnlyIntegrations(): Integration[] {
}

/** Get the default integrations for the GCP SDK. */
export function getDefaultIntegrations(_options: Options): Integration[] {
return [...getDefaultIntegrationsWithoutPerformance(), ...getCjsOnlyIntegrations()];
export function getDefaultIntegrations(options: Options): Integration[] {
return [...getDefaultIntegrationsWithoutPerformance(options), ...getCjsOnlyIntegrations()];
}

/**
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 @@ -23,6 +23,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;

// Different implementation in server and worker
export declare const vercelAIIntegration: typeof serverSdk.vercelAIIntegration;
Expand Down
Loading
Loading