Skip to content

Commit cae415c

Browse files
authored
Merge branch 'develop' into sig/wrap-with-resolver-nuxt
2 parents 28bb272 + 046b6d0 commit cae415c

307 files changed

Lines changed: 12653 additions & 9071 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Work in this release was contributed by @psh4607, @thijsw, @trinitiwowka, @nehap
1414
- `DenoMongoose` => `Mongoose`
1515
- `DenoMysql` => `Mysql`
1616
- `DenoPostgres` => `Postgres`
17+
- feat(node): Add first-party Mastra integration ([#23823](https://github.com/getsentry/sentry-javascript/pull/23823)). Enabled by default; disable with `defaultIntegrations: integrations => integrations.filter(i => i.name !== 'Mastra')`.
1718
- **feat(browser): Add `bfcacheMetricsIntegration` to track back/forward cache health**
1819

1920
The new opt-in `bfcacheMetricsIntegration` emits metrics about browser back/forward cache (bfcache) navigations, so you can

MIGRATION.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ User IP address inference, which was previously gated on `sendDefaultPii`, is no
246246
`dataCollection.userInfo`. An explicit `requestDataIntegration({ include: { ip: true } })` overrides
247247
`dataCollection.userInfo: false` for data collected by that integration.
248248

249+
#### Astro client IP
250+
251+
`trackClientIp` no longer defaults to `false`. When you leave it unset, `handleRequest` now follows
252+
`dataCollection.userInfo`, which defaults to `true`, so Astro apps that set neither option start
253+
reporting `user.ip_address`. Pass `trackClientIp: false` to keep the v10 behaviour.
254+
249255
#### Remix action form data
250256

251257
`captureActionFormDataKeys` is an integration-level override, so it no longer requires
@@ -1303,6 +1309,41 @@ Affected SDKs: `@sentry/remix`.
13031309
13041310
The plugin now also applies the build-time instrumentation transform. If you added `sentryOrchestrionPlugin()` from `@sentry/server-utils/orchestrion/vite` to your Vite config manually, remove it. Opt out with `sentryRemixVitePlugin({ buildTimeInstrumentation: false })`.
13051311
1312+
### React: Simpler React Router setup via `@sentry/react/react-router`
1313+
1314+
Affected SDKs: `@sentry/react`.
1315+
1316+
`@sentry/react` gained a new `@sentry/react/react-router` entry point that pulls the required React Router hooks (`useLocation`, `useNavigationType`, `matchRoutes`, `createRoutesFromChildren`) from `react-router` for you, so you no longer have to thread them through `reactRouterBrowserTracingIntegration` yourself:
1317+
1318+
```diff
1319+
- import * as Sentry from '@sentry/react';
1320+
- import { useEffect } from 'react';
1321+
- import { createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from 'react-router';
1322+
+ import * as Sentry from '@sentry/react';
1323+
+ import { reactRouterBrowserTracingIntegration } from '@sentry/react/react-router';
1324+
1325+
Sentry.init({
1326+
integrations: [
1327+
- Sentry.reactRouterBrowserTracingIntegration({
1328+
- useEffect,
1329+
- useLocation,
1330+
- useNavigationType,
1331+
- createRoutesFromChildren,
1332+
- matchRoutes,
1333+
- }),
1334+
+ reactRouterBrowserTracingIntegration(),
1335+
],
1336+
});
1337+
```
1338+
1339+
The `wrapReactRouterRouting`, `wrapUseRoutes`, `wrapCreateBrowserRouter` and `wrapCreateMemoryRouter` helpers are re-exported from `@sentry/react/react-router` as well.
1340+
1341+
This entry requires `react-router` to be resolvable — it is declared as an optional peer dependency and supports React Router v6, v7 and v8. If you are on React Router v6 with only `react-router-dom` installed, either add `react-router` as a dependency or keep importing `reactRouterBrowserTracingIntegration` from `@sentry/react` and pass the hooks explicitly.
1342+
1343+
The existing `@sentry/react` API is unchanged and keeps working; passing the hooks there is now optional too (`useEffect` in particular is no longer used and can be omitted).
1344+
1345+
Additionally — for **every** `@sentry/react` routing setup, not just the new entry — the order in which you add the browser tracing integration and wrap your routes no longer matters.
1346+
13061347
## 3. Removed APIs
13071348
13081349
### `@sentry/core` / All SDKs
@@ -2133,6 +2174,7 @@ The main entry re-exported the build plugin statically, which pulled the whole b
21332174
`any`.
21342175
- Attribute typing and serialization were unified across the SDK.
21352176
- The `attributes` field on the `ScopeData` type is now required. `Scope.getScopeData()` always returned it, so this only affects code that constructs `ScopeData` objects manually — add `attributes: {}` there.
2177+
- The `attributes` field on the `SamplingContext` passed to `tracesSampler` is now required (previously optional); it is always provided by the SDK, so this only affects code that narrows or constructs `SamplingContext` objects by hand.
21362178
- The `endTimestamp` property was removed from the `SentrySpanArguments` interface. It was never part of
21372179
`StartSpanOptions`, so it could only be passed by ignoring TypeScript, in which case the span ended itself
21382180
during construction. Call `span.end(timestamp)` instead.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { registerWebWorkerWasm } from '@sentry/wasm';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
traceLifecycle: 'static',
8+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
9+
});
10+
11+
// `registerWebWorkerWasm` installs the same patches a worker would, and reports
12+
// every registered module to the scope it is given. Collecting them here is the
13+
// only way to observe registration from the page, since main-thread images stay
14+
// module-internal until a frame matches one.
15+
window.registeredImages = [];
16+
registerWebWorkerWasm({
17+
self: {
18+
postMessage: message => window.registeredImages.push(...(message._sentryWasmImages || [])),
19+
},
20+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
window.loadWasmFromBuffer = async () => {
2+
const response = await fetch('https://localhost:5887/simple.wasm');
3+
const buffer = await response.arrayBuffer();
4+
5+
await WebAssembly.instantiate(new Uint8Array(buffer), {
6+
env: {
7+
external_func: () => {},
8+
},
9+
});
10+
11+
return window.registeredImages;
12+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { Page, Route } from '@playwright/test';
2+
import { expect } from '@playwright/test';
3+
import fs from 'fs';
4+
import path from 'path';
5+
import { sentryTest } from '../../../utils/fixtures';
6+
import { shouldSkipWASMTests } from '../../../utils/wasmHelpers';
7+
8+
function serveWasmFixture(page: Page): Promise<void> {
9+
return page.route('**/simple.wasm', (route: Route) => {
10+
const wasmModule = fs.readFileSync(path.resolve(__dirname, '..', 'simple.wasm'));
11+
12+
return route.fulfill({
13+
status: 200,
14+
body: wasmModule,
15+
headers: {
16+
'Content-Type': 'application/wasm',
17+
},
18+
});
19+
});
20+
}
21+
22+
sentryTest(
23+
'registers a module loaded via fetch, arrayBuffer and instantiate under its response url',
24+
async ({ getLocalTestUrl, page, browserName }) => {
25+
if (shouldSkipWASMTests(browserName)) {
26+
sentryTest.skip();
27+
}
28+
29+
const url = await getLocalTestUrl({ testDir: __dirname });
30+
await serveWasmFixture(page);
31+
await page.goto(url);
32+
33+
const images = await page.evaluate(async () => {
34+
// @ts-expect-error this function exists
35+
return window.loadWasmFromBuffer();
36+
});
37+
38+
expect(images).toEqual([
39+
{
40+
type: 'wasm',
41+
code_file: 'https://localhost:5887/simple.wasm',
42+
code_id: '0ba020cdd2444f7eafdd25999a8e9010',
43+
debug_file: null,
44+
debug_id: '0ba020cdd2444f7eafdd25999a8e90100',
45+
},
46+
]);
47+
},
48+
);

dev-packages/cloudflare-integration-tests/runner.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ type Expected = Envelope | ((envelope: Envelope) => void);
141141

142142
type StartResult = {
143143
completed(): Promise<void>;
144+
/** Every non-ignored envelope received so far, matched or not, for count assertions. */
145+
getReceivedEnvelopes(): Envelope[];
144146
makeRequest<T>(
145147
method: 'get' | 'post',
146148
path: string,
@@ -225,6 +227,7 @@ export function createRunner(...paths: string[]) {
225227
});
226228

227229
const expectedEnvelopeCount = expectedEnvelopes.length;
230+
const receivedEnvelopes: Envelope[] = [];
228231

229232
let envelopeCount = 0;
230233
let unexpectedEnvelopeError: Error | undefined;
@@ -271,6 +274,8 @@ export function createRunner(...paths: string[]) {
271274
return;
272275
}
273276

277+
receivedEnvelopes.push(envelope);
278+
274279
// Resolve per-request waiters first, matching in any order so a request
275280
// expecting multiple envelopes isn't sensitive to their arrival order.
276281
if (envelopeWaiters.length > 0) {
@@ -444,6 +449,9 @@ export function createRunner(...paths: string[]) {
444449
throw unexpectedEnvelopeError;
445450
}
446451
},
452+
getReceivedEnvelopes: function (): Envelope[] {
453+
return receivedEnvelopes;
454+
},
447455
makeRequest: async function <T>(
448456
method: 'get' | 'post',
449457
path: string,

dev-packages/cloudflare-integration-tests/suites/vite-autoinstrument/durableobject-reexport-instrumented/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ interface Env {
88
// `Counter` is imported from another module (`./counter`) where it was already
99
// manually wrapped with `instrumentDurableObjectWithSentry`, then re-exported
1010
// here. The auto-instrument transform runs over this entry and sees
11-
// `export { Counter }`, but `Counter` is an imported binding — not a local class
12-
// declaration — so it cannot (and must not) wrap it. The DO stays instrumented
13-
// solely via the manual wrap in `./counter`, and the plain default export below
14-
// is still auto-wrapped with `withSentry`.
11+
// `export { Counter }`, but nothing in this file reveals that the binding is
12+
// already wrapped, so it emits its wrapper behind a guard
13+
// (`_INTERNAL_wrapUnlessInstrumented`) that returns the manual wrap unchanged
14+
// instead of nesting. The plain default export below is still auto-wrapped
15+
// with `withSentry`.
1516
export { Counter };
1617

1718
export default {

dev-packages/cloudflare-integration-tests/suites/vite-autoinstrument/durableobject-reexport-instrumented/test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ function expectMainWorkerTransaction(transactionEvent: TransactionEvent): void {
4343

4444
// `Counter` is manually wrapped with `instrumentDurableObjectWithSentry` in a
4545
// separate module (`./counter`), imported into the entry, and re-exported via a
46-
// plain `export { Counter }`. Because `Counter` is an imported binding rather
47-
// than a local class declaration, the transform cannot wrap it in the entry and
48-
// must leave it alone — no double-wrap, no broken build. The DO stays
49-
// instrumented via the manual wrap, so we still expect a storage-bearing DO
50-
// transaction, alongside the auto-wrapped default export's child-less one.
51-
it('leaves an imported, already-instrumented Durable Object untouched and still wraps the default export', async ({
46+
// plain `export { Counter }`. The transform sees only the imported binding, so it
47+
// emits its wrapper behind `_INTERNAL_wrapUnlessInstrumented`, which recognizes
48+
// the hand-wrapped class and hands it straight back. Without that guard the two
49+
// wrappers nest and every storage call reports twice, so the exactly-two span
50+
// assertion below is the real check. The DO stays instrumented via the manual
51+
// wrap, alongside the auto-wrapped default export's child-less transaction.
52+
it('does not double-instrument an imported, already-wrapped Durable Object and still wraps the default export', async ({
5253
signal,
5354
}) => {
5455
const runner = createRunner(__dirname)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
import { WorkerEntrypoint } from 'cloudflare:workers';
3+
4+
interface Env {
5+
SENTRY_DSN: string;
6+
}
7+
8+
class GreeterImpl extends WorkerEntrypoint<Env> {
9+
async fetch(): Promise<Response> {
10+
return new Response('Hello from the entrypoint');
11+
}
12+
}
13+
14+
// Manually instrumented here, in a module separate from the worker entry, which
15+
// only imports and re-exports the wrapped class.
16+
export const GreeterEntrypoint = Sentry.withSentry(
17+
(env: Env) => ({ dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0 }),
18+
GreeterImpl,
19+
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { GreeterEntrypoint } from './greeter';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
SELF: Fetcher;
6+
}
7+
8+
// `GreeterEntrypoint` was already wrapped by hand in `./greeter`. The
9+
// auto-instrument transform cannot see that from this entry (it only knows the
10+
// class from the self service binding in wrangler.jsonc), so it emits its
11+
// wrapper behind `_INTERNAL_wrapUnlessInstrumented`, which hands the manual
12+
// wrap back unchanged instead of nesting a second wrapper around it.
13+
export { GreeterEntrypoint };
14+
15+
export default {
16+
async fetch(request: Request, env: Env): Promise<Response> {
17+
const url = new URL(request.url);
18+
19+
if (url.pathname === '/call-entrypoint') {
20+
return env.SELF.fetch(new Request('https://self/greet'));
21+
}
22+
23+
return new Response('Not found', { status: 404 });
24+
},
25+
} satisfies ExportedHandler<Env>;

0 commit comments

Comments
 (0)