Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nuxt): Adding experimental_basicServerTracing option to Nuxt module #13643

Merged
merged 8 commits into from
Sep 13, 2024
Merged
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
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a reminder to also update the PR title accordingly ;)

};
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,
);
}
}
});
}
Loading