Skip to content

Commit

Permalink
feat(nuxt): Add connected tracing meta tags (#13098)
Browse files Browse the repository at this point in the history
Also changes the `findDefaultSdkInitFile` function as the
backend-related file is located and named differently than the client
config file (`sentry.client.config.ts` and
`public/instrument.server.mjs`).

closes #13097
  • Loading branch information
s1gr1d authored Jul 30, 2024
1 parent 8f037ac commit 7dc6a25
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 2 deletions.
8 changes: 7 additions & 1 deletion packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion packages/nuxt/src/runtime/plugins/sentry.server.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -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);
});
});
24 changes: 24 additions & 0 deletions packages/nuxt/src/runtime/utils.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -26,3 +30,23 @@ export function extractErrorContext(errorContext: CapturedErrorContext): Context

return dropUndefinedKeys(structuredContext);
}

/**
* Adds Sentry tracing <meta> 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),
);

head.push(`<meta name="sentry-trace" content="${traceParentData}"/>`);
head.push(`<meta name="baggage" content="${dynamicSamplingContext}"/>`);
}
}
68 changes: 68 additions & 0 deletions packages/nuxt/test/server/runtime/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -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(`<meta name="sentry-trace" content="${mockReturns.traceHeader}"/>`);
expect(head).toContain(`<meta name="baggage" content="${mockReturns.baggageHeader}"/>`);
});

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

0 comments on commit 7dc6a25

Please sign in to comment.