From df79871ab34707932511bd562ef2627e552c7457 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:48:45 +0200 Subject: [PATCH] feat(nuxt): Adding `experimental_basicServerTracing` option to Nuxt module (#13643) Enabling this option will import the Sentry server config at the top of the server entry file. This can be used when adding the node option `--import` does not work. This however only comes with limited tracing functionality. Example Usage: ```js export default defineNuxtConfig({ sentry: { // ... other options simplifiedDeployment: true }, }) --- packages/nuxt/README.md | 54 ++++++++++++++++------- packages/nuxt/src/common/types.ts | 12 +++++ packages/nuxt/src/module.ts | 21 +++++---- packages/nuxt/src/vite/addServerConfig.ts | 41 +++++++++++++++-- 4 files changed, 102 insertions(+), 26 deletions(-) diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md index df41599e45b9..b16d555a3648 100644 --- a/packages/nuxt/README.md +++ b/packages/nuxt/README.md @@ -37,21 +37,23 @@ functionality related to Nuxt. **What is partly working:** +- Source Maps +- Connected Tracing (Frontend & Backend) - Tracing by setting `tracesSampleRate` - UI (Vue) traces - HTTP (Node) traces -**What is not yet(!) included:** - -- Source Maps -- Nuxt-specific traces and connecting frontend & backend traces - **Known Issues:** -- When adding `sentry.server.config.(ts/js)`, you get this error: "Failed to register ESM hook", but the application - will still work -- When initializing Sentry on the server with `instrument.server.(js|ts)`, you get an `'import-in-the-middle'` error, - and the application won't work +- When adding `sentry.server.config.(ts/js)`, you get an error like this: + "`Failed to register ESM hook (import-in-the-middle/hook.mjs)`". You can add a resolution for `@vercel/nft` to fix + this. This will add the `hook.mjs` file to your build output + ([issue here](https://github.com/unjs/nitro/issues/2703)). + ```json + "resolutions": { + "@vercel/nft": "^0.27.4" + } + ``` ## Automatic Setup @@ -93,16 +95,18 @@ export default defineNuxtConfig({ Add a `sentry.client.config.(js|ts)` file to the root of your project: ```javascript +import { useRuntimeConfig } from '#imports'; import * as Sentry from '@sentry/nuxt'; Sentry.init({ - dsn: process.env.SENTRY_DSN, + // If set up, you can use your runtime config here + dsn: useRuntimeConfig().public.sentry.dsn, }); ``` ### 4. Server-side setup -Add an `instrument.server.mjs` file to your `public` folder: +Add an `sentry.client.config.(js|ts)` file to the root of your project: ```javascript import * as Sentry from '@sentry/nuxt'; @@ -110,18 +114,38 @@ import * as Sentry from '@sentry/nuxt'; // Only run `init` when process.env.SENTRY_DSN is available. if (process.env.SENTRY_DSN) { Sentry.init({ - dsn: process.env.SENTRY_DSN, + dsn: 'your-dsn', }); } ``` -Add an import flag to the `NODE_OPTIONS` of your preview script in the `package.json` file, so the file loads before any -other imports: +The Nuxt runtime config does not work in the Sentry server to technical reasons (it has to be loaded before Nuxt is +loaded). To be able to use `process.env` you either have to add `--env-file=.env` to your node command + +```bash +node --env-file=.env --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs +``` + +or use the `dotenv` package: + +```javascript +import dotenv from 'dotenv'; +import * as Sentry from '@sentry/nuxt'; + +dotenv.config(); + +Sentry.init({ + dsn: process.env.SENTRY_DSN, +}); +``` + +Add an import flag to the Node options of your `node` command (not `nuxt preview`), so the file loads before any other +imports (keep in mind the `.mjs` file ending): ```json { "scripts": { - "preview": "NODE_OPTIONS='--import ./public/instrument.server.mjs' nuxt preview" + "start": "node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs" } } ``` diff --git a/packages/nuxt/src/common/types.ts b/packages/nuxt/src/common/types.ts index 08dc0d2b805e..5fbe68bd89cb 100644 --- a/packages/nuxt/src/common/types.ts +++ b/packages/nuxt/src/common/types.ts @@ -99,4 +99,16 @@ export type SentryNuxtModuleOptions = { * Enabling this will give you, for example, logs about source maps. */ debug?: boolean; + + /** + * Enabling basic server tracing can be used for environments where modifying the node option `--import` is not possible. + * However, enabling this option only supports limited tracing instrumentation. Only http traces will be collected (but no database-specific traces etc.). + * + * If this option is `true`, the Sentry SDK will import the Sentry server config at the top of the server entry file to load the SDK on the server. + * + * **DO NOT** enable this option if you've already added the node option `--import` in your node start script. This would initialize Sentry twice on the server-side and leads to unexpected issues. + * + * @default false + */ + experimental_basicServerTracing?: boolean; }; diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index faa48e5c3c26..0afdeaa03de7 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -1,6 +1,6 @@ import { addPlugin, addPluginTemplate, addServerPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'; import type { SentryNuxtModuleOptions } from './common/types'; -import { addServerConfigToBuild } from './vite/addServerConfig'; +import { addSentryTopImport, addServerConfigToBuild } from './vite/addServerConfig'; import { setupSourceMaps } from './vite/sourceMaps'; import { findDefaultSdkInitFile } from './vite/utils'; @@ -62,15 +62,20 @@ export default defineNuxtModule({ if (clientConfigFile || serverConfigFile) { setupSourceMaps(moduleOptions, nuxt); } - if (serverConfigFile && serverConfigFile.includes('.server.config')) { - if (moduleOptions.debug) { - // eslint-disable-next-line no-console - console.log( - `[Sentry] Using your \`${serverConfigFile}\` file for the server-side Sentry configuration. In case you have a \`public/instrument.server\` file, the \`public/instrument.server\` file will be ignored. Make sure the file path in your node \`--import\` option matches the Sentry server config file in your \`.output\` folder and has a \`.mjs\` extension.`, - ); - } + if (serverConfigFile && serverConfigFile.includes('.server.config')) { addServerConfigToBuild(moduleOptions, nuxt, serverConfigFile); + + if (moduleOptions.experimental_basicServerTracing) { + addSentryTopImport(moduleOptions, nuxt); + } else { + if (moduleOptions.debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Using your \`${serverConfigFile}\` file for the server-side Sentry configuration. In case you have a \`public/instrument.server\` file, the \`public/instrument.server\` file will be ignored. Make sure the file path in your node \`--import\` option matches the Sentry server config file in your \`.output\` folder and has a \`.mjs\` extension.`, + ); + } + } } }, }); diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts index dc1fc21dd6bd..dee15ee34dce 100644 --- a/packages/nuxt/src/vite/addServerConfig.ts +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -1,5 +1,4 @@ import * as fs from 'fs'; -import * as path from 'path'; import { createResolver } from '@nuxt/kit'; import type { Nuxt } from '@nuxt/schema'; import type { SentryNuxtModuleOptions } from '../common/types'; @@ -30,8 +29,9 @@ export function addServerConfigToBuild( * This is necessary because we need to reference this file path in the node --import option. */ nuxt.hook('close', async () => { - const source = path.resolve('.nuxt/dist/server/sentry.server.config.mjs'); - const destination = path.resolve('.output/server/sentry.server.config.mjs'); + const rootDirResolver = createResolver(nuxt.options.rootDir); + const source = rootDirResolver.resolve('.nuxt/dist/server/sentry.server.config.mjs'); + const destination = rootDirResolver.resolve('.output/server/sentry.server.config.mjs'); try { await fs.promises.access(source, fs.constants.F_OK); @@ -55,3 +55,38 @@ export function addServerConfigToBuild( }); }); } + +/** + * Adds the Sentry server config import at the top of the server entry file to load the SDK on the server. + * This is necessary for environments where modifying the node option `--import` is not possible. + * However, only limited tracing instrumentation is supported when doing this. + */ +export function addSentryTopImport(moduleOptions: SentryNuxtModuleOptions, nuxt: Nuxt): void { + nuxt.hook('close', async () => { + const rootDirResolver = createResolver(nuxt.options.rootDir); + const entryFilePath = rootDirResolver.resolve('.output/server/index.mjs'); + + try { + fs.readFile(entryFilePath, 'utf8', (err, data) => { + const updatedContent = `import './sentry.server.config.mjs';\n${data}`; + + fs.writeFile(entryFilePath, updatedContent, 'utf8', () => { + if (moduleOptions.debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Successfully added the Sentry import to the server entry file "\`${entryFilePath}\`"`, + ); + } + }); + }); + } catch (err) { + if (moduleOptions.debug) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] An error occurred when trying to add the Sentry import to the server entry file "\`${entryFilePath}\`":`, + err, + ); + } + } + }); +}