Skip to content

meta(changelog): Update changelog for 7.46.0 #7666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9309b3b
Merge pull request #7605 from getsentry/master
github-actions[bot] Mar 24, 2023
b830f87
ci: Fix CodeQL by skipping tsconfig.json (#7607)
mydea Mar 24, 2023
dce26c9
build(tracing-internal): Remove circular dependency (#7608)
mydea Mar 24, 2023
11704a3
feat(sveltekit): Add meta tag for backend -> frontend (#7574)
AbhiPrasad Mar 24, 2023
68c2301
build: Remove `test/tsconfig.json` files (#7606)
mydea Mar 24, 2023
a2103f3
feat(replay): Capture replay mutation breadcrumbs & add experiment (#…
mydea Mar 24, 2023
5abe629
ref(sveltekit): Distinguish spans of server-only and universal load f…
Lms24 Mar 24, 2023
e1af1e6
feat(node): Auto discovery only returns integrations where dependency…
timfish Mar 24, 2023
a2bd1e0
test: Try to fix flaky tests? (#7597)
mydea Mar 27, 2023
b589685
fix(nextjs): Rewrite `abs_path` frames (#7619)
Mar 27, 2023
02d065d
build(cdn): Make integration CDN bundles build in parallel (#7621)
mydea Mar 27, 2023
2e5d850
chore(browser): Add attribution and licences for stack parsers (#7620)
timfish Mar 27, 2023
f6ce5c9
fix(sveltekit): Fix `<meta>` tag injection (#7612)
Lms24 Mar 27, 2023
0fab403
fix(core): Remove `abs_path` from stack trace (reverting #7167) (#7623)
Mar 27, 2023
0ed4f87
build(cdn): Move tracing CDN bundle generation to `@sentry/browser` (…
mydea Mar 27, 2023
6162fb8
chore(browser): Add attribution and licences for stack parsers (#7620)
timfish Mar 27, 2023
46f996e
feat(node): Undici integration (#7582)
AbhiPrasad Mar 27, 2023
ee59584
chore(node): Remove src/declarations.d.ts (#7627)
AbhiPrasad Mar 27, 2023
7d080dc
feat(replay): Add `responseStatus`, `decodedBodySize` to perf entries…
billyvg Mar 27, 2023
d330c2f
test(utils): Add test for global rate limit response with scope (#7631)
Lms24 Mar 28, 2023
3ba8265
feat(core): Add `ignoreTransactions` option (#7594)
mydea Mar 28, 2023
849297a
feat(tracing): Ensure `pageload` transaction starts at timeOrigin (#7…
mydea Mar 28, 2023
0743e98
chore(vue): Add attribution license (#7633)
AbhiPrasad Mar 28, 2023
30f2c24
feat(replay): Add experiment to capture request/response bodies (#7589)
mydea Mar 29, 2023
2974ff1
fix(nextjs): Use Next.js internal AsyncStorage (#7630)
Mar 29, 2023
f033ede
chore(utils): Add license to vendor attribution (#7635)
AbhiPrasad Mar 29, 2023
32675e8
test(node): Add tests for Undici (#7628)
AbhiPrasad Mar 29, 2023
b068c68
chore(remix): Add license text to vendored files (#7629)
AbhiPrasad Mar 29, 2023
773f180
fix(sveltekit): Handle nested server calls in `sentryHandle` (#7598)
Lms24 Mar 29, 2023
e7a0b1c
fix(nextjs): Add loading component type to server component wrapping …
Mar 29, 2023
9619432
tests(e2e): Undo intentional error (#7643)
Mar 29, 2023
ccd5f27
meta: Add CODEOWNERS (for replay only) (#7636)
billyvg Mar 29, 2023
0643828
fix(nextjs): Don't report `NEXT_NOT_FOUND` and `NEXT_REDIRECT` errors…
Mar 29, 2023
8a25d00
fix(node): Convert debugging code to callbacks to fix memory leak in …
timfish Mar 29, 2023
3a91a62
fix(nextjs): Show errors and warnings only once during build (#7651)
Mar 29, 2023
1b04113
ref(node): Clean up Undici options (#7646)
AbhiPrasad Mar 29, 2023
9e98330
feat(nextjs): Add Undici integration automatically (#7648)
AbhiPrasad Mar 29, 2023
120ae6d
feat(sveltekit): Add Undici integration by default (#7650)
AbhiPrasad Mar 29, 2023
a3dfde6
fix(sveltekit): Explicitly export Node SDK exports (#7644)
Lms24 Mar 30, 2023
6678a4a
doc(sveltekit): Update README (#7653)
Lms24 Mar 30, 2023
dc0179c
ref(sveltekit): Split up universal and server load wrappers (#7652)
Lms24 Mar 30, 2023
e055f27
feat(tracing): Add custom finishReason for when interaction is interr…
0Calories Mar 30, 2023
d98ac5d
feat(tracing): Do not create interactions if `navigation` or `pageloa…
0Calories Mar 30, 2023
bb11a2e
feat(node): Add Sentry tRPC middleware (#7511)
Mar 30, 2023
cefd523
ref(sveltekit): Inject init plugin only if sentry config files exist …
Lms24 Mar 30, 2023
1eb271a
doc(sveltekit): Move SDK initialization to hooks and restructure setu…
Lms24 Mar 30, 2023
65c44ec
perf(build): Use @rollup/plugin-typescript for es5 builds (#7665)
AbhiPrasad Mar 30, 2023
9ecd152
feat(node): Sanitize URLs in Span descriptions and breadcrumbs (#7667)
Lms24 Mar 30, 2023
1403e77
test: Make E2E tests async & parallelizable (#7466)
mydea Mar 30, 2023
d94a017
fix(docs): Remove references to `@sentry/tracing` from READMEs (#7600)
timfish Mar 30, 2023
5e288f5
meta(changelog): Update changelog for 7.46.0
AbhiPrasad Mar 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(replay): Capture replay mutation breadcrumbs & add experiment (#…
  • Loading branch information
mydea authored Mar 24, 2023
commit a2103f37794740573f6fce0f549e24bd06d08cbb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 500,
flushMaxDelay: 500,
});

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="button-add">Add items</button>
<button id="button-modify">Modify items</button>
<button id="button-remove">Remove items</button>
<ul class="list"></ul>

<script>
document.querySelector('#button-add').addEventListener('click', () => {
const list = document.querySelector('.list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `test list item: ${i}`;
li.setAttribute('id', `${i}`);
list.appendChild(li);
}
});

document.querySelector('#button-modify').addEventListener('click', () => {
document.querySelectorAll('li').forEach(li => {
el.setAttribute('js-is-checked', new Date().toISOString());
el.setAttribute('js-is-checked-2', new Date().toISOString());
el.setAttribute('js-is-checked-3', 'yes');
el.setAttribute('js-is-checked-4', 'yes');
el.setAttribute('js-is-checked-5', 'yes');
el.setAttribute('js-is-checked-6', 'yes');
});
});

document.querySelector('#button-remove').addEventListener('click', () => {
document.querySelectorAll('li').forEach(li => {
document.querySelector('ul').removeChild(li);
});
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getReplayRecordingContent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest(
'handles large mutations with default options',
async ({ getLocalTestPath, page, forceFlushReplay, browserName }) => {
if (shouldSkipReplayTest() || ['webkit', 'firefox'].includes(browserName)) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise0b = waitForReplayRequest(page, 1);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await forceFlushReplay();
const res0 = await reqPromise0;
await reqPromise0b;
// A second request is sent right after initial snapshot, we want to wait for that to settle before we continue

const reqPromise1 = waitForReplayRequest(page);

void page.click('#button-add');
await forceFlushReplay();
const res1 = await reqPromise1;

const reqPromise2 = waitForReplayRequest(page);

void page.click('#button-modify');
await forceFlushReplay();
const res2 = await reqPromise2;

const reqPromise3 = waitForReplayRequest(page);

void page.click('#button-remove');
await forceFlushReplay();
const res3 = await reqPromise3;

const replayData0 = getReplayRecordingContent(res0);
const replayData1 = getReplayRecordingContent(res1);
const replayData2 = getReplayRecordingContent(res2);
const replayData3 = getReplayRecordingContent(res3);

expect(replayData0.fullSnapshots.length).toBe(1);
expect(replayData0.incrementalSnapshots.length).toBe(0);

expect(replayData1.fullSnapshots.length).toBe(0);
expect(replayData1.incrementalSnapshots.length).toBeGreaterThan(0);

expect(replayData2.fullSnapshots.length).toBe(0);
expect(replayData2.incrementalSnapshots.length).toBeGreaterThan(0);

expect(replayData3.fullSnapshots.length).toBe(0);
expect(replayData3.incrementalSnapshots.length).toBeGreaterThan(0);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 500,
flushMaxDelay: 500,
_experiments: {
mutationLimit: 250,
},
});

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,
debug: true,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="button-add">Add items</button>
<button id="button-modify">Modify items</button>
<button id="button-remove">Remove items</button>
<ul class="list"></ul>

<script>
document.querySelector('#button-add').addEventListener('click', () => {
const list = document.querySelector('.list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `test list item: ${i}`;
li.setAttribute('id', `${i}`);
list.appendChild(li);
}
});

document.querySelector('#button-modify').addEventListener('click', () => {
document.querySelectorAll('li').forEach(li => {
el.setAttribute('js-is-checked', new Date().toISOString());
el.setAttribute('js-is-checked-2', new Date().toISOString());
el.setAttribute('js-is-checked-3', 'yes');
el.setAttribute('js-is-checked-4', 'yes');
el.setAttribute('js-is-checked-5', 'yes');
el.setAttribute('js-is-checked-6', 'yes');
});
});

document.querySelector('#button-remove').addEventListener('click', () => {
document.querySelectorAll('li').forEach(li => {
document.querySelector('ul').removeChild(li);
});
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getReplayRecordingContent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest(
'handles large mutations with _experiments.mutationLimit configured',
async ({ getLocalTestPath, page, forceFlushReplay, browserName }) => {
if (shouldSkipReplayTest() || ['webkit', 'firefox'].includes(browserName)) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise0b = waitForReplayRequest(page, 1);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
const res0 = await reqPromise0;
await reqPromise0b;
// A second request is sent right after initial snapshot, we want to wait for that to settle before we continue

const reqPromise1 = waitForReplayRequest(page);

void page.click('#button-add');
await forceFlushReplay();
const res1 = await reqPromise1;

const reqPromise2 = waitForReplayRequest(page);

void page.click('#button-modify');
await forceFlushReplay();
const res2 = await reqPromise2;

const reqPromise3 = waitForReplayRequest(page);

void page.click('#button-remove');
await forceFlushReplay();
const res3 = await reqPromise3;

const replayData0 = getReplayRecordingContent(res0);
const replayData1 = getReplayRecordingContent(res1);
const replayData2 = getReplayRecordingContent(res2);
const replayData3 = getReplayRecordingContent(res3);

expect(replayData0.fullSnapshots.length).toBe(1);
expect(replayData0.incrementalSnapshots.length).toBe(0);

// This includes both a full snapshot as well as some incremental snapshots
expect(replayData1.fullSnapshots.length).toBe(1);
expect(replayData1.incrementalSnapshots.length).toBeGreaterThan(0);

// This does not trigger mutations, for whatever reason - so no full snapshot either!
expect(replayData2.fullSnapshots.length).toBe(0);
expect(replayData2.incrementalSnapshots.length).toBeGreaterThan(0);

// This includes both a full snapshot as well as some incremental snapshots
expect(replayData3.fullSnapshots.length).toBe(1);
expect(replayData3.incrementalSnapshots.length).toBeGreaterThan(0);
},
);
53 changes: 34 additions & 19 deletions packages/replay/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,23 +207,7 @@ export class ReplayContainer implements ReplayContainerInterface {
// instead, we'll always keep the last 60 seconds of replay before an error happened
...(this.recordingMode === 'error' && { checkoutEveryNms: ERROR_CHECKOUT_TIME }),
emit: getHandleRecordingEmit(this),
onMutation: (mutations: unknown[]) => {
if (this._options._experiments.captureMutationSize) {
const count = mutations.length;

if (count > 500) {
const breadcrumb = createBreadcrumb({
category: 'replay.mutations',
data: {
count,
},
});
this._createCustomBreadcrumb(breadcrumb);
}
}
// `true` means we use the regular mutation handling by rrweb
return true;
},
onMutation: this._onMutationHandler,
});
} catch (err) {
this._handleException(err);
Expand Down Expand Up @@ -622,10 +606,10 @@ export class ReplayContainer implements ReplayContainerInterface {
* Trigger rrweb to take a full snapshot which will cause this plugin to
* create a new Replay event.
*/
private _triggerFullSnapshot(): void {
private _triggerFullSnapshot(checkout = true): void {
try {
__DEBUG_BUILD__ && logger.log('[Replay] Taking full rrweb snapshot');
record.takeFullSnapshot(true);
record.takeFullSnapshot(checkout);
} catch (err) {
this._handleException(err);
}
Expand Down Expand Up @@ -839,4 +823,35 @@ export class ReplayContainer implements ReplayContainerInterface {
saveSession(this.session);
}
}

/** Handler for rrweb.record.onMutation */
private _onMutationHandler = (mutations: unknown[]): boolean => {
const count = mutations.length;

const mutationLimit = this._options._experiments.mutationLimit || 0;
const mutationBreadcrumbLimit = this._options._experiments.mutationBreadcrumbLimit || 1000;
const overMutationLimit = mutationLimit && count > mutationLimit;

// Create a breadcrumb if a lot of mutations happen at the same time
// We can show this in the UI as an information with potential performance improvements
if (count > mutationBreadcrumbLimit || overMutationLimit) {
const breadcrumb = createBreadcrumb({
category: 'replay.mutations',
data: {
count,
},
});
this._createCustomBreadcrumb(breadcrumb);
}

if (overMutationLimit) {
// We want to skip doing an incremental snapshot if there are too many mutations
// Instead, we do a full snapshot
this._triggerFullSnapshot(false);
return false;
}

// `true` means we use the regular mutation handling by rrweb
return true;
};
}
3 changes: 2 additions & 1 deletion packages/replay/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export interface ReplayPluginOptions extends SessionOptions {
_experiments: Partial<{
captureExceptions: boolean;
traceInternals: boolean;
captureMutationSize: boolean;
mutationLimit: number;
mutationBreadcrumbLimit: number;
}>;
}

Expand Down