Skip to content

Commit

Permalink
feat(nuxt): Filter out Nuxt build assets (#13148)
Browse files Browse the repository at this point in the history
Filtering out events of the `_nuxt`
[buildAssetDir](https://nuxt.com/docs/api/nuxt-config#buildassetsdir).
Next step would be to change the regex based on the folder name (as this
could be changed in the nuxt config during build time - but probably
doesn't happen too often).

Also added some unit tests for the server-side of the SDK.

Before:
<img width="698" alt="image"
src="https://github.com/user-attachments/assets/2ec5ccfb-b8fb-449b-97ba-24fbecabd998">

After:
<img width="686" alt="image"
src="https://github.com/user-attachments/assets/adf43031-7521-44f5-b155-ea57259001f5">
  • Loading branch information
s1gr1d authored Aug 1, 2024
1 parent d17f1be commit 29f070f
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"preview": "NODE_OPTIONS='--import ./public/instrument.server.mjs' nuxt preview",
"clean": "npx nuxi cleanup",
"test": "playwright test",
"test:build": "pnpm install && npx playwright install && pnpm build",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from '@sentry/nuxt';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
environment: 'qa', // dynamic sampling bias to keep transactions
tracesSampleRate: 1.0, // Capture 100% of the transactions
tunnel: 'http://localhost:3031/', // proxy server
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';

test('sends a server action transaction on pageload', async ({ page }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction.includes('GET /test-param/');
});

await page.goto('/test-param/1234');

const transaction = await transactionPromise;

expect(transaction.contexts.trace).toEqual(
expect.objectContaining({
data: expect.objectContaining({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.http',
}),
}),
);
});

test('does not send transactions for build asset folder "_nuxt"', async ({ page }) => {
let buildAssetFolderOccurred = false;

waitForTransaction('nuxt-3', transactionEvent => {
if (transactionEvent.transaction?.match(/^GET \/_nuxt\//)) {
buildAssetFolderOccurred = true;
}
return false; // expects to return a boolean (but not relevant here)
});

const transactionEventPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction.includes('GET /test-param/');
});

await page.goto('/test-param/1234');

const transactionEvent = await transactionEventPromise;

expect(buildAssetFolderOccurred).toBe(false);

// todo: url not yet parametrized
expect(transactionEvent.transaction).toBe('GET /test-param/1234');
});
31 changes: 28 additions & 3 deletions packages/nuxt/src/server/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { applySdkMetadata } from '@sentry/core';
import { applySdkMetadata, getGlobalScope } from '@sentry/core';
import { init as initNode } from '@sentry/node';
import type { Client } from '@sentry/types';
import type { Client, EventProcessor } from '@sentry/types';
import type { SentryNuxtOptions } from '../common/types';

/**
Expand All @@ -15,5 +15,30 @@ export function init(options: SentryNuxtOptions): Client | undefined {

applySdkMetadata(sentryOptions, 'nuxt', ['nuxt', 'node']);

return initNode(sentryOptions);
const client = initNode(sentryOptions);

getGlobalScope().addEventProcessor(
Object.assign(
(event => {
if (event.type === 'transaction') {
// Filter out transactions for Nuxt build assets
// This regex matches the default path to the nuxt-generated build assets (`_nuxt`).
// todo: the buildAssetDir could be changed in the nuxt config - change this to a more generic solution
if (event.transaction?.match(/^GET \/_nuxt\//)) {
options.debug &&
// eslint-disable-next-line no-console
console.log('[Sentry] NuxtLowQualityTransactionsFilter filtered transaction: ', event.transaction);
return null;
}

return event;
} else {
return event;
}
}) satisfies EventProcessor,
{ id: 'NuxtLowQualityTransactionsFilter' },
),
);

return client;
}
42 changes: 42 additions & 0 deletions packages/nuxt/test/server/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as SentryNode from '@sentry/node';
import { SDK_VERSION } from '@sentry/node';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { init } from '../../src/server';

const nodeInit = vi.spyOn(SentryNode, 'init');

describe('Nuxt Server SDK', () => {
describe('init', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('Adds Nuxt metadata to the SDK options', () => {
expect(nodeInit).not.toHaveBeenCalled();

init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
});

const expectedMetadata = {
_metadata: {
sdk: {
name: 'sentry.javascript.nuxt',
version: SDK_VERSION,
packages: [
{ name: 'npm:@sentry/nuxt', version: SDK_VERSION },
{ name: 'npm:@sentry/node', version: SDK_VERSION },
],
},
},
};

expect(nodeInit).toHaveBeenCalledTimes(1);
expect(nodeInit).toHaveBeenLastCalledWith(expect.objectContaining(expectedMetadata));
});

it('returns client from init', () => {
expect(init({})).not.toBeUndefined();
});
});
});

0 comments on commit 29f070f

Please sign in to comment.