Skip to content

Commit e38f89d

Browse files
authored
feat(nuxt): Add vue-router instrumentation (#13054)
Adding the Vue BrowserTracing instrumentation to get parametrized routes.
1 parent 47ed220 commit e38f89d

File tree

8 files changed

+155
-95
lines changed

8 files changed

+155
-95
lines changed

dev-packages/e2e-tests/test-applications/nuxt-3/components/ErrorButton.vue

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
<script setup>
2+
import { defineProps } from 'vue';
3+
4+
const props = defineProps({
5+
errorText: {
6+
type: String,
7+
required: true
8+
}
9+
})
10+
211
const triggerError = () => {
3-
throw new Error('Error thrown from Nuxt-3 E2E test app');
12+
throw new Error(props.errorText);
413
};
514
</script>
615

dev-packages/e2e-tests/test-applications/nuxt-3/pages/client-error.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ErrorButton from '../components/ErrorButton.vue';
33
</script>
44

55
<template>
6-
<ErrorButton />
6+
<ErrorButton error-text="Error thrown from Nuxt-3 E2E test app"/>
77
</template>
88

99

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<template>
2+
<p>{{ $route.params.param }} - {{ $route.params.param }}</p>
3+
<ErrorButton errorText="Error thrown from Param Route Button" />
4+
</template>

dev-packages/e2e-tests/test-applications/nuxt-3/tests/errors.client.test.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ test.describe('client-side errors', async () => {
1212

1313
const error = await errorPromise;
1414

15+
expect(error.transaction).toEqual('/client-error');
1516
expect(error).toMatchObject({
1617
exception: {
1718
values: [
@@ -25,6 +26,33 @@ test.describe('client-side errors', async () => {
2526
],
2627
},
2728
});
28-
expect(error.transaction).toEqual('/client-error');
29+
});
30+
31+
test('shows parametrized route on button error', async ({ page }) => {
32+
const errorPromise = waitForError('nuxt-3', async errorEvent => {
33+
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Param Route Button';
34+
});
35+
36+
await page.goto(`/test-param/1234`);
37+
await page.locator('#errorBtn').click();
38+
39+
const error = await errorPromise;
40+
41+
expect(error.sdk.name).toEqual('sentry.javascript.nuxt');
42+
expect(error.transaction).toEqual('/test-param/:param()');
43+
expect(error.request.url).toMatch(/\/test-param\/1234/);
44+
expect(error).toMatchObject({
45+
exception: {
46+
values: [
47+
{
48+
type: 'Error',
49+
value: 'Error thrown from Param Route Button',
50+
mechanism: {
51+
handled: false,
52+
},
53+
},
54+
],
55+
},
56+
});
2957
});
3058
});

packages/nuxt/src/client/sdk.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
browserTracingIntegration,
3-
getDefaultIntegrations as getBrowserDefaultIntegrations,
4-
init as initBrowser,
5-
} from '@sentry/browser';
1+
import { getDefaultIntegrations as getBrowserDefaultIntegrations, init as initBrowser } from '@sentry/browser';
62
import { applySdkMetadata } from '@sentry/core';
73
import type { Client } from '@sentry/types';
84
import type { SentryNuxtOptions } from '../common/types';
@@ -14,7 +10,8 @@ import type { SentryNuxtOptions } from '../common/types';
1410
*/
1511
export function init(options: SentryNuxtOptions): Client | undefined {
1612
const sentryOptions = {
17-
defaultIntegrations: [...getBrowserDefaultIntegrations(options), browserTracingIntegration()],
13+
/* BrowserTracing is added later with the Nuxt client plugin */
14+
defaultIntegrations: [...getBrowserDefaultIntegrations(options)],
1815
...options,
1916
};
2017

packages/nuxt/src/common/types.ts

+67-67
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,93 @@ export type SentryNuxtOptions = Omit<Parameters<typeof init>[0] & object, 'app'>
55

66
type SourceMapsOptions = {
77
/**
8-
* Options for the Sentry Vite plugin to customize the source maps upload process.
8+
* If this flag is `true`, and an auth token is detected, the Sentry SDK will
9+
* automatically generate and upload source maps to Sentry during a production build.
910
*
10-
* These options are always read from the `sentry` module options in the `nuxt.config.(js|ts).
11-
* Do not define them in the `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
11+
* @default true
1212
*/
13-
sourceMapsUploadOptions?: {
14-
/**
15-
* If this flag is `true`, and an auth token is detected, the Sentry integration will
16-
* automatically generate and upload source maps to Sentry during a production build.
17-
*
18-
* @default true
19-
*/
20-
enabled?: boolean;
13+
enabled?: boolean;
2114

22-
/**
23-
* The auth token to use when uploading source maps to Sentry.
24-
*
25-
* Instead of specifying this option, you can also set the `SENTRY_AUTH_TOKEN` environment variable.
26-
*
27-
* To create an auth token, follow this guide:
28-
* @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens
29-
*/
30-
authToken?: string;
15+
/**
16+
* The auth token to use when uploading source maps to Sentry.
17+
*
18+
* Instead of specifying this option, you can also set the `SENTRY_AUTH_TOKEN` environment variable.
19+
*
20+
* To create an auth token, follow this guide:
21+
* @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens
22+
*/
23+
authToken?: string;
3124

32-
/**
33-
* The organization slug of your Sentry organization.
34-
* Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable.
35-
*/
36-
org?: string;
25+
/**
26+
* The organization slug of your Sentry organization.
27+
* Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable.
28+
*/
29+
org?: string;
30+
31+
/**
32+
* The project slug of your Sentry project.
33+
* Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable.
34+
*/
35+
project?: string;
3736

37+
/**
38+
* If this flag is `true`, the Sentry plugin will collect some telemetry data and send it to Sentry.
39+
* It will not collect any sensitive or user-specific data.
40+
*
41+
* @default true
42+
*/
43+
telemetry?: boolean;
44+
45+
/**
46+
* Options related to sourcemaps
47+
*/
48+
sourcemaps?: {
3849
/**
39-
* The project slug of your Sentry project.
40-
* Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable.
50+
* A glob or an array of globs that specify the build artifacts and source maps that will be uploaded to Sentry.
51+
*
52+
* If this option is not specified, sensible defaults based on your adapter and nuxt.config.js
53+
* setup will be used. Use this option to override these defaults, for instance if you have a
54+
* customized build setup that diverges from Nuxt's defaults.
55+
*
56+
* The globbing patterns must follow the implementation of the `glob` package.
57+
* @see https://www.npmjs.com/package/glob#glob-primer
4158
*/
42-
project?: string;
59+
assets?: string | Array<string>;
4360

4461
/**
45-
* If this flag is `true`, the Sentry plugin will collect some telemetry data and send it to Sentry.
46-
* It will not collect any sensitive or user-specific data.
62+
* A glob or an array of globs that specifies which build artifacts should not be uploaded to Sentry.
4763
*
48-
* @default true
64+
* @default [] - By default no files are ignored. Thus, all files matching the `assets` glob
65+
* or the default value for `assets` are uploaded.
66+
*
67+
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
4968
*/
50-
telemetry?: boolean;
69+
ignore?: string | Array<string>;
5170

5271
/**
53-
* Options related to sourcemaps
72+
* A glob or an array of globs that specifies the build artifacts that should be deleted after the artifact
73+
* upload to Sentry has been completed.
74+
*
75+
* @default [] - By default no files are deleted.
76+
*
77+
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
5478
*/
55-
sourcemaps?: {
56-
/**
57-
* A glob or an array of globs that specify the build artifacts and source maps that will be uploaded to Sentry.
58-
*
59-
* If this option is not specified, sensible defaults based on your adapter and nuxt.config.js
60-
* setup will be used. Use this option to override these defaults, for instance if you have a
61-
* customized build setup that diverges from Nuxt's defaults.
62-
*
63-
* The globbing patterns must follow the implementation of the `glob` package.
64-
* @see https://www.npmjs.com/package/glob#glob-primer
65-
*/
66-
assets?: string | Array<string>;
67-
68-
/**
69-
* A glob or an array of globs that specifies which build artifacts should not be uploaded to Sentry.
70-
*
71-
* @default [] - By default no files are ignored. Thus, all files matching the `assets` glob
72-
* or the default value for `assets` are uploaded.
73-
*
74-
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
75-
*/
76-
ignore?: string | Array<string>;
77-
78-
/**
79-
* A glob or an array of globs that specifies the build artifacts that should be deleted after the artifact
80-
* upload to Sentry has been completed.
81-
*
82-
* @default [] - By default no files are deleted.
83-
*
84-
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
85-
*/
86-
filesToDeleteAfterUpload?: string | Array<string>;
87-
};
79+
filesToDeleteAfterUpload?: string | Array<string>;
8880
};
8981
};
9082

9183
/**
9284
* Build options for the Sentry module. These options are used during build-time by the Sentry SDK.
9385
*/
94-
export type SentryNuxtModuleOptions = SourceMapsOptions & {
86+
export type SentryNuxtModuleOptions = {
87+
/**
88+
* Options for the Sentry Vite plugin to customize the source maps upload process.
89+
*
90+
* These options are always read from the `sentry` module options in the `nuxt.config.(js|ts).
91+
* Do not define them in the `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
92+
*/
93+
sourceMapsUploadOptions?: SourceMapsOptions;
94+
9595
/**
9696
* Enable debug functionality of the SDK during build-time.
9797
* Enabling this will give you, for example, logs about source maps.

packages/nuxt/src/runtime/plugins/sentry.client.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
11
import { getClient } from '@sentry/core';
2-
import { vueIntegration } from '@sentry/vue';
2+
import { browserTracingIntegration, vueIntegration } from '@sentry/vue';
33
import { defineNuxtPlugin } from 'nuxt/app';
44

5+
// --- Types are copied from @sentry/vue (so it does not need to be exported) ---
6+
// The following type is an intersection of the Route type from VueRouter v2, v3, and v4.
7+
// This is not great, but kinda necessary to make it work with all versions at the same time.
8+
type Route = {
9+
/** Unparameterized URL */
10+
path: string;
11+
/**
12+
* Query params (keys map to null when there is no value associated, e.g. "?foo" and to an array when there are
13+
* multiple query params that have the same key, e.g. "?foo&foo=bar")
14+
*/
15+
query: Record<string, string | null | (string | null)[]>;
16+
/** Route name (VueRouter provides a way to give routes individual names) */
17+
name?: string | symbol | null | undefined;
18+
/** Evaluated parameters */
19+
params: Record<string, string | string[]>;
20+
/** All the matched route objects as defined in VueRouter constructor */
21+
matched: { path: string }[];
22+
};
23+
24+
interface VueRouter {
25+
onError: (fn: (err: Error) => void) => void;
26+
beforeEach: (fn: (to: Route, from: Route, next?: () => void) => void) => void;
27+
}
28+
29+
// Tree-shakable guard to remove all code related to tracing
30+
declare const __SENTRY_TRACING__: boolean;
31+
532
export default defineNuxtPlugin(nuxtApp => {
33+
// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside
34+
// will get tree-shaken away
35+
if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
36+
const sentryClient = getClient();
37+
38+
if (sentryClient && '$router' in nuxtApp) {
39+
sentryClient.addIntegration(
40+
browserTracingIntegration({ router: nuxtApp.$router as VueRouter, routeLabel: 'path' }),
41+
);
42+
}
43+
}
44+
645
nuxtApp.hook('app:created', vueApp => {
746
const sentryClient = getClient();
847

packages/nuxt/test/client/sdk.test.ts

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as SentryBrowser from '@sentry/browser';
2-
import { type BrowserClient, SDK_VERSION, getClient } from '@sentry/vue';
2+
import { SDK_VERSION } from '@sentry/vue';
33
import { beforeEach, describe, expect, it, vi } from 'vitest';
44
import { init } from '../../src/client';
55

@@ -35,23 +35,6 @@ describe('Nuxt Client SDK', () => {
3535
expect(browserInit).toHaveBeenLastCalledWith(expect.objectContaining(expectedMetadata));
3636
});
3737

38-
describe('Automatically adds BrowserTracing integration', () => {
39-
it.each([
40-
['tracesSampleRate', { tracesSampleRate: 0 }],
41-
['tracesSampler', { tracesSampler: () => 1.0 }],
42-
['enableTracing', { enableTracing: true }],
43-
['no tracing option set', {}] /* enable "tracing without performance" by default */,
44-
])('adds a browserTracingIntegration if tracing is enabled via %s', (_, tracingOptions) => {
45-
init({
46-
dsn: 'https://[email protected]/1337',
47-
...tracingOptions,
48-
});
49-
50-
const browserTracing = getClient<BrowserClient>()?.getIntegrationByName('BrowserTracing');
51-
expect(browserTracing).toBeDefined();
52-
});
53-
});
54-
5538
it('returns client from init', () => {
5639
expect(init({})).not.toBeUndefined();
5740
});

0 commit comments

Comments
 (0)