Skip to content

Commit 13743e9

Browse files
authored
Merge pull request #25184 from getsentry/prepare-release/11.6.0
meta(changelog): Update changelog for 11.6.0
2 parents 4c3386a + 3605c50 commit 13743e9

436 files changed

Lines changed: 16426 additions & 10270 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.

‎.size-limit.js‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ module.exports = [
419419
import: createImport('init'),
420420
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
421421
gzip: true,
422-
limit: '150 KB',
422+
limit: '151 KB',
423423
disablePlugins: ['@size-limit/esbuild'],
424424
},
425425
{
@@ -465,7 +465,7 @@ module.exports = [
465465
path: 'packages/node/build/esm/index.js',
466466
import: createImport('init'),
467467
gzip: true,
468-
limit: '128 KB',
468+
limit: '130 KB',
469469
disablePlugins: ['@size-limit/esbuild'],
470470
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
471471
modifyWebpackConfig: function (config) {
@@ -533,6 +533,27 @@ module.exports = [
533533
return config;
534534
},
535535
},
536+
// Next.js on Cloudflare Workers (ESM) - minified to match `wrangler deploy --dry-run --minify` output. An import of
537+
// `@sentry/node` in this entry more than doubles its size.
538+
{
539+
name: '@sentry/nextjs/cloudflare (withSentry) - minified',
540+
path: 'packages/nextjs/build/esm/cloudflare/index.js',
541+
import: createImport('withSentry'),
542+
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
543+
gzip: false,
544+
brotli: false,
545+
limit: '223 KiB',
546+
disablePlugins: ['@size-limit/webpack'],
547+
webpack: false,
548+
modifyEsbuildConfig: function (config) {
549+
config.keepNames = true;
550+
// Match wrangler's build settings
551+
config.conditions = ['workerd', 'worker', 'browser'];
552+
config.platform = 'browser';
553+
config.format = 'esm';
554+
return config;
555+
},
556+
},
536557
];
537558

538559
function createImport(...args) {

‎CHANGELOG.md‎

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

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

7+
## 11.6.0
8+
9+
### Important Changes
10+
11+
- **feat(nextjs): Add `@sentry/nextjs/cloudflare` ([#24998](https://github.com/getsentry/sentry-javascript/pull/24998))**
12+
13+
The new `@sentry/nextjs/cloudflare` entry point exports `withSentry` for the Worker entry of a Next.js app on Cloudflare Workers, for example `.open-next/worker.js` of OpenNext. It is `withSentry` of `@sentry/cloudflare` with the Next.js server handling added, so a Next.js app on Workers gets the same spans as on Node.js.
14+
15+
```ts
16+
import * as Sentry from '@sentry/nextjs/cloudflare';
17+
import handler from './.open-next/worker.js';
18+
19+
export default Sentry.withSentry(
20+
env => ({
21+
dsn: env.SENTRY_DSN,
22+
tracesSampleRate: 1.0,
23+
}),
24+
handler,
25+
);
26+
```
27+
28+
- **feat(server-utils): Add instrumentation for pi-durable ([#24993](https://github.com/getsentry/sentry-javascript/pull/24993))**
29+
30+
The new `piDurableIntegration` traces agents built with [pi-durable](https://github.com/earendil-works/pi/tree/main/packages/durable): each run, with its model requests and tool calls.
31+
32+
- **fix(core): Correct sleep clock drift on telemetry timestamps ([#23054](https://github.com/getsentry/sentry-javascript/pull/23054))**
33+
34+
When a device sleeps, the `performance.now()` clock of the browser stops. Before this change, timestamps of spans and other telemetry recorded after the sleep could be too early. The SDK now detects this drift and corrects its time origin, also for spans created from performance entries. Span end times are now computed from the `performance.now()` duration since the span start, like in OpenTelemetry ([#24903](https://github.com/getsentry/sentry-javascript/pull/24903)).
35+
36+
### Other Changes
37+
38+
- feat(cloudflare): Skip spans for the pi_ tables of PiHarness ([#25028](https://github.com/getsentry/sentry-javascript/pull/25028))
39+
- feat(remix): Propagate the server trace to Remix 3 page loads ([#25145](https://github.com/getsentry/sentry-javascript/pull/25145))
40+
- feat(server-utils): Map Mastra classifier evaluations to gen_ai.evaluate spans ([#25044](https://github.com/getsentry/sentry-javascript/pull/25044))
41+
- feat(server-utils): Record LangChain TypeSafeClassifier runs as gen_ai.evaluate spans ([#25120](https://github.com/getsentry/sentry-javascript/pull/25120))
42+
- fix(browser): Check for clock drift on page lifecycle events ([#25091](https://github.com/getsentry/sentry-javascript/pull/25091))
43+
- fix(core): Exclude attachments from internal exception events ([#25160](https://github.com/getsentry/sentry-javascript/pull/25160))
44+
- fix(effect): Convert Effect span times to the Sentry clock ([#24972](https://github.com/getsentry/sentry-javascript/pull/24972))
45+
- fix(effect): Detect HTTP spans named after the request method ([#25159](https://github.com/getsentry/sentry-javascript/pull/25159))
46+
- fix(node): Register Vercel keep-alive listeners once ([#25146](https://github.com/getsentry/sentry-javascript/pull/25146))
47+
- fix(replay): Place CLS and INP web vitals at the right time ([#24990](https://github.com/getsentry/sentry-javascript/pull/24990))
48+
- fix(server-utils): Record LangGraph span I/O for non-MessagesAnnotation state ([#23315](https://github.com/getsentry/sentry-javascript/pull/23315))
49+
- fix(sveltekit): Upload source maps with SvelteKit 3 ([#25139](https://github.com/getsentry/sentry-javascript/pull/25139))
50+
51+
<details>
52+
<summary> <strong>Internal Changes</strong> </summary>
53+
54+
- chore: Add external contributor to CHANGELOG.md ([#25136](https://github.com/getsentry/sentry-javascript/pull/25136))
55+
- chore(deps-dev): Bump next from 15.5.24 to 15.5.27 ([#25171](https://github.com/getsentry/sentry-javascript/pull/25171))
56+
- chore(nitro): Remove stale bundler-plugin-core rollup external ([#25149](https://github.com/getsentry/sentry-javascript/pull/25149))
57+
- feat(cloudflare): Wrap vinext Worker entries with `@sentry/nextjs/cloudflare` ([#25000](https://github.com/getsentry/sentry-javascript/pull/25000))
58+
- feat(deps): Bump next from 16.2.11 to 16.3.8 in /dev-packages/e2e-tests/test-applications/nextjs-sourcemaps ([#25170](https://github.com/getsentry/sentry-javascript/pull/25170))
59+
- fix(deps): Bump @graphql-tools/utils to 12.0.3 ([#25162](https://github.com/getsentry/sentry-javascript/pull/25162))
60+
- ref(eslint-plugin-sdk): Improve `no-unsafe-random-apis` rule ([#25123](https://github.com/getsentry/sentry-javascript/pull/25123))
61+
- test(cloudflare): Port the remaining static suites to span streaming ([#25112](https://github.com/getsentry/sentry-javascript/pull/25112))
62+
- test(cloudflare): Widen the wrangler readiness retry window in integration tests ([#24925](https://github.com/getsentry/sentry-javascript/pull/24925))
63+
- test(e2e): Add a cloudflare-pi-durable end-to-end application ([#25022](https://github.com/getsentry/sentry-javascript/pull/25022))
64+
- test(e2e): Add a node-pi-durable end-to-end application ([#24994](https://github.com/getsentry/sentry-javascript/pull/24994))
65+
- test(e2e): Add Mastra Jev classifier test to node-mastra ([#25115](https://github.com/getsentry/sentry-javascript/pull/25115))
66+
- test(e2e): Wait for the soft navigation entry before hiding the page ([#25127](https://github.com/getsentry/sentry-javascript/pull/25127))
67+
- test(nextjs): Run nextjs-16 on Bun, Deno and Cloudflare ([#24999](https://github.com/getsentry/sentry-javascript/pull/24999))
68+
- test(node): Add streamed span collection helpers ([#25131](https://github.com/getsentry/sentry-javascript/pull/25131))
69+
- test(node): Cover Jev calls inside createAgent and LangGraph ([#25132](https://github.com/getsentry/sentry-javascript/pull/25132))
70+
- test(node): Port AMQP integration tests to span streaming ([#25158](https://github.com/getsentry/sentry-javascript/pull/25158))
71+
- test(node): Port Anthropic integration tests to span streaming ([#25082](https://github.com/getsentry/sentry-javascript/pull/25082))
72+
- test(node): Port Bedrock tests to span streaming ([#25086](https://github.com/getsentry/sentry-javascript/pull/25086))
73+
- test(node): Port Google GenAI tests to span streaming ([#25083](https://github.com/getsentry/sentry-javascript/pull/25083))
74+
- test(node): Port Knex integration tests to span streaming ([#25154](https://github.com/getsentry/sentry-javascript/pull/25154))
75+
- test(node): Port LangChain tests to span streaming ([#25084](https://github.com/getsentry/sentry-javascript/pull/25084))
76+
- test(node): Port LangGraph tests to span streaming ([#25085](https://github.com/getsentry/sentry-javascript/pull/25085))
77+
- test(node): Port Mastra tests to span streaming ([#25087](https://github.com/getsentry/sentry-javascript/pull/25087))
78+
- test(node): Port MySQL integration tests to span streaming ([#25148](https://github.com/getsentry/sentry-javascript/pull/25148))
79+
- test(node): Port OpenAI integration tests to span streaming ([#25081](https://github.com/getsentry/sentry-javascript/pull/25081))
80+
- test(node): Port pool and DataLoader integration tests to span streaming ([#25156](https://github.com/getsentry/sentry-javascript/pull/25156))
81+
- test(node): Port Tedious integration tests to span streaming ([#25155](https://github.com/getsentry/sentry-javascript/pull/25155))
82+
- test(node): Port Vercel AI v5-v7 tests to span streaming ([#25090](https://github.com/getsentry/sentry-javascript/pull/25090))
83+
84+
</details>
85+
86+
Work in this release was contributed by @zkasuran. Thank you for your contribution!
87+
788
## 11.5.0
889

990
### Important Changes

‎dev-packages/bun-integration-tests/node-suites/excludes.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const NO_AUTO_INSTRUMENTATION = [
100100
'suites/tracing/knex/**',
101101
'suites/tracing/koa/test.ts',
102102
'suites/tracing/langchain/**',
103-
'suites/tracing/langgraph/test.ts',
103+
'suites/tracing/langgraph/**',
104104
'suites/tracing/lru-memoizer/test.ts',
105105
'suites/tracing/mastra/test.ts',
106106
'suites/tracing/mcp-handler-exact-once/test.ts',
@@ -124,6 +124,7 @@ export const NO_AUTO_INSTRUMENTATION = [
124124
'suites/tracing/openai/v6/test.ts',
125125
'suites/tracing/openai/v7/test.ts',
126126
'suites/tracing/orchestrion-lazy-registration/test.ts',
127+
'suites/tracing/pi-durable/test.ts',
127128
'suites/tracing/postgres-streamed/test.ts',
128129
'suites/tracing/postgres/test.ts',
129130
'suites/tracing/postgresjs-streamed/test.ts',
@@ -244,6 +245,7 @@ const BUN_BUILD_NOT_TRIAGED = [
244245
'suites/tracing/google-genai-v2/test.ts',
245246
'suites/tracing/google-genai/test.ts',
246247
'suites/tracing/langchain/v1/test.ts',
248+
'suites/tracing/langgraph/v1/test.ts',
247249
'suites/tracing/mastra/test.ts',
248250
'suites/tracing/mcp-handler-exact-once/test.ts',
249251
'suites/tracing/mcp-server-streamed/test.ts',

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ type RetryOptions = { maxRetries?: number; retryDelayMs?: number };
8585
async function fetchWithRetry(
8686
url: string,
8787
init: RequestInit,
88-
{ maxRetries = 25, retryDelayMs = 200 }: RetryOptions = {},
88+
{ maxRetries = 75, retryDelayMs = 200 }: RetryOptions = {},
8989
): Promise<Response> {
9090
for (let attempt = 0; attempt < maxRetries; attempt++) {
9191
try {
@@ -569,7 +569,12 @@ export function createRunner(...paths: string[]) {
569569

570570
if (!res.ok) {
571571
if (!expectError) {
572-
reject(new Error(`Expected request to "${path}" to succeed, but got a ${res.status} response`));
572+
const responseBody = await res.text().catch(() => '');
573+
reject(
574+
new Error(
575+
`Expected request to "${path}" to succeed, but got a ${res.status} response: ${responseBody.slice(0, 500)}`,
576+
),
577+
);
573578
}
574579

575580
return;

‎dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-overlapping/instrument.server.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@ import { defineCloudflareOptions } from '@sentry/cloudflare';
22

33
export default defineCloudflareOptions((env: { SENTRY_DSN: string }) => ({
44
dsn: env.SENTRY_DSN,
5-
traceLifecycle: 'static',
65
tracesSampleRate: 1.0,
76
}));
Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,35 @@
1-
import type { TransactionEvent } from '@sentry/core';
21
import { expect, it } from 'vitest';
32
import { createRunner } from '../../../../runner';
3+
import { getSpanOp } from '../../../../spanUtils';
44

55
it('propagates the worker trace into each of two overlapping Durable Object RPC calls', async ({ signal }) => {
6-
const doTraces: Record<string, TransactionEvent['contexts']> = {};
7-
let workerTraceId: string | undefined;
8-
let workerSpanId: string | undefined;
9-
10-
const runner = createRunner(__dirname)
11-
.unordered()
12-
.expect(envelope => {
13-
const transactionEvent = envelope[1]?.[0]?.[1] as TransactionEvent;
14-
expect(transactionEvent.transaction).toBe('work');
15-
expect(transactionEvent.contexts?.trace?.op).toBe('rpc');
16-
expect(transactionEvent.contexts?.trace?.data?.['test.label']).toBe('a');
17-
doTraces.a = transactionEvent.contexts;
18-
})
19-
.expect(envelope => {
20-
const transactionEvent = envelope[1]?.[0]?.[1] as TransactionEvent;
21-
expect(transactionEvent.transaction).toBe('work');
22-
expect(transactionEvent.contexts?.trace?.op).toBe('rpc');
23-
expect(transactionEvent.contexts?.trace?.data?.['test.label']).toBe('b');
24-
doTraces.b = transactionEvent.contexts;
25-
})
26-
.expect(envelope => {
27-
const transactionEvent = envelope[1]?.[0]?.[1] as TransactionEvent;
28-
expect(transactionEvent.transaction).toBe('GET /overlapping');
29-
expect(transactionEvent.contexts?.trace?.op).toBe('http.server');
30-
workerTraceId = transactionEvent.contexts?.trace?.trace_id;
31-
workerSpanId = transactionEvent.contexts?.trace?.span_id;
32-
})
33-
.start(signal);
6+
const runner = createRunner(__dirname).start(signal);
7+
8+
// The worker and the Durable Object stream from separate isolates, and both RPC segments come from
9+
// the same Durable Object, so they can share an envelope.
10+
const spansPromise = runner.collectStreamedSpans(
11+
spansOfTrace => spansOfTrace.filter(span => span.is_segment).length === 3,
12+
);
3413

3514
const response = await runner.makeRequest<string>('get', '/overlapping');
3615
expect(response).toBe('a,b');
3716

38-
await runner.completed();
17+
const segmentSpans = (await spansPromise).filter(span => span.is_segment);
18+
const workerSpan = segmentSpans.find(span => getSpanOp(span) === 'http.server');
19+
const rpcSpanA = segmentSpans.find(span => span.attributes['test.label']?.value === 'a');
20+
const rpcSpanB = segmentSpans.find(span => span.attributes['test.label']?.value === 'b');
21+
22+
// `/overlapping` is a raw URL, so the streamed segment name keeps the method only.
23+
expect(workerSpan?.name).toBe('GET');
24+
expect(workerSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/overlapping' });
3925

40-
expect(workerTraceId).toMatch(/^[\da-f]{32}$/);
41-
expect(workerSpanId).toMatch(/^[\da-f]{16}$/);
26+
expect(rpcSpanA?.name).toBe('work');
27+
expect(getSpanOp(rpcSpanA!)).toBe('rpc');
28+
expect(rpcSpanA?.parent_span_id).toBe(workerSpan?.span_id);
4229

43-
expect(doTraces.a?.trace?.trace_id).toBe(workerTraceId);
44-
expect(doTraces.a?.trace?.parent_span_id).toBe(workerSpanId);
45-
expect(doTraces.b?.trace?.trace_id).toBe(workerTraceId);
46-
expect(doTraces.b?.trace?.parent_span_id).toBe(workerSpanId);
30+
expect(rpcSpanB?.name).toBe('work');
31+
expect(getSpanOp(rpcSpanB!)).toBe('rpc');
32+
expect(rpcSpanB?.parent_span_id).toBe(workerSpan?.span_id);
4733

48-
expect(doTraces.a?.trace?.span_id).not.toBe(doTraces.b?.trace?.span_id);
34+
expect(rpcSpanA?.span_id).not.toBe(rpcSpanB?.span_id);
4935
});

‎dev-packages/cloudflare-integration-tests/suites/vite-autoinstrument/workerentrypoint-reexport-instrumented/greeter.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ class GreeterImpl extends WorkerEntrypoint<Env> {
1414
// Manually instrumented here, in a module separate from the worker entry, which
1515
// only imports and re-exports the wrapped class.
1616
export const GreeterEntrypoint = Sentry.withSentry(
17-
(env: Env) => ({ dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0 }),
17+
(env: Env) => ({ dsn: env.SENTRY_DSN, tracesSampleRate: 1.0 }),
1818
GreeterImpl,
1919
);

‎dev-packages/cloudflare-integration-tests/suites/vite-autoinstrument/workerentrypoint-reexport-instrumented/instrument.server.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@ import { defineCloudflareOptions } from '@sentry/cloudflare';
22

33
export default defineCloudflareOptions((env: { SENTRY_DSN: string }) => ({
44
dsn: env.SENTRY_DSN,
5-
traceLifecycle: 'static',
65
tracesSampleRate: 1.0,
76
}));
Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
1-
import type { TransactionEvent } from '@sentry/core';
21
import { expect, it } from 'vitest';
32
import { createRunner } from '../../../runner';
3+
import { getSpanOp } from '../../../spanUtils';
44

55
// `GreeterEntrypoint` is hand-wrapped in `./greeter` and only re-exported by
66
// the entry, so the transform's emitted `_INTERNAL_wrapUnlessInstrumented`
77
// guard must hand the manual wrap back instead of nesting a second wrapper.
8-
// Nested entrypoint wrappers each instrument `fetch`, which shows up as extra
9-
// spans on the entrypoint transaction, the strict shape below catches that.
8+
// A nested wrapper throws inside the entrypoint and fails the request. The
9+
// child-less assertion below catches a nested wrapper that runs without an
10+
// error but instruments `fetch` a second time.
1011
it('does not double-instrument an imported, already-wrapped WorkerEntrypoint', async ({ signal }) => {
11-
const runner = createRunner(__dirname)
12-
.unordered()
13-
.expect(envelope => {
14-
const transactionEvent = envelope[1]?.[0]?.[1] as TransactionEvent;
15-
// The entrypoint's own transaction, child-less when wrapped exactly once.
16-
expect(transactionEvent.transaction).toBe('GET /greet');
17-
expect(transactionEvent.contexts?.trace?.op).toBe('http.server');
18-
expect(transactionEvent.spans ?? []).toHaveLength(0);
19-
})
20-
.expect(envelope => {
21-
const transactionEvent = envelope[1]?.[0]?.[1] as TransactionEvent;
22-
// The auto-wrapped default export's transaction.
23-
expect(transactionEvent.transaction).toBe('GET /call-entrypoint');
24-
expect(transactionEvent.contexts?.trace?.op).toBe('http.server');
25-
})
26-
.start(signal);
12+
const runner = createRunner(__dirname).start(signal);
13+
14+
// The default handler and the entrypoint stream from separate isolates, so the two segment spans
15+
// of the trace arrive in separate envelopes.
16+
const spansPromise = runner.collectStreamedSpans(
17+
spansOfTrace => spansOfTrace.filter(span => span.is_segment).length === 2,
18+
);
2719

2820
await runner.makeRequest('get', '/call-entrypoint');
29-
await runner.completed();
21+
22+
const spans = await spansPromise;
23+
// Both routes are raw URLs, so the streamed segment names keep the method only and the route is
24+
// read from `url.path`.
25+
const workerSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/call-entrypoint');
26+
const entrypointSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/greet');
27+
28+
// The auto-wrapped default export's segment span.
29+
expect(workerSpan?.name).toBe('GET');
30+
expect(getSpanOp(workerSpan!)).toBe('http.server');
31+
32+
expect(entrypointSpan?.name).toBe('GET');
33+
expect(getSpanOp(entrypointSpan!)).toBe('http.server');
34+
expect(entrypointSpan?.parent_span_id).toBe(workerSpan?.span_id);
35+
// The entrypoint's spans stay buffered until its invocation flushes, so a child span would arrive
36+
// in the same envelope as the entrypoint's segment span.
37+
expect(spans.filter(span => span.parent_span_id === entrypointSpan?.span_id)).toEqual([]);
3038
});

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export default {
1515
async fetch(request: Request, env: Env): Promise<Response> {
1616
const url = new URL(request.url);
1717

18-
// Issued by the test after `/trigger` returned, its transaction is the
18+
// Issued by the test after `/trigger` returned, its segment span is the
1919
// sentinel proving every earlier envelope (including a duplicate step
20-
// transaction from an accidental double wrap) has been delivered.
20+
// span from an accidental double wrap) has been delivered.
2121
if (url.pathname === '/sentinel') {
2222
return new Response('ok');
2323
}
@@ -26,7 +26,7 @@ export default {
2626
const instance = await env.MY_WORKFLOW.create();
2727
// Respond only once the workflow finished, so every step envelope (including
2828
// a duplicate from an accidental double wrap) is sent before this request's
29-
// own transaction completes the test's expectations.
29+
// own segment span completes the test's expectations.
3030
for (let i = 0; i < 20; i++) {
3131
try {
3232
const s = await instance.status();

0 commit comments

Comments
 (0)