From 7173e3203dfc2b98a29c5aebbb8d0325d241bbac Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 30 Jul 2024 09:48:00 +0200 Subject: [PATCH 1/2] feat(nuxt): Add connected tracing meta tags --- packages/nuxt/src/module.ts | 8 ++- .../nuxt/src/runtime/plugins/sentry.server.ts | 8 ++- packages/nuxt/src/runtime/utils.ts | 27 ++++++++ .../nuxt/test/server/runtime/plugin.test.ts | 68 +++++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 packages/nuxt/test/server/runtime/plugin.test.ts diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 4786fe1e4aa9..6cfccfbd2714 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -60,7 +60,13 @@ function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined { const cwd = process.cwd(); const filePath = possibleFileExtensions - .map(e => path.resolve(path.join(cwd, `sentry.${type}.config.${e}`))) + .map(e => + path.resolve( + type === 'server' + ? path.join(cwd, 'public', `instrument.${type}.${e}`) + : path.join(cwd, `sentry.${type}.config.${e}`), + ), + ) .find(filename => fs.existsSync(filename)); return filePath ? path.basename(filePath) : undefined; diff --git a/packages/nuxt/src/runtime/plugins/sentry.server.ts b/packages/nuxt/src/runtime/plugins/sentry.server.ts index f0a815375ec8..476037ac980b 100644 --- a/packages/nuxt/src/runtime/plugins/sentry.server.ts +++ b/packages/nuxt/src/runtime/plugins/sentry.server.ts @@ -1,7 +1,8 @@ import { captureException } from '@sentry/node'; import { H3Error } from 'h3'; import { defineNitroPlugin } from 'nitropack/runtime'; -import { extractErrorContext } from '../utils'; +import type { NuxtRenderHTMLContext } from 'nuxt/app'; +import { addSentryTracingMetaTags, extractErrorContext } from '../utils'; export default defineNitroPlugin(nitroApp => { nitroApp.hooks.hook('error', (error, errorContext) => { @@ -20,4 +21,9 @@ export default defineNitroPlugin(nitroApp => { mechanism: { handled: false }, }); }); + + // @ts-expect-error - 'render:html' is a valid hook name in the Nuxt context + nitroApp.hooks.hook('render:html', (html: NuxtRenderHTMLContext) => { + addSentryTracingMetaTags(html.head); + }); }); diff --git a/packages/nuxt/src/runtime/utils.ts b/packages/nuxt/src/runtime/utils.ts index 07294806a546..5b449dce91ff 100644 --- a/packages/nuxt/src/runtime/utils.ts +++ b/packages/nuxt/src/runtime/utils.ts @@ -1,6 +1,10 @@ +import { getActiveSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; +import { getDynamicSamplingContextFromSpan } from '@sentry/opentelemetry'; import type { Context } from '@sentry/types'; import { dropUndefinedKeys } from '@sentry/utils'; +import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { CapturedErrorContext } from 'nitropack'; +import type { NuxtRenderHTMLContext } from 'nuxt/app'; /** * Extracts the relevant context information from the error context (H3Event in Nitro Error) @@ -26,3 +30,26 @@ export function extractErrorContext(errorContext: CapturedErrorContext): Context return dropUndefinedKeys(structuredContext); } + +/** + * Adds Sentry tracing tags to the returned html page. + * + * Exported only for testing + */ +export function addSentryTracingMetaTags(head: NuxtRenderHTMLContext['head']): void { + const activeSpan = getActiveSpan(); + const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; + + if (rootSpan) { + const traceParentData = spanToTraceHeader(rootSpan); + const dynamicSamplingContext = dynamicSamplingContextToSentryBaggageHeader( + getDynamicSamplingContextFromSpan(rootSpan), + ); + const contentMeta = [ + ``, + ``, + ]; + + head.push(...contentMeta); + } +} diff --git a/packages/nuxt/test/server/runtime/plugin.test.ts b/packages/nuxt/test/server/runtime/plugin.test.ts new file mode 100644 index 000000000000..518b20026cbd --- /dev/null +++ b/packages/nuxt/test/server/runtime/plugin.test.ts @@ -0,0 +1,68 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { addSentryTracingMetaTags } from '../../../src/runtime/utils'; + +const mockReturns = vi.hoisted(() => { + return { + traceHeader: 'trace-header', + baggageHeader: 'baggage-header', + }; +}); + +vi.mock('@sentry/core', async () => { + const actual = await vi.importActual('@sentry/core'); + + return { + ...actual, + getActiveSpan: vi.fn().mockReturnValue({ spanId: '123' }), + getRootSpan: vi.fn().mockReturnValue({ spanId: 'root123' }), + spanToTraceHeader: vi.fn(() => mockReturns.traceHeader), + }; +}); + +vi.mock('@sentry/opentelemetry', async () => { + const actual = await vi.importActual('@sentry/opentelemetry'); + + return { + ...actual, + getDynamicSamplingContextFromSpan: vi.fn().mockReturnValue('contextValue'), + }; +}); + +vi.mock('@sentry/utils', async () => { + const actual = await vi.importActual('@sentry/utils'); + + return { + ...actual, + dynamicSamplingContextToSentryBaggageHeader: vi.fn().mockReturnValue(mockReturns.baggageHeader), + }; +}); + +describe('addSentryTracingMetaTags', () => { + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should add meta tags when there is an active root span', () => { + const head: string[] = []; + addSentryTracingMetaTags(head); + + expect(head).toContain(``); + expect(head).toContain(``); + }); + + it('should not add meta tags when there is no active root span', () => { + vi.doMock('@sentry/core', async () => { + const actual = await vi.importActual('@sentry/core'); + + return { + ...actual, + getActiveSpan: vi.fn().mockReturnValue(undefined), + }; + }); + + const head: string[] = []; + addSentryTracingMetaTags(head); + + expect(head).toHaveLength(0); + }); +}); From c128d8472b2c2f788fb46d6f222dc0972c4cc89f Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 30 Jul 2024 11:59:39 +0200 Subject: [PATCH 2/2] not spreading array --- packages/nuxt/src/runtime/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/nuxt/src/runtime/utils.ts b/packages/nuxt/src/runtime/utils.ts index 5b449dce91ff..b7c038ddd505 100644 --- a/packages/nuxt/src/runtime/utils.ts +++ b/packages/nuxt/src/runtime/utils.ts @@ -45,11 +45,8 @@ export function addSentryTracingMetaTags(head: NuxtRenderHTMLContext['head']): v const dynamicSamplingContext = dynamicSamplingContextToSentryBaggageHeader( getDynamicSamplingContextFromSpan(rootSpan), ); - const contentMeta = [ - ``, - ``, - ]; - head.push(...contentMeta); + head.push(``); + head.push(``); } }