Skip to content

Commit db704a6

Browse files
authored
Merge branch 'develop' into sig/cache-component-e2e-pageload
2 parents a082c46 + 48c8b87 commit db704a6

139 files changed

Lines changed: 5472 additions & 939 deletions

File tree

Some content is hidden

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

‎.agents/skills/add-ai-integration/SKILL.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ What the reference files won't tell you:
5858
- [ ] Instrumentation in `src/ai/`, integration in `src/integrations/`, registered in `getTracingIntegrations()` (LangChain first)
5959
- [ ] Exported from `packages/server-utils/src/index.ts`, re-exported from the supported runtime packages
6060
- [ ] E2E tests in `dev-packages/node-integration-tests/suites/tracing/{provider}/` (and `cloudflare-integration-tests/` if supported)
61+
- [ ] The new suite passes on the other runtimes that run the Node suites, or is excluded there (see "Other Runtimes" in `dev-packages/node-integration-tests/README.md`)
6162
- [ ] Ops and attributes from `@sentry/conventions`, op derived via `getGenAiSpanOp()`
6263
- [ ] Recording gated on `resolveAIRecordingOptions()`; no truncation, no token rollup
6364
- [ ] JSDoc names the channels subscribed to, the supported SDK versions, and — for Pattern 2 — that it requires the Sentry runtime hook or bundler plugin

‎.agents/skills/vendor-otel/SKILL.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ grep "import.*from '" <file> | grep -v "@opentelemetry\|'\./\|@sentry\|'util'\|'
4343

4444
Check test coverage and report gaps:
4545

46-
- Integration tests: `dev-packages/node-integration-tests/suites/tracing/<name>/`
46+
- Integration tests: `dev-packages/node-integration-tests/suites/tracing/<name>/`. Other runtimes run these
47+
suites too; see "Other Runtimes" in `dev-packages/node-integration-tests/README.md`.
4748
- E2E tests: `dev-packages/e2e-tests/test-applications/node-<name>/`
4849
- Unit tests: `packages/node/test/integrations/tracing/<name>.test.ts`
4950

‎.agents/skills/write-tests/SKILL.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ describe('patchRoute', () => {
337337
Node integration tests (`dev-packages/node-integration-tests/`) use `createEsmAndCjsTests` to
338338
run a real Node scenario file and assert on captured Sentry envelopes.
339339

340+
Other runtimes run these suites too. A new suite must pass there or be excluded; see "Other
341+
Runtimes" in `dev-packages/node-integration-tests/README.md`.
342+
340343
### Minimize `test()` calls — each one spawns a separate Node process
341344

342345
**This is the opposite of the Playwright rule.** In Playwright, each `test()` is cheap — use

‎.github/CODEOWNERS‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88
/dev-packages/browser-integration-tests/ @getsentry/team-javascript-sdks-browser
99

1010
# Node/server runtimes and related packages
11-
# TEMP: whole JS SDK team reviews orchestrion work; revert to team-javascript-sdks-server after
12-
/packages/node/ @getsentry/team-javascript-sdks
13-
/packages/server-runtime-injection/ @getsentry/team-javascript-sdks
14-
/packages/server-utils/ @getsentry/team-javascript-sdks
11+
/packages/node/ @getsentry/team-javascript-sdks-server
12+
/packages/server-runtime-injection/ @getsentry/team-javascript-sdks-server
13+
/packages/server-utils/ @getsentry/team-javascript-sdks-server
1514
/packages/node-native/ @getsentry/team-javascript-sdks-server
1615
/packages/profiling-node/ @getsentry/team-javascript-sdks-server
1716
/packages/opentelemetry/ @getsentry/team-javascript-sdks-server

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

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

7-
Work in this release was contributed by @camc314 and @ihsraham. Thank you for your contributions!
7+
Work in this release was contributed by @camc314, @ihsraham, @zkasuran, and @Shubham-Padkonde. Thank you for your contributions!
88

99
## 11.0.0
1010

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
import * as Sentry from '@sentry/browser';
2-
import { registerWebWorkerWasm } from '@sentry/wasm';
2+
import { wasmIntegration } from '@sentry/wasm';
33

44
window.Sentry = Sentry;
55

66
Sentry.init({
77
traceLifecycle: 'static',
88
dsn: 'https://public@dsn.ingest.sentry.io/1337',
9-
});
10-
11-
// `registerWebWorkerWasm` installs the same patches a worker would, and reports
12-
// every registered module to the scope it is given. Collecting them here is the
13-
// only way to observe registration from the page, since main-thread images stay
14-
// module-internal until a frame matches one.
15-
window.registeredImages = [];
16-
registerWebWorkerWasm({
17-
self: {
18-
postMessage: message => window.registeredImages.push(...(message._sentryWasmImages || [])),
9+
integrations: [wasmIntegration()],
10+
beforeSend: event => {
11+
window.events.push(event);
12+
return null;
1913
},
2014
});
15+
window.events = [];
Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
window.loadWasmFromBuffer = async () => {
2-
const response = await fetch('https://localhost:5887/simple.wasm');
3-
const buffer = await response.arrayBuffer();
1+
window.getEvent = async () => {
2+
function crash() {
3+
throw new Error('whoops');
4+
}
45

5-
await WebAssembly.instantiate(new Uint8Array(buffer), {
6+
const response = await fetch('https://localhost:5887/named.wasm');
7+
const buffer = await response.arrayBuffer();
8+
const { instance } = await WebAssembly.instantiate(new Uint8Array(buffer), {
69
env: {
7-
external_func: () => {},
10+
external_func: crash,
811
},
912
});
1013

11-
return window.registeredImages;
14+
try {
15+
instance.exports.internal_func();
16+
} catch (err) {
17+
Sentry.captureException(err);
18+
return window.events.pop();
19+
}
1220
};
Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,68 @@
1-
import type { Page, Route } from '@playwright/test';
21
import { expect } from '@playwright/test';
32
import fs from 'fs';
43
import path from 'path';
54
import { sentryTest } from '../../../utils/fixtures';
65
import { shouldSkipWASMTests } from '../../../utils/wasmHelpers';
76

8-
async function serveWasmFixture(page: Page): Promise<void> {
9-
// `page.route` resolves with a `Disposable` as of Playwright 1.63, so it can't be returned directly.
10-
await page.route('**/simple.wasm', (route: Route) => {
11-
const wasmModule = fs.readFileSync(path.resolve(__dirname, '..', 'simple.wasm'));
12-
13-
return route.fulfill({
14-
status: 200,
15-
body: wasmModule,
16-
headers: {
17-
'Content-Type': 'application/wasm',
18-
},
19-
});
20-
});
21-
}
22-
7+
// `named.wasm` is `../simple.wasm` plus a module-name subsection (`namedmodule`)
8+
// in its `name` section. Chrome labels bytes-compiled modules with that name
9+
// as `wasm://wasm/namedmodule-<hash>`, not with the fetch URL.
2310
sentryTest(
24-
'registers a module loaded via fetch, arrayBuffer and instantiate under its response url',
11+
'maps frames of a module compiled from fetched bytes to its debug image',
2512
async ({ getLocalTestUrl, page, browserName }) => {
2613
if (shouldSkipWASMTests(browserName)) {
2714
sentryTest.skip();
2815
}
2916

3017
const url = await getLocalTestUrl({ testDir: __dirname });
31-
await serveWasmFixture(page);
18+
19+
await page.route('**/named.wasm', route => {
20+
const wasmModule = fs.readFileSync(path.resolve(__dirname, '..', 'named.wasm'));
21+
22+
return route.fulfill({
23+
status: 200,
24+
body: wasmModule,
25+
headers: {
26+
'Content-Type': 'application/wasm',
27+
},
28+
});
29+
});
30+
3231
await page.goto(url);
3332

34-
const images = await page.evaluate(async () => {
33+
const event = await page.evaluate(async () => {
3534
// @ts-expect-error this function exists
36-
return window.loadWasmFromBuffer();
35+
return window.getEvent();
3736
});
3837

39-
expect(images).toEqual([
40-
{
41-
type: 'wasm',
42-
code_file: 'https://localhost:5887/simple.wasm',
43-
code_id: '0ba020cdd2444f7eafdd25999a8e9010',
44-
debug_file: null,
45-
debug_id: '0ba020cdd2444f7eafdd25999a8e90100',
46-
},
47-
]);
38+
expect(event.exception.values[0].stacktrace.frames).toEqual(
39+
expect.arrayContaining([
40+
expect.objectContaining({
41+
filename: 'https://localhost:5887/named.wasm',
42+
function: 'namedmodule.internal_func',
43+
in_app: true,
44+
instruction_addr: '0x8c',
45+
addr_mode: 'rel:0',
46+
platform: 'native',
47+
}),
48+
expect.objectContaining({
49+
filename: expect.stringMatching(/subject\.bundle\.js$/),
50+
function: 'crash',
51+
in_app: true,
52+
}),
53+
]),
54+
);
55+
56+
expect(event.debug_meta).toMatchObject({
57+
images: [
58+
{
59+
code_file: 'https://localhost:5887/named.wasm',
60+
code_id: '0ba020cdd2444f7eafdd25999a8e9010',
61+
debug_file: null,
62+
debug_id: '0ba020cdd2444f7eafdd25999a8e90100',
63+
type: 'wasm',
64+
},
65+
],
66+
});
4867
},
4968
);
4.09 KB
Binary file not shown.

‎dev-packages/browser-integration-tests/suites/wasm/webWorker/assets/worker.js‎

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@
33
// between worker and main thread independent of SDK implementation details
44
// in production code you would do: registerWebWorkerWasm({ self });
55

6+
// Bytes-compiled modules carry no URL, so remember the fetch URL per buffer
7+
// like the SDK's Response patch does.
8+
const bufferUrls = new WeakMap();
9+
const origArrayBuffer = Response.prototype.arrayBuffer;
10+
Response.prototype.arrayBuffer = function arrayBuffer() {
11+
return origArrayBuffer.call(this).then(buffer => {
12+
if (this.url) {
13+
bufferUrls.set(buffer, this.url);
14+
}
15+
return buffer;
16+
});
17+
};
18+
19+
const origInstantiate = WebAssembly.instantiate;
20+
WebAssembly.instantiate = function instantiate(source, importObject) {
21+
return origInstantiate(source, importObject).then(result => {
22+
const url = bufferUrls.get(source instanceof ArrayBuffer ? source : source.buffer);
23+
if (url && result.module) {
24+
registerModuleAndForward(result.module, url);
25+
}
26+
return result;
27+
});
28+
};
29+
630
const origInstantiateStreaming = WebAssembly.instantiateStreaming;
731
WebAssembly.instantiateStreaming = function instantiateStreaming(response, importObject) {
832
return Promise.resolve(response).then(res => {
@@ -26,6 +50,10 @@ function registerModuleAndForward(module, url) {
2650
debug_file: null,
2751
debug_id: `${`${buildId}00000000000000000000000000000000`.slice(0, 32)}0`,
2852
};
53+
const moduleName = getModuleName(module);
54+
if (moduleName) {
55+
image.moduleName = moduleName;
56+
}
2957

3058
self.postMessage({
3159
_sentryMessage: true,
@@ -46,6 +74,24 @@ function getBuildId(module) {
4674
return null;
4775
}
4876

77+
// Read the module name from the `name` custom section. Chrome labels
78+
// bytes-compiled modules `wasm://wasm/<name>-<hash>`, and the main thread
79+
// links such frames to this image by that name.
80+
function getModuleName(module) {
81+
const sections = WebAssembly.Module.customSections(module, 'name');
82+
if (sections.length === 0) {
83+
return null;
84+
}
85+
const bytes = new Uint8Array(sections[0]);
86+
// Subsection id 0 is the module name: id, payload size, name length, name.
87+
// Sizes are LEB128, but the fixture's fit in one byte each.
88+
if (bytes[0] !== 0) {
89+
return null;
90+
}
91+
const length = bytes[2];
92+
return new TextDecoder().decode(bytes.subarray(3, 3 + length));
93+
}
94+
4995
// Handle messages from the main thread
5096
self.addEventListener('message', async event => {
5197
if (event.origin !== '' && event.origin !== self.location.origin) {
@@ -56,15 +102,23 @@ self.addEventListener('message', async event => {
56102
throw new Error('WASM error from worker');
57103
}
58104

59-
if (event.data.type === 'load-wasm-and-crash') {
105+
if (event.data.type === 'load-wasm-and-crash' || event.data.type === 'load-wasm-bytes-and-crash') {
60106
const wasmUrl = event.data.wasmUrl;
107+
const imports = {
108+
env: {
109+
external_func: crash,
110+
},
111+
};
61112

62113
try {
63-
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), {
64-
env: {
65-
external_func: crash,
66-
},
67-
});
114+
let instance;
115+
if (event.data.type === 'load-wasm-bytes-and-crash') {
116+
const response = await fetch(wasmUrl);
117+
const buffer = await response.arrayBuffer();
118+
({ instance } = await WebAssembly.instantiate(new Uint8Array(buffer), imports));
119+
} else {
120+
({ instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), imports));
121+
}
68122

69123
instance.exports.internal_func();
70124
} catch (err) {

0 commit comments

Comments
 (0)