Skip to content

Commit

Permalink
feat(nuxt): Adding experimental_basicServerTracing option to Nuxt m…
Browse files Browse the repository at this point in the history
…odule (#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
  },
})
  • Loading branch information
s1gr1d committed Sep 13, 2024
1 parent 1664dc7 commit df79871
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 26 deletions.
54 changes: 39 additions & 15 deletions packages/nuxt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -93,35 +95,57 @@ 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';

// 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"
}
}
```
Expand Down
12 changes: 12 additions & 0 deletions packages/nuxt/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
21 changes: 13 additions & 8 deletions packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -62,15 +62,20 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
);
}
}
}
},
});
41 changes: 38 additions & 3 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand All @@ -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,
);
}
}
});
}

0 comments on commit df79871

Please sign in to comment.