Skip to content

Commit dbdddc8

Browse files
authored
Merge pull request #17481 from getsentry/prepare-release/10.8.0
meta(changelog): Update changelog for 10.8.0
2 parents 27e97b0 + f5d4bd6 commit dbdddc8

File tree

96 files changed

+4878
-857
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+4878
-857
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,48 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
## 10.8.0
8+
9+
### Important Changes
10+
11+
- **feat(sveltekit): Add Compatibility for builtin SvelteKit Tracing ([#17423](https://github.com/getsentry/sentry-javascript/pull/17423))**
12+
13+
This release makes the `@sentry/sveltekit` SDK compatible with SvelteKit's native [observability support](https://svelte.dev/docs/kit/observability) introduced in SvelteKit version `2.31.0`.
14+
If you enable both, instrumentation and tracing, the SDK will now initialize early enough to set up additional instrumentation like database queries and it will pick up spans emitted from SvelteKit.
15+
16+
We will follow up with docs how to set up the SDK soon.
17+
For now, If you're on SvelteKit version `2.31.0` or newer, you can easily opt into the new feature:
18+
19+
1. Enable [experimental tracing and instrumentation support](https://svelte.dev/docs/kit/observability) in `svelte.config.js`:
20+
2. Move your `Sentry.init()` call from `src/hooks.server.(js|ts)` to the new `instrumentation.server.(js|ts)` file:
21+
22+
```ts
23+
// instrumentation.server.ts
24+
import * as Sentry from '@sentry/sveltekit';
25+
26+
Sentry.init({
27+
dsn: '...',
28+
// rest of your config
29+
});
30+
```
31+
32+
The rest of your Sentry config in `hooks.server.ts` (`sentryHandle` and `handleErrorWithSentry`) should stay the same.
33+
34+
If you prefer to stay on the hooks-file based config for now, the SDK will continue to work as previously.
35+
36+
Thanks to the Svelte team and @elliott-with-the-longest-name-on-github for implementing observability support and for reviewing our PR!
37+
38+
### Other Changes
39+
40+
- fix(react): Avoid multiple name updates on navigation spans ([#17438](https://github.com/getsentry/sentry-javascript/pull/17438))
41+
42+
<details>
43+
<summary> <strong>Internal Changes</strong> </summary>
44+
45+
- test(profiling): Add tests for current state of profiling ([#17470](https://github.com/getsentry/sentry-javascript/pull/17470))
46+
47+
</details>
48+
749
## 10.7.0
850

951
### Important Changes
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { browserProfilingIntegration } from '@sentry/browser';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [browserProfilingIntegration()],
9+
tracesSampleRate: 1,
10+
profilesSampleRate: 1,
11+
});
12+
13+
function fibonacci(n) {
14+
if (n <= 1) {
15+
return n;
16+
}
17+
return fibonacci(n - 1) + fibonacci(n - 2);
18+
}
19+
20+
await Sentry.startSpanManual({ name: 'root-fibonacci-2', parentSpan: null, forceTransaction: true }, async span => {
21+
fibonacci(30);
22+
23+
// Timeout to prevent flaky tests. Integration samples every 20ms, if function is too fast it might not get sampled
24+
await new Promise(resolve => setTimeout(resolve, 21));
25+
span.end();
26+
});
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event, Profile } from '@sentry/core';
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import {
5+
properEnvelopeRequestParser,
6+
shouldSkipTracingTest,
7+
waitForTransactionRequestOnUrl,
8+
} from '../../../utils/helpers';
9+
10+
sentryTest(
11+
'does not send profile envelope when document-policy is not set',
12+
async ({ page, getLocalTestUrl, browserName }) => {
13+
if (shouldSkipTracingTest() || browserName !== 'chromium') {
14+
// Profiling only works when tracing is enabled
15+
sentryTest.skip();
16+
}
17+
18+
const url = await getLocalTestUrl({ testDir: __dirname });
19+
20+
const req = await waitForTransactionRequestOnUrl(page, url);
21+
const transactionEvent = properEnvelopeRequestParser<Event>(req, 0);
22+
const profileEvent = properEnvelopeRequestParser<Profile>(req, 1);
23+
24+
expect(transactionEvent).toBeDefined();
25+
expect(profileEvent).toBeUndefined();
26+
},
27+
);
28+
29+
sentryTest('sends profile envelope in legacy mode', async ({ page, getLocalTestUrl, browserName }) => {
30+
if (shouldSkipTracingTest() || browserName !== 'chromium') {
31+
// Profiling only works when tracing is enabled
32+
sentryTest.skip();
33+
}
34+
35+
const url = await getLocalTestUrl({ testDir: __dirname, responseHeaders: { 'Document-Policy': 'js-profiling' } });
36+
37+
const req = await waitForTransactionRequestOnUrl(page, url);
38+
const profileEvent = properEnvelopeRequestParser<Profile>(req, 1);
39+
expect(profileEvent).toBeDefined();
40+
41+
const profile = profileEvent.profile;
42+
expect(profileEvent.profile).toBeDefined();
43+
44+
expect(profile.samples).toBeDefined();
45+
expect(profile.stacks).toBeDefined();
46+
expect(profile.frames).toBeDefined();
47+
expect(profile.thread_metadata).toBeDefined();
48+
49+
// Samples
50+
expect(profile.samples.length).toBeGreaterThanOrEqual(2);
51+
for (const sample of profile.samples) {
52+
expect(typeof sample.elapsed_since_start_ns).toBe('string');
53+
expect(sample.elapsed_since_start_ns).toMatch(/^\d+$/); // Numeric string
54+
expect(parseInt(sample.elapsed_since_start_ns, 10)).toBeGreaterThanOrEqual(0);
55+
56+
expect(typeof sample.stack_id).toBe('number');
57+
expect(sample.stack_id).toBeGreaterThanOrEqual(0);
58+
expect(sample.thread_id).toBe('0'); // Should be main thread
59+
}
60+
61+
// Stacks
62+
expect(profile.stacks.length).toBeGreaterThan(0);
63+
for (const stack of profile.stacks) {
64+
expect(Array.isArray(stack)).toBe(true);
65+
for (const frameIndex of stack) {
66+
expect(typeof frameIndex).toBe('number');
67+
expect(frameIndex).toBeGreaterThanOrEqual(0);
68+
expect(frameIndex).toBeLessThan(profile.frames.length);
69+
}
70+
}
71+
72+
// Frames
73+
expect(profile.frames.length).toBeGreaterThan(0);
74+
for (const frame of profile.frames) {
75+
expect(frame).toHaveProperty('function');
76+
expect(frame).toHaveProperty('abs_path');
77+
expect(frame).toHaveProperty('lineno');
78+
expect(frame).toHaveProperty('colno');
79+
80+
expect(typeof frame.function).toBe('string');
81+
expect(typeof frame.abs_path).toBe('string');
82+
expect(typeof frame.lineno).toBe('number');
83+
expect(typeof frame.colno).toBe('number');
84+
}
85+
86+
const functionNames = profile.frames.map(frame => frame.function).filter(name => name !== '');
87+
88+
if ((process.env.PW_BUNDLE || '').endsWith('min')) {
89+
// Function names are minified in minified bundles
90+
expect(functionNames.length).toBeGreaterThan(0);
91+
expect((functionNames as string[]).every(name => name?.length > 0)).toBe(true); // Just make sure they're not empty strings
92+
} else {
93+
expect(functionNames).toEqual(
94+
expect.arrayContaining([
95+
'_startRootSpan',
96+
'withScope',
97+
'createChildOrRootSpan',
98+
'startSpanManual',
99+
'startProfileForSpan',
100+
'startJSSelfProfile',
101+
]),
102+
);
103+
}
104+
105+
expect(profile.thread_metadata).toHaveProperty('0');
106+
expect(profile.thread_metadata['0']).toHaveProperty('name');
107+
expect(profile.thread_metadata['0'].name).toBe('main');
108+
109+
// Test that profile duration makes sense (should be > 20ms based on test setup)
110+
const startTime = parseInt(profile.samples[0].elapsed_since_start_ns, 10);
111+
const endTime = parseInt(profile.samples[profile.samples.length - 1].elapsed_since_start_ns, 10);
112+
const durationNs = endTime - startTime;
113+
const durationMs = durationNs / 1_000_000; // Convert ns to ms
114+
115+
// Should be at least 20ms based on our setTimeout(21) in the test
116+
expect(durationMs).toBeGreaterThan(20);
117+
});

dev-packages/browser-integration-tests/utils/fixtures.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type TestFixtures = {
3535
skipRouteHandler?: boolean;
3636
skipDsnRouteHandler?: boolean;
3737
handleLazyLoadedFeedback?: boolean;
38+
responseHeaders?: Record<string, string>;
3839
}) => Promise<string>;
3940
forceFlushReplay: () => Promise<string>;
4041
enableConsole: () => void;
@@ -59,7 +60,13 @@ const sentryTest = base.extend<TestFixtures>({
5960

6061
getLocalTestUrl: ({ page }, use) => {
6162
return use(
62-
async ({ testDir, skipRouteHandler = false, skipDsnRouteHandler = false, handleLazyLoadedFeedback = false }) => {
63+
async ({
64+
testDir,
65+
skipRouteHandler = false,
66+
skipDsnRouteHandler = false,
67+
handleLazyLoadedFeedback = false,
68+
responseHeaders = {},
69+
}) => {
6370
const pagePath = `${TEST_HOST}/index.html`;
6471

6572
const tmpDir = path.join(testDir, 'dist', crypto.randomUUID());
@@ -86,7 +93,9 @@ const sentryTest = base.extend<TestFixtures>({
8693
const file = route.request().url().split('/').pop();
8794
const filePath = path.resolve(tmpDir, `./${file}`);
8895

89-
return fs.existsSync(filePath) ? route.fulfill({ path: filePath }) : route.continue();
96+
return fs.existsSync(filePath)
97+
? route.fulfill({ path: filePath, headers: responseHeaders })
98+
: route.continue();
9099
});
91100

92101
if (handleLazyLoadedFeedback) {

dev-packages/browser-integration-tests/utils/generatePlugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record<string, string> = {
3636
feedbackIntegration: 'feedback',
3737
moduleMetadataIntegration: 'modulemetadata',
3838
graphqlClientIntegration: 'graphqlclient',
39+
browserProfilingIntegration: 'browserprofiling',
3940
// technically, this is not an integration, but let's add it anyway for simplicity
4041
makeMultiplexedTransport: 'multiplexedtransport',
4142
};

dev-packages/browser-integration-tests/utils/helpers.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const envelopeParser = (request: Request | null): unknown[] => {
2929
});
3030
};
3131

32+
// Rather use the `properEnvelopeRequestParser`, as the `envelopeParser` does not follow the envelope spec.
3233
export const envelopeRequestParser = <T = SentryEvent>(request: Request | null, envelopeIndex = 2): T => {
3334
return envelopeParser(request)[envelopeIndex] as T;
3435
};
@@ -79,8 +80,12 @@ function getEventAndTraceHeader(envelope: EventEnvelope): EventAndTraceHeader {
7980
return [event, trace];
8081
}
8182

82-
export const properEnvelopeRequestParser = <T = SentryEvent>(request: Request | null, envelopeIndex = 1): T => {
83-
return properEnvelopeParser(request)[0]?.[envelopeIndex] as T;
83+
export const properEnvelopeRequestParser = <T = SentryEvent>(
84+
request: Request | null,
85+
envelopeItemIndex: number,
86+
envelopeIndex = 1, // 1 is usually the payload of the envelope (0 is the header)
87+
): T => {
88+
return properEnvelopeParser(request)[envelopeItemIndex]?.[envelopeIndex] as T;
8489
};
8590

8691
export const properFullEnvelopeRequestParser = <T extends Envelope>(request: Request | null): T => {

dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ const router = sentryCreateBrowserRouter(
5050
lazyChildren: () => import('./pages/InnerLazyRoutes').then(module => module.someMoreNestedRoutes),
5151
},
5252
},
53+
{
54+
path: '/another-lazy',
55+
handle: {
56+
lazyChildren: () => import('./pages/AnotherLazyRoutes').then(module => module.anotherNestedRoutes),
57+
},
58+
},
5359
{
5460
path: '/static',
5561
element: <>Hello World</>,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { Link } from 'react-router-dom';
3+
4+
export const anotherNestedRoutes = [
5+
{
6+
path: 'sub',
7+
children: [
8+
{
9+
index: true,
10+
element: (
11+
<div id="another-lazy-route">
12+
Another Lazy Route
13+
<Link to="/lazy/inner/999/888/777" id="navigate-to-inner">
14+
Navigate to Inner Lazy Route
15+
</Link>
16+
</div>
17+
),
18+
},
19+
{
20+
path: ':id',
21+
children: [
22+
{
23+
index: true,
24+
element: <div id="another-lazy-route-with-id">Another Lazy Route with ID</div>,
25+
},
26+
{
27+
path: ':subId',
28+
element: (
29+
<div id="another-lazy-route-deep">
30+
Another Deep Lazy Route
31+
<Link to="/lazy/inner/111/222/333" id="navigate-to-inner-from-deep">
32+
Navigate to Inner from Deep
33+
</Link>
34+
</div>
35+
),
36+
},
37+
],
38+
},
39+
],
40+
},
41+
];

dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/Index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ const Index = () => {
77
<Link to="/lazy/inner/123/456/789" id="navigation">
88
navigate
99
</Link>
10+
<br />
11+
<Link to="/another-lazy/sub" id="navigation-to-another">
12+
Navigate to Another Lazy Route
13+
</Link>
14+
<br />
15+
<Link to="/another-lazy/sub/555/666" id="navigation-to-another-deep">
16+
Navigate to Another Deep Lazy Route
17+
</Link>
1018
</>
1119
);
1220
};

dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/InnerLazyRoutes.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { Link } from 'react-router-dom';
23

34
export const someMoreNestedRoutes = [
45
{
@@ -24,9 +25,15 @@ export const someMoreNestedRoutes = [
2425
},
2526
{
2627
path: ':someAnotherId',
27-
element: <div id="innermost-lazy-route">
28-
Rendered
29-
</div>,
28+
element: (
29+
<div id="innermost-lazy-route">
30+
Rendered
31+
<br />
32+
<Link to="/another-lazy/sub/888/999" id="navigate-to-another-from-inner">
33+
Navigate to Another Lazy Route
34+
</Link>
35+
</div>
36+
),
3037
},
3138
],
3239
},

0 commit comments

Comments
 (0)