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
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,67 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

### Important Changes

- **feat(core): Make `matcher` parameter optional in `makeMultiplexedTransport` ([#10798](https://github.com/getsentry/sentry-javascript/pull/10798))** .

The `matcher` parameter in `makeMultiplexedTransport` is now optional with a sensible default. This makes it much easier to use the multiplexed transport for sending events to multiple DSNs based on runtime configuration.

**Before:**

```javascript
import { makeFetchTransport, makeMultiplexedTransport } from '@sentry/browser';

const EXTRA_KEY = 'ROUTE_TO';

const transport = makeMultiplexedTransport(makeFetchTransport, args => {
const event = args.getEvent();
if (event?.extra?.[EXTRA_KEY] && Array.isArray(event.extra[EXTRA_KEY])) {
return event.extra[EXTRA_KEY];
}
return [];
});

Sentry.init({
transport,
// ... other options
});

// Capture events with routing info
Sentry.captureException(error, {
extra: {
[EXTRA_KEY]: [
{ dsn: 'https://[email protected]/project1', release: 'v1.0.0' },
{ dsn: 'https://[email protected]/project2' },
],
},
});
```

**After:**

```javascript
import { makeFetchTransport, makeMultiplexedTransport, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from '@sentry/browser';

// Just pass the transport generator - the default matcher handles the rest!
Sentry.init({
transport: makeMultiplexedTransport(makeFetchTransport),
// ... other options
});

// Capture events with routing info using the exported constant
Sentry.captureException(error, {
extra: {
[MULTIPLEXED_TRANSPORT_EXTRA_KEY]: [
{ dsn: 'https://[email protected]/project1', release: 'v1.0.0' },
{ dsn: 'https://[email protected]/project2' },
],
},
});
```

The default matcher looks for routing information in `event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY]`. You can still provide a custom matcher function for advanced use cases.

- **feat(nextjs): Support cacheComponents on turbopack ([#18304](https://github.com/getsentry/sentry-javascript/pull/18304))**

This release adds support for `cacheComponents` on turbopack builds. We are working on adding support for this feature in webpack builds as well.
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export {
getSpanStatusFromHttpCode,
setHttpStatus,
makeMultiplexedTransport,
MULTIPLEXED_TRANSPORT_EXTRA_KEY,
moduleMetadataIntegration,
supabaseIntegration,
instrumentSupabaseClient,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export { ServerRuntimeClient } from './server-runtime-client';
export { initAndBind, setCurrentClient } from './sdk';
export { createTransport } from './transports/base';
export { makeOfflineTransport } from './transports/offline';
export { makeMultiplexedTransport } from './transports/multiplexed';
export { makeMultiplexedTransport, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from './transports/multiplexed';
export { getIntegrationsToSetup, addIntegration, defineIntegration, installedIntegrations } from './integration';
export {
_INTERNAL_skipAiProviderWrapping,
Expand Down
28 changes: 26 additions & 2 deletions packages/core/src/transports/multiplexed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ interface MatchParam {
type RouteTo = { dsn: string; release: string };
type Matcher = (param: MatchParam) => (string | RouteTo)[];

/**
* Key used in event.extra to provide routing information for the multiplexed transport.
* Should contain an array of `{ dsn: string, release?: string }` objects.
*/
export const MULTIPLEXED_TRANSPORT_EXTRA_KEY = 'MULTIPLEXED_TRANSPORT_EXTRA_KEY';

/**
* Gets an event from an envelope.
*
Expand Down Expand Up @@ -79,15 +85,33 @@ function overrideDsn(envelope: Envelope, dsn: string): Envelope {

/**
* Creates a transport that can send events to different DSNs depending on the envelope contents.
*
* If no matcher is provided, the transport will look for routing information in
* `event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY]`, which should contain
* an array of `{ dsn: string, release?: string }` objects.
*/
export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
createTransport: (options: TO) => Transport,
matcher: Matcher,
matcher?: Matcher,
): (options: TO) => Transport {
return options => {
const fallbackTransport = createTransport(options);
const otherTransports: Map<string, Transport> = new Map();

// Use provided matcher or default to simple multiplexed transport behavior
const actualMatcher: Matcher =
matcher ||
(args => {
const event = args.getEvent();
if (
event?.extra?.[MULTIPLEXED_TRANSPORT_EXTRA_KEY] &&
Array.isArray(event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY])
) {
return event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY];
}
return [];
});

function getTransport(dsn: string, release: string | undefined): [string, Transport] | undefined {
// We create a transport for every unique dsn/release combination as there may be code from multiple releases in
// use at the same time
Expand Down Expand Up @@ -118,7 +142,7 @@ export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
return eventFromEnvelope(envelope, eventTypes);
}

const transports = matcher({ envelope, getEvent })
const transports = actualMatcher({ envelope, getEvent })
.map(result => {
if (typeof result === 'string') {
return getTransport(result, undefined);
Expand Down
81 changes: 80 additions & 1 deletion packages/core/test/lib/transports/multiplexed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
makeMultiplexedTransport,
parseEnvelope,
} from '../../../src';
import { eventFromEnvelope } from '../../../src/transports/multiplexed';
import { eventFromEnvelope, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from '../../../src/transports/multiplexed';
import type { ClientReport } from '../../../src/types-hoist/clientreport';
import type { Envelope, EventEnvelope, EventItem } from '../../../src/types-hoist/envelope';
import type { TransactionEvent } from '../../../src/types-hoist/event';
Expand Down Expand Up @@ -242,3 +242,82 @@ describe('makeMultiplexedTransport', () => {
await transport.send(TRANSACTION_ENVELOPE);
});
});

describe('makeMultiplexedTransport() with default matcher', () => {
it('sends events to targets provided in event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY]', async () => {
expect.assertions(2);

const makeTransport = makeMultiplexedTransport(
createTestTransport(
url => {
expect(url).toBe(DSN1_URL);
},
url => {
expect(url).toBe(DSN2_URL);
},
),
);

const envelope = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[
{ type: 'event' },
{
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
extra: {
[MULTIPLEXED_TRANSPORT_EXTRA_KEY]: [DSN1, DSN2],
},
},
] as EventItem,
]);

const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
await transport.send(envelope);
});

it('sends events to default DSN if event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY] is not set', async () => {
expect.assertions(1);

const makeTransport = makeMultiplexedTransport(
createTestTransport(url => {
expect(url).toBe(DSN1_URL);
}),
);

const envelope = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[
{ type: 'event' },
{
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
},
] as EventItem,
]);

const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
await transport.send(envelope);
});

it('sends events to default DSN if event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY] is an empty array', async () => {
expect.assertions(1);

const makeTransport = makeMultiplexedTransport(
createTestTransport(url => {
expect(url).toBe(DSN1_URL);
}),
);

const envelope = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[
{ type: 'event' },
{
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
extra: {
[MULTIPLEXED_TRANSPORT_EXTRA_KEY]: [],
},
},
] as EventItem,
]);

const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
await transport.send(envelope);
});
});
Loading