Skip to content

Commit 05f78b4

Browse files
maxkostyLuca Forstnerandreiborza
authored
feat(core): Make matcher parameter optional in makeMultiplexedTransport (#10798)
Hoping to reduce complexity for setting up micro-frontends routing. Also: I suppose we could just make Matcher argument optional instead of exporting a separate function... that may be a cleaner design --------- Co-authored-by: Luca Forstner <[email protected]> Co-authored-by: Andrei Borza <[email protected]>
1 parent cabb611 commit 05f78b4

File tree

5 files changed

+169
-4
lines changed

5 files changed

+169
-4
lines changed

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,67 @@
44

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

7+
### Important Changes
8+
9+
- **feat(core): Make `matcher` parameter optional in `makeMultiplexedTransport` ([#10798](https://github.com/getsentry/sentry-javascript/pull/10798))** .
10+
11+
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.
12+
13+
**Before:**
14+
15+
```javascript
16+
import { makeFetchTransport, makeMultiplexedTransport } from '@sentry/browser';
17+
18+
const EXTRA_KEY = 'ROUTE_TO';
19+
20+
const transport = makeMultiplexedTransport(makeFetchTransport, args => {
21+
const event = args.getEvent();
22+
if (event?.extra?.[EXTRA_KEY] && Array.isArray(event.extra[EXTRA_KEY])) {
23+
return event.extra[EXTRA_KEY];
24+
}
25+
return [];
26+
});
27+
28+
Sentry.init({
29+
transport,
30+
// ... other options
31+
});
32+
33+
// Capture events with routing info
34+
Sentry.captureException(error, {
35+
extra: {
36+
[EXTRA_KEY]: [
37+
{ dsn: 'https://[email protected]/project1', release: 'v1.0.0' },
38+
{ dsn: 'https://[email protected]/project2' },
39+
],
40+
},
41+
});
42+
```
43+
44+
**After:**
45+
46+
```javascript
47+
import { makeFetchTransport, makeMultiplexedTransport, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from '@sentry/browser';
48+
49+
// Just pass the transport generator - the default matcher handles the rest!
50+
Sentry.init({
51+
transport: makeMultiplexedTransport(makeFetchTransport),
52+
// ... other options
53+
});
54+
55+
// Capture events with routing info using the exported constant
56+
Sentry.captureException(error, {
57+
extra: {
58+
[MULTIPLEXED_TRANSPORT_EXTRA_KEY]: [
59+
{ dsn: 'https://[email protected]/project1', release: 'v1.0.0' },
60+
{ dsn: 'https://[email protected]/project2' },
61+
],
62+
},
63+
});
64+
```
65+
66+
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.
67+
768
- **feat(nextjs): Support cacheComponents on turbopack ([#18304](https://github.com/getsentry/sentry-javascript/pull/18304))**
869
970
This release adds support for `cacheComponents` on turbopack builds. We are working on adding support for this feature in webpack builds as well.

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export {
5757
getSpanStatusFromHttpCode,
5858
setHttpStatus,
5959
makeMultiplexedTransport,
60+
MULTIPLEXED_TRANSPORT_EXTRA_KEY,
6061
moduleMetadataIntegration,
6162
supabaseIntegration,
6263
instrumentSupabaseClient,

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export { ServerRuntimeClient } from './server-runtime-client';
5454
export { initAndBind, setCurrentClient } from './sdk';
5555
export { createTransport } from './transports/base';
5656
export { makeOfflineTransport } from './transports/offline';
57-
export { makeMultiplexedTransport } from './transports/multiplexed';
57+
export { makeMultiplexedTransport, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from './transports/multiplexed';
5858
export { getIntegrationsToSetup, addIntegration, defineIntegration, installedIntegrations } from './integration';
5959
export {
6060
_INTERNAL_skipAiProviderWrapping,

packages/core/src/transports/multiplexed.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ interface MatchParam {
2121
type RouteTo = { dsn: string; release: string };
2222
type Matcher = (param: MatchParam) => (string | RouteTo)[];
2323

24+
/**
25+
* Key used in event.extra to provide routing information for the multiplexed transport.
26+
* Should contain an array of `{ dsn: string, release?: string }` objects.
27+
*/
28+
export const MULTIPLEXED_TRANSPORT_EXTRA_KEY = 'MULTIPLEXED_TRANSPORT_EXTRA_KEY';
29+
2430
/**
2531
* Gets an event from an envelope.
2632
*
@@ -79,15 +85,33 @@ function overrideDsn(envelope: Envelope, dsn: string): Envelope {
7985

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

101+
// Use provided matcher or default to simple multiplexed transport behavior
102+
const actualMatcher: Matcher =
103+
matcher ||
104+
(args => {
105+
const event = args.getEvent();
106+
if (
107+
event?.extra?.[MULTIPLEXED_TRANSPORT_EXTRA_KEY] &&
108+
Array.isArray(event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY])
109+
) {
110+
return event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY];
111+
}
112+
return [];
113+
});
114+
91115
function getTransport(dsn: string, release: string | undefined): [string, Transport] | undefined {
92116
// We create a transport for every unique dsn/release combination as there may be code from multiple releases in
93117
// use at the same time
@@ -118,7 +142,7 @@ export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
118142
return eventFromEnvelope(envelope, eventTypes);
119143
}
120144

121-
const transports = matcher({ envelope, getEvent })
145+
const transports = actualMatcher({ envelope, getEvent })
122146
.map(result => {
123147
if (typeof result === 'string') {
124148
return getTransport(result, undefined);

packages/core/test/lib/transports/multiplexed.test.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
makeMultiplexedTransport,
99
parseEnvelope,
1010
} from '../../../src';
11-
import { eventFromEnvelope } from '../../../src/transports/multiplexed';
11+
import { eventFromEnvelope, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from '../../../src/transports/multiplexed';
1212
import type { ClientReport } from '../../../src/types-hoist/clientreport';
1313
import type { Envelope, EventEnvelope, EventItem } from '../../../src/types-hoist/envelope';
1414
import type { TransactionEvent } from '../../../src/types-hoist/event';
@@ -242,3 +242,82 @@ describe('makeMultiplexedTransport', () => {
242242
await transport.send(TRANSACTION_ENVELOPE);
243243
});
244244
});
245+
246+
describe('makeMultiplexedTransport() with default matcher', () => {
247+
it('sends events to targets provided in event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY]', async () => {
248+
expect.assertions(2);
249+
250+
const makeTransport = makeMultiplexedTransport(
251+
createTestTransport(
252+
url => {
253+
expect(url).toBe(DSN1_URL);
254+
},
255+
url => {
256+
expect(url).toBe(DSN2_URL);
257+
},
258+
),
259+
);
260+
261+
const envelope = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
262+
[
263+
{ type: 'event' },
264+
{
265+
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
266+
extra: {
267+
[MULTIPLEXED_TRANSPORT_EXTRA_KEY]: [DSN1, DSN2],
268+
},
269+
},
270+
] as EventItem,
271+
]);
272+
273+
const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
274+
await transport.send(envelope);
275+
});
276+
277+
it('sends events to default DSN if event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY] is not set', async () => {
278+
expect.assertions(1);
279+
280+
const makeTransport = makeMultiplexedTransport(
281+
createTestTransport(url => {
282+
expect(url).toBe(DSN1_URL);
283+
}),
284+
);
285+
286+
const envelope = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
287+
[
288+
{ type: 'event' },
289+
{
290+
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
291+
},
292+
] as EventItem,
293+
]);
294+
295+
const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
296+
await transport.send(envelope);
297+
});
298+
299+
it('sends events to default DSN if event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY] is an empty array', async () => {
300+
expect.assertions(1);
301+
302+
const makeTransport = makeMultiplexedTransport(
303+
createTestTransport(url => {
304+
expect(url).toBe(DSN1_URL);
305+
}),
306+
);
307+
308+
const envelope = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
309+
[
310+
{ type: 'event' },
311+
{
312+
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
313+
extra: {
314+
[MULTIPLEXED_TRANSPORT_EXTRA_KEY]: [],
315+
},
316+
},
317+
] as EventItem,
318+
]);
319+
320+
const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
321+
await transport.send(envelope);
322+
});
323+
});

0 commit comments

Comments
 (0)