Skip to content

Commit 5929a1b

Browse files
authored
fix(sveltekit): Ensure trace meta tags are always injected (#13231)
Removes the incorrect root span check guarding the trace `<meta>` tag injection. With this fix, trace data is now also injected for Tracing Without Performance scenarios where there is no active span. closes #13106
1 parent 8b1b64c commit 5929a1b

File tree

25 files changed

+362
-31
lines changed

25 files changed

+362
-31
lines changed

.github/workflows/build.yml

+1
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ jobs:
915915
'sveltekit',
916916
'sveltekit-2',
917917
'sveltekit-2-svelte-5',
918+
'sveltekit-2-twp',
918919
'tanstack-router',
919920
'generic-ts3.8',
920921
'node-fastify',

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,38 @@ const META_TAG_PARENT_SPAN_ID = '1234567890123456';
1212
const META_TAG_BAGGAGE =
1313
'sentry-trace_id=12345678901234567890123456789012,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod';
1414

15+
sentryTest('error on initial page has traceId from meta tag', async ({ getLocalTestUrl, page }) => {
16+
if (shouldSkipTracingTest()) {
17+
sentryTest.skip();
18+
}
19+
20+
const url = await getLocalTestUrl({ testDir: __dirname });
21+
await page.goto(url);
22+
23+
const errorEventPromise = getFirstSentryEnvelopeRequest<EventAndTraceHeader>(
24+
page,
25+
undefined,
26+
eventAndTraceHeaderRequestParser,
27+
);
28+
29+
await page.locator('#errorBtn').click();
30+
const [errorEvent, errorTraceHeader] = await errorEventPromise;
31+
32+
expect(errorEvent.type).toEqual(undefined);
33+
expect(errorEvent.contexts?.trace).toEqual({
34+
trace_id: META_TAG_TRACE_ID,
35+
parent_span_id: META_TAG_PARENT_SPAN_ID,
36+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
37+
});
38+
39+
expect(errorTraceHeader).toEqual({
40+
environment: 'prod',
41+
public_key: 'public',
42+
release: '1.0.0',
43+
trace_id: META_TAG_TRACE_ID,
44+
});
45+
});
46+
1547
sentryTest('error has new traceId after navigation', async ({ getLocalTestUrl, page }) => {
1648
if (shouldSkipTracingTest()) {
1749
sentryTest.skip();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_Store
2+
node_modules
3+
/build
4+
/.svelte-kit
5+
/package
6+
.env
7+
.env.*
8+
!.env.example
9+
vite.config.js.timestamp-*
10+
vite.config.ts.timestamp-*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Tracing Without Performance E2E test app
2+
3+
E2E test app for testing Tracing Without Performance in a (SvelteKit) meta framework scenario
4+
5+
Add tests to this app that specifically test TwP in meta frameworks.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "sveltekit-2-svelte-5",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"dev": "vite dev",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"proxy": "node start-event-proxy.mjs",
10+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
11+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
13+
"test:prod": "TEST_ENV=production playwright test",
14+
"test:build": "pnpm install && npx playwright install && pnpm build",
15+
"test:assert": "pnpm test:prod"
16+
},
17+
"dependencies": {
18+
"@sentry/sveltekit": "latest || *"
19+
},
20+
"devDependencies": {
21+
"@playwright/test": "^1.44.1",
22+
"@sentry-internal/test-utils": "link:../../../test-utils",
23+
"@sentry/types": "latest || *",
24+
"@sentry/utils": "latest || *",
25+
"@sveltejs/adapter-auto": "^3.0.0",
26+
"@sveltejs/kit": "^2.0.0",
27+
"@sveltejs/vite-plugin-svelte": "^3.0.0",
28+
"svelte": "^5.0.0-next.115",
29+
"svelte-check": "^3.6.0",
30+
"tslib": "^2.4.1",
31+
"typescript": "^5.0.0",
32+
"vite": "^5.0.3"
33+
},
34+
"type": "module"
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
3+
const config = getPlaywrightConfig({
4+
startCommand: 'pnpm preview --port 3030',
5+
port: 3030,
6+
});
7+
8+
export default config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// See https://kit.svelte.dev/docs/types#app
2+
// for information about these interfaces
3+
declare global {
4+
namespace App {
5+
// interface Error {}
6+
// interface Locals {}
7+
// interface PageData {}
8+
// interface PageState {}
9+
// interface Platform {}
10+
}
11+
}
12+
13+
export {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
%sveltekit.head%
8+
</head>
9+
<body data-sveltekit-preload-data="hover">
10+
<div style="display: contents">%sveltekit.body%</div>
11+
</body>
12+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { env } from '$env/dynamic/public';
2+
import * as Sentry from '@sentry/sveltekit';
3+
4+
Sentry.init({
5+
environment: 'qa', // dynamic sampling bias to keep transactions
6+
dsn: env.PUBLIC_E2E_TEST_DSN,
7+
release: '1.0.0',
8+
tunnel: `http://localhost:3031/`, // proxy server
9+
});
10+
11+
const myErrorHandler = ({ error, event }: any) => {
12+
console.error('An error occurred on the client side:', error, event);
13+
};
14+
15+
export const handleError = Sentry.handleErrorWithSentry(myErrorHandler);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { E2E_TEST_DSN } from '$env/static/private';
2+
import * as Sentry from '@sentry/sveltekit';
3+
4+
Sentry.init({
5+
environment: 'qa', // dynamic sampling bias to keep transactions
6+
dsn: E2E_TEST_DSN,
7+
tunnel: `http://localhost:3031/`, // proxy server
8+
});
9+
10+
// not logging anything to console to avoid noise in the test output
11+
export const handleError = Sentry.handleErrorWithSentry(() => {});
12+
13+
export const handle = Sentry.sentryHandle();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<h1>Sveltekit E2E Test app</h1>
2+
<div data-sveltekit-preload-data="off">
3+
<slot></slot>
4+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<h1>Welcome to SvelteKit 2 with Svelte 5!</h1>
2+
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
3+
4+
<ul>
5+
<li>
6+
<a href="/errors">error route</a>
7+
</li>
8+
</ul>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from '@sentry/sveltekit';
2+
3+
export const load = async ({ url }) => {
4+
if (!url.search) {
5+
Sentry.captureException(new Error('No search query provided'));
6+
return {
7+
error: 'No search query provided',
8+
};
9+
}
10+
return {
11+
message: 'hi',
12+
};
13+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
4+
export let data;
5+
6+
onMount(() => {
7+
if (data.error) {
8+
throw new Error('Client Error');
9+
}
10+
});
11+
</script>
12+
13+
<p>
14+
Data:
15+
{data.message || 'nothing'}
16+
</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { startEventProxyServer } from '@sentry-internal/test-utils';
2+
3+
startEventProxyServer({
4+
port: 3031,
5+
proxyServerName: 'sveltekit-2-twp',
6+
});
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import adapter from '@sveltejs/adapter-auto';
2+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3+
4+
/** @type {import('@sveltejs/kit').Config} */
5+
const config = {
6+
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
7+
// for more information about preprocessors
8+
preprocess: vitePreprocess(),
9+
10+
kit: {
11+
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12+
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
13+
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
14+
adapter: adapter(),
15+
},
16+
};
17+
18+
export default config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForError } from '@sentry-internal/test-utils';
3+
4+
test('errors on frontend and backend are connected by the same trace', async ({ page }) => {
5+
const clientErrorPromise = waitForError('sveltekit-2-twp', evt => {
6+
return evt.exception?.values?.[0].value === 'Client Error';
7+
});
8+
9+
const serverErrorPromise = waitForError('sveltekit-2-twp', evt => {
10+
return evt.exception?.values?.[0].value === 'No search query provided';
11+
});
12+
13+
await page.goto('/errors');
14+
15+
const clientError = await clientErrorPromise;
16+
const serverError = await serverErrorPromise;
17+
18+
expect(clientError).toMatchObject({
19+
contexts: {
20+
trace: {
21+
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
22+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
23+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
24+
},
25+
},
26+
environment: 'qa',
27+
exception: {
28+
values: [
29+
{
30+
mechanism: {
31+
handled: false,
32+
type: 'onunhandledrejection',
33+
},
34+
stacktrace: expect.any(Object),
35+
type: 'Error',
36+
value: 'Client Error',
37+
},
38+
],
39+
},
40+
level: 'error',
41+
platform: 'javascript',
42+
release: '1.0.0',
43+
timestamp: expect.any(Number),
44+
transaction: '/errors',
45+
});
46+
47+
expect(serverError).toMatchObject({
48+
contexts: {
49+
trace: {
50+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
51+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
52+
},
53+
},
54+
environment: 'qa',
55+
exception: {
56+
values: [
57+
{
58+
mechanism: {
59+
handled: true,
60+
type: 'generic',
61+
},
62+
stacktrace: {},
63+
},
64+
],
65+
},
66+
platform: 'node',
67+
timestamp: expect.any(Number),
68+
transaction: 'GET /errors',
69+
});
70+
71+
const clientTraceId = clientError.contexts?.trace?.trace_id;
72+
const serverTraceId = serverError.contexts?.trace?.trace_id;
73+
74+
expect(clientTraceId).toBe(serverTraceId);
75+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
test('Initially loaded page contains trace meta tags from backend trace', async ({ page }) => {
4+
await page.goto('/');
5+
6+
const sentryTraceMetaTag = page.locator('meta[name="sentry-trace"]').first();
7+
const sentryTraceContent = await sentryTraceMetaTag.getAttribute('content');
8+
9+
const baggageMetaTag = page.locator('meta[name="baggage"]').first();
10+
const baggageContent = await baggageMetaTag.getAttribute('content');
11+
12+
// ensure that we do not pass a sampled -1 or -0 flag at the end:
13+
expect(sentryTraceContent).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}$/);
14+
15+
expect(baggageContent?.length).toBeGreaterThan(0);
16+
17+
const traceId = sentryTraceContent!.split('-')[0];
18+
19+
expect(baggageContent).toContain('sentry-environment=qa');
20+
expect(baggageContent).toContain(`sentry-trace_id=${traceId}`);
21+
// ensure baggage also doesn't contain a sampled flag
22+
expect(baggageContent).not.toContain('sentry-sampled=');
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"extends": "./.svelte-kit/tsconfig.json",
3+
"compilerOptions": {
4+
"allowJs": true,
5+
"checkJs": true,
6+
"esModuleInterop": true,
7+
"forceConsistentCasingInFileNames": true,
8+
"resolveJsonModule": true,
9+
"skipLibCheck": true,
10+
"sourceMap": true,
11+
"strict": true,
12+
"moduleResolution": "bundler"
13+
},
14+
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15+
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
16+
//
17+
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18+
// from the referenced tsconfig.json - TypeScript does not merge them in
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { sentrySvelteKit } from '@sentry/sveltekit';
2+
import { sveltekit } from '@sveltejs/kit/vite';
3+
import { defineConfig } from 'vite';
4+
5+
export default defineConfig({
6+
plugins: [
7+
sentrySvelteKit({
8+
autoUploadSourceMaps: false,
9+
}),
10+
sveltekit(),
11+
],
12+
});

0 commit comments

Comments
 (0)