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
34 changes: 29 additions & 5 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,34 @@ Affected SDKs: `@sentry/cloudflare`.

Calls to rate limiter bindings (`env.MY_RATE_LIMITER.limit()`) no longer create a span. The removed span had the op `rpc`, the origin `auto.faas.cloudflare.rate_limit`, and the attribute `rpc.service: cloudflare.rate_limit`. Remove any dashboard, alert, or `ignoreSpans` entry that references it.

### `@sentry/nuxt`: the server config is bundled, `--import` is no longer needed

The SDK now bundles `sentry.server.config.ts` into the Nitro server build, where it initializes itself when the server starts. Instrumentation happens at build time, so preloading the config file is no longer necessary.

Remove the `--import` flag from your production start command:

```bash
# before
node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs

# after
node .output/server/index.mjs
```

Old start commands keep working: the SDK still emits a file at the old path, but it only prints a reminder that the flag can be removed. If you preload a file that calls `Sentry.init` yourself, that init wins and the bundled one is skipped.

The same applies in development. Remove the `NODE_OPTIONS` preload:

```bash
# before
NODE_OPTIONS='--import ./.nuxt/dev/sentry.server.config.mjs' nuxt dev

# after
nuxt dev
```

Since no preload is needed anymore, the `autoInjectServerSentry` option (`'top-level-import'` and `'experimental_dynamic-import'`) and `experimental_entrypointWrappedFunctions` are deprecated. Remove them from your `sentry` module options as the default behavior replaces both. They will be deleted in the next major version.

### `@sentry/ember` is now a v2 addon with manual setup

Affected SDKs: `@sentry/ember`.
Expand Down Expand Up @@ -1698,11 +1726,7 @@ public/instrument.server.ts
sentry.server.config.ts
```

After the rename, the SDK also emits `.output/server/sentry.server.config.mjs` for you to preload:

```bash
node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs
```
After the rename, the SDK bundles the file into the Nitro server build and initializes itself at server startup. See ["the server config is bundled"](#sentrynuxt-the-server-config-is-bundled---import-is-no-longer-needed) above: the `--import` preload is no longer needed.

The deprecated `sourceMapsUploadOptions` module option was removed. Move its fields to the root level of the `sentry` module options. Note that `url` was renamed to `sentryUrl`, and `enabled` was replaced by `sourcemaps.disable` (inverted: `enabled: false` becomes `sourcemaps: { disable: true }`).

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"clean": "npx nuxi cleanup",
"test": "playwright test",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "bash ./nuxt-start-dev-server.bash && TEST_ENV=development playwright test environment",
"test:dev": "TEST_ENV=development playwright test environment db-drivers",
"test:build": "pnpm install && pnpm build",
"test:build-canary": "pnpm add nuxt@npm:nuxt-nightly@latest && pnpm add nitropack@npm:nitropack-nightly@latest && pnpm install --force && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ if (!testEnv) {

const getStartCommand = () => {
if (testEnv === 'development') {
return "NODE_OPTIONS='--import ./.nuxt/dev/sentry.server.config.mjs' nuxt dev -p 3030";
// The Sentry server config is bundled into the dev server via a nitro plugin, so no preload is needed.
return 'nuxt dev -p 3030';
}

if (testEnv === 'production') {
return 'pnpm start:import';
return 'pnpm start';
}

throw new Error(`Unknown test env: ${testEnv}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

// The Nuxt module auto-wires the orchestrion build-time transform, which injects
// `diagnostics_channel` publishers into these drivers as Nitro bundles them. That
// only happens in the production build, so these tests are excluded from the
// `test:dev` pass (which filters to `environment`).
// `diagnostics_channel` publishers into these drivers as Nitro bundles them. `nuxt dev`
// has no bundle to transform, so there the drivers rely on runtime injection instead.
test('Instruments ioredis automatically', async ({ baseURL }) => {
// ioredis 5.10.x has no native channels, so dev needs runtime injection — but the dev bundle hoists
// its import above the inlined `Sentry.init`, so it loads before injection is active. (ioredis >=5.11
// publishes native channels and does work in dev; mysql requires its file lazily, after init.)
test.skip(process.env.TEST_ENV === 'development', 'ioredis <5.11 loads before runtime injection is active in dev');

const transactionEventPromise = waitForTransaction('nuxt-4-static', transactionEvent => {
return (
transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/db-ioredis'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Simulates a v10-style `node --import` preload that fully initializes the SDK
// before the config bundled into the server build runs its own `Sentry.init`.
import * as Sentry from '@sentry/nuxt';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
tunnel: 'http://localhost:3031/',
});

This file was deleted.

15 changes: 11 additions & 4 deletions dev-packages/e2e-tests/test-applications/nuxt-4/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
"clean": "npx nuxi cleanup",
"test": "playwright test",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "bash ./nuxt-start-dev-server.bash && TEST_ENV=development playwright test environment",
"test:build": "pnpm install && pnpm build",
"test:build-canary": "pnpm add nuxt@npm:nuxt-nightly@latest && pnpm add nitropack@npm:nitropack-nightly@latest && pnpm install --force && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev"
"test:dev": "TEST_ENV=development playwright test environment db-drivers",
"test:build": "pnpm install && node ./scripts/build-with-prerender-event-sink.mjs",
"test:build-canary": "pnpm add nuxt@npm:nuxt-nightly@latest && pnpm add nitropack@npm:nitropack-nightly@latest && pnpm install --force && node ./scripts/build-with-prerender-event-sink.mjs",
"test:assert": "pnpm test:prod && pnpm test:dev",
"test:prod:import": "TEST_ENV=production-import playwright test"
},
"//": "Pin ioredis to 5.10.1 because that's the last version before it publishes its own diagnostics channels",
"dependencies": {
"@pinia/nuxt": "^0.5.5",
"@sentry/nuxt": "file:../../packed/sentry-nuxt-packed.tgz",
Expand All @@ -38,6 +40,11 @@
"build-command": "E2E_TEST_OTEL_SETUP=true pnpm test:build",
"assert-command": "E2E_TEST_OTEL_SETUP=true pnpm test:assert",
"label": "nuxt-4 (tracer provider)"
},
{
"build-command": "pnpm test:build",
"assert-command": "pnpm test:prod:import",
"label": "nuxt-4 (--import compat)"
}
],
"optionalVariants": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ if (!testEnv) {

const getStartCommand = () => {
if (testEnv === 'development') {
return "NODE_OPTIONS='--import ./.nuxt/dev/sentry.server.config.mjs' nuxt dev -p 3030";
// The Sentry server config is bundled into the dev server via a nitro plugin, so no preload is needed.
return 'nuxt dev -p 3030';
}

if (testEnv === 'production') {
return 'pnpm start';
}

// Runs the suite with the compat shim preloaded, like existing `--import` deploy commands do.
if (testEnv === 'production-import') {
return 'pnpm start:import';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Runs `nuxt build` with a sink on the Sentry tunnel port and records how many envelopes arrive,
// so tests can assert that prerendering sends no telemetry during the build.
import { spawn } from 'node:child_process';
import { mkdirSync, writeFileSync } from 'node:fs';
import { createServer } from 'node:http';

let envelopeCount = 0;
const sink = createServer((req, res) => {
req.resume();
req.on('end', () => {
envelopeCount += 1;
res.writeHead(200).end('{}');
});
});

const sinkAvailable = await new Promise(resolve => {
sink.once('error', () => resolve(false));
sink.listen(3031, () => resolve(true));
});

const build = spawn('nuxt', ['build'], { stdio: 'inherit', shell: true });
const exitCode = await new Promise(resolve => build.on('exit', resolve));

sink.close();
mkdirSync('.output', { recursive: true });
writeFileSync(
'.output/build-envelope-count.json',
JSON.stringify({ envelopeCount: sinkAvailable ? envelopeCount : null }),
);
process.exit(exitCode ?? 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineNitroPlugin } from 'nitropack/runtime';

// Throws during module evaluation, before any plugin function runs.
// The `aa-` prefix makes this the first scanned plugin.
if (process.env.SENTRY_TEST_EVAL_CRASH) {
throw new Error('eval-crash-test');
}

export default defineNitroPlugin(() => {});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineNitroPlugin } from 'nitropack/runtime';

// Throws while nitro runs its plugins, before `listen`.
// The `zz-` prefix makes this the last scanned plugin (`aa-eval-crash.ts` covers the earliest point).
export default defineNitroPlugin(() => {
if (process.env.SENTRY_TEST_STARTUP_CRASH) {
throw new Error('startup-crash-test');
}
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readFileSync, readdirSync } from 'node:fs';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';
import { expect, test } from '@playwright/test';

Expand Down Expand Up @@ -43,3 +43,37 @@ test.describe('Orchestrion build-time injection', () => {
expect(clientBundle).not.toMatch(/orchestrion:/);
});
});

test.describe('Sentry server config injection', () => {
test('evaluates Sentry.init before nitro runs its plugins', () => {
const nitroChunk = readFileSync(path.join(process.cwd(), '.output/server/chunks/nitro/nitro.mjs'), 'utf8');

// The app DSN only appears in the transpiled `Sentry.init` options object, so it marks where
// init evaluates inside the chunk.
const initIndex = nitroChunk.indexOf('https://public@dsn.ingest.sentry.io/1337');
const runPluginsIndex = nitroChunk.indexOf('runNitroPlugins');

expect(initIndex).toBeGreaterThan(-1);
expect(runPluginsIndex).toBeGreaterThan(-1);
expect(initIndex).toBeLessThan(runPluginsIndex);
});

test('emits the `--import` compatibility shim at the former config path', () => {
const shimPath = path.join(process.cwd(), '.output/server/sentry.server.config.mjs');

expect(existsSync(shimPath)).toBe(true);
expect(readFileSync(shimPath, 'utf8')).toContain('no longer needed');
});

test('sends no telemetry during the prerendering build', () => {
// Prerendering executes the server bundle at build time; init is skipped there, so the tunnel
// must receive zero envelopes while `test:build` runs (counted by scripts/build-with-prerender-event-sink.mjs).
const countPath = path.join(process.cwd(), '.output/build-envelope-count.json');
test.skip(!existsSync(countPath), 'build ran without the event-sink wrapper (use `pnpm test:build`)');

const { envelopeCount } = JSON.parse(readFileSync(countPath, 'utf8'));
test.skip(envelopeCount === null, 'tunnel port was busy during the build');

expect(envelopeCount).toBe(0);
});
Comment thread
s1gr1d marked this conversation as resolved.
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { expect, test } from '@playwright/test';
import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils';

// The Nuxt module auto-wires the orchestrion build-time transform, which injects
// `diagnostics_channel` publishers into these drivers as Nitro bundles them. That
// only happens in the production build, so these tests are excluded from the
// `test:dev` pass (which filters to `environment`).
// `diagnostics_channel` publishers into these drivers as Nitro bundles them. `nuxt dev`
// has no bundle to transform, so there the drivers rely on runtime injection instead.

// Exact-match API routes keep a bare method-only segment name under h3 v1, so the
// segment is selected via its `url.path` attribute. Driver spans can flush before
Expand All @@ -17,6 +16,11 @@ async function collectRequestSpans(path: string) {
}

test('Instruments ioredis automatically', async ({ baseURL }) => {
// ioredis 5.10.x has no native channels, so dev needs runtime injection — but the dev bundle hoists
// its import above the inlined `Sentry.init`, so it loads before injection is active. (ioredis >=5.11
// publishes native channels and does work in dev; mysql requires its file lazily, after init.)
test.skip(process.env.TEST_ENV === 'development', 'ioredis <5.11 loads before runtime injection is active in dev');

const spansPromise = collectRequestSpans('/api/db-ioredis');

const response = await fetch(`${baseURL}/api/db-ioredis`);
Expand Down
Loading
Loading