Skip to content

Commit 7bba582

Browse files
authored
Merge branch 'develop' into sig/nuxt-sourcemaps
2 parents fe665ab + 77b3355 commit 7bba582

File tree

4 files changed

+175
-6
lines changed

4 files changed

+175
-6
lines changed

.github/workflows/release-comment-issues.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ jobs:
2020
run: echo "version=${{ github.event.inputs.version || github.event.release.tag_name }}" >> $GITHUB_OUTPUT
2121

2222
- name: Comment on linked issues that are mentioned in release
23-
if: steps.get_version.outputs.version != ''
23+
if: |
24+
steps.get_version.outputs.version != ''
25+
&& !contains(steps.get_version.outputs.version, '-beta.')
26+
&& !contains(steps.get_version.outputs.version, '-alpha.')
27+
&& !contains(steps.get_version.outputs.version, '-rc.')
28+
2429
uses: getsentry/release-comment-issues-gh-action@v1
2530
with:
2631
github_token: ${{ secrets.GITHUB_TOKEN }}

dev-packages/browser-integration-tests/suites/replay/bufferModeManual/test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,125 @@ sentryTest(
287287
},
288288
);
289289

290+
sentryTest(
291+
'[buffer-mode] manually starting replay ignores earlier performance entries',
292+
async ({ getLocalTestUrl, page, browserName }) => {
293+
// This was sometimes flaky on webkit, so skipping for now
294+
if (shouldSkipReplayTest() || browserName === 'webkit') {
295+
sentryTest.skip();
296+
}
297+
298+
const reqPromise0 = waitForReplayRequest(page, 0);
299+
300+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
301+
return route.fulfill({
302+
status: 200,
303+
contentType: 'application/json',
304+
body: JSON.stringify({ id: 'test-id' }),
305+
});
306+
});
307+
308+
const url = await getLocalTestUrl({ testDir: __dirname });
309+
310+
await page.goto(url);
311+
312+
// Wait for everything to be initialized - Replay is not running yet
313+
await page.waitForFunction('!!window.Replay');
314+
315+
// Wait for a second, then start replay
316+
await new Promise(resolve => setTimeout(resolve, 1000));
317+
await page.evaluate('window.Replay.start()');
318+
319+
const req0 = await reqPromise0;
320+
321+
const event0 = getReplayEvent(req0);
322+
const content0 = getReplayRecordingContent(req0);
323+
324+
expect(event0).toEqual(
325+
getExpectedReplayEvent({
326+
replay_type: 'session',
327+
}),
328+
);
329+
330+
const { performanceSpans } = content0;
331+
332+
// Here, we test that this does not contain any web-vital etc. performance spans
333+
// as these have been emitted _before_ the replay was manually started
334+
expect(performanceSpans).toEqual([
335+
{
336+
op: 'memory',
337+
description: 'memory',
338+
startTimestamp: expect.any(Number),
339+
endTimestamp: expect.any(Number),
340+
data: {
341+
memory: {
342+
jsHeapSizeLimit: expect.any(Number),
343+
totalJSHeapSize: expect.any(Number),
344+
usedJSHeapSize: expect.any(Number),
345+
},
346+
},
347+
},
348+
]);
349+
},
350+
);
351+
352+
sentryTest(
353+
'[buffer-mode] manually starting replay ignores earlier performance entries when starting immediately',
354+
async ({ getLocalTestUrl, page, browserName }) => {
355+
// This was sometimes flaky on webkit, so skipping for now
356+
if (shouldSkipReplayTest() || browserName === 'webkit') {
357+
sentryTest.skip();
358+
}
359+
360+
const reqPromise0 = waitForReplayRequest(page, 0);
361+
362+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
363+
return route.fulfill({
364+
status: 200,
365+
contentType: 'application/json',
366+
body: JSON.stringify({ id: 'test-id' }),
367+
});
368+
});
369+
370+
const url = await getLocalTestUrl({ testDir: __dirname });
371+
372+
page.goto(url);
373+
374+
// Wait for everything to be initialized, then start replay as soon as possible
375+
await page.waitForFunction('!!window.Replay');
376+
await page.evaluate('window.Replay.start()');
377+
378+
const req0 = await reqPromise0;
379+
380+
const event0 = getReplayEvent(req0);
381+
const content0 = getReplayRecordingContent(req0);
382+
383+
expect(event0).toEqual(
384+
getExpectedReplayEvent({
385+
replay_type: 'session',
386+
}),
387+
);
388+
389+
const { performanceSpans } = content0;
390+
391+
expect(performanceSpans).toEqual([
392+
{
393+
op: 'memory',
394+
description: 'memory',
395+
startTimestamp: expect.any(Number),
396+
endTimestamp: expect.any(Number),
397+
data: {
398+
memory: {
399+
jsHeapSizeLimit: expect.any(Number),
400+
totalJSHeapSize: expect.any(Number),
401+
usedJSHeapSize: expect.any(Number),
402+
},
403+
},
404+
},
405+
]);
406+
},
407+
);
408+
290409
// Doing this in buffer mode to test changing error sample rate after first
291410
// error happens.
292411
sentryTest(

packages/nuxt/src/server/sdk.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { applySdkMetadata, getGlobalScope } from '@sentry/core';
2-
import { init as initNode } from '@sentry/node';
3-
import type { Client, EventProcessor } from '@sentry/types';
4-
import { logger } from '@sentry/utils';
1+
import { applySdkMetadata, flush, getGlobalScope } from '@sentry/core';
2+
import {
3+
type NodeOptions,
4+
getDefaultIntegrations as getDefaultNodeIntegrations,
5+
httpIntegration,
6+
init as initNode,
7+
} from '@sentry/node';
8+
import type { Client, EventProcessor, Integration } from '@sentry/types';
9+
import { logger, vercelWaitUntil } from '@sentry/utils';
510
import { DEBUG_BUILD } from '../common/debug-build';
611
import type { SentryNuxtServerOptions } from '../common/types';
712

@@ -14,6 +19,7 @@ export function init(options: SentryNuxtServerOptions): Client | undefined {
1419
const sentryOptions = {
1520
...options,
1621
registerEsmLoaderHooks: mergeRegisterEsmLoaderHooks(options),
22+
defaultIntegrations: getNuxtDefaultIntegrations(options),
1723
};
1824

1925
applySdkMetadata(sentryOptions, 'nuxt', ['nuxt', 'node']);
@@ -46,6 +52,21 @@ export function init(options: SentryNuxtServerOptions): Client | undefined {
4652
return client;
4753
}
4854

55+
function getNuxtDefaultIntegrations(options: NodeOptions): Integration[] {
56+
return [
57+
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
58+
// The httpIntegration is added as defaultIntegration, so users can still overwrite it
59+
httpIntegration({
60+
instrumentation: {
61+
responseHook: () => {
62+
// Makes it possible to end the tracing span before closing the Vercel lambda (https://vercel.com/docs/functions/functions-api-reference#waituntil)
63+
vercelWaitUntil(flushSafelyWithTimeout());
64+
},
65+
},
66+
}),
67+
];
68+
}
69+
4970
/**
5071
* Adds /vue/ to the registerEsmLoaderHooks options and merges it with the old values in the array if one is defined.
5172
* If the registerEsmLoaderHooks option is already a boolean, nothing is changed.
@@ -64,3 +85,16 @@ export function mergeRegisterEsmLoaderHooks(
6485
}
6586
return options.registerEsmLoaderHooks ?? { exclude: [/vue/] };
6687
}
88+
89+
/**
90+
* Flushes pending Sentry events with a 2-second timeout and in a way that cannot create unhandled promise rejections.
91+
*/
92+
export async function flushSafelyWithTimeout(): Promise<void> {
93+
try {
94+
DEBUG_BUILD && logger.log('Flushing events...');
95+
await flush(2000);
96+
DEBUG_BUILD && logger.log('Done flushing events');
97+
} catch (e) {
98+
DEBUG_BUILD && logger.log('Error while flushing events:\n', e);
99+
}
100+
}

packages/replay-internal/src/replay.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,11 +1046,22 @@ export class ReplayContainer implements ReplayContainerInterface {
10461046
* are included in the replay event before it is finished and sent to Sentry.
10471047
*/
10481048
private _addPerformanceEntries(): Promise<Array<AddEventResult | null>> {
1049-
const performanceEntries = createPerformanceEntries(this.performanceEntries).concat(this.replayPerformanceEntries);
1049+
let performanceEntries = createPerformanceEntries(this.performanceEntries).concat(this.replayPerformanceEntries);
10501050

10511051
this.performanceEntries = [];
10521052
this.replayPerformanceEntries = [];
10531053

1054+
// If we are manually starting, we want to ensure we only include performance entries
1055+
// that are after the initial timestamp
1056+
// The reason for this is that we may have performance entries from the page load, but may decide to start
1057+
// the replay later on, in which case we do not want to include these entries.
1058+
// without this, manually started replays can have events long before the actual replay recording starts,
1059+
// which messes with the timeline etc.
1060+
if (this._requiresManualStart) {
1061+
const initialTimestampInSeconds = this._context.initialTimestamp / 1000;
1062+
performanceEntries = performanceEntries.filter(entry => entry.start >= initialTimestampInSeconds);
1063+
}
1064+
10541065
return Promise.all(createPerformanceSpans(this, performanceEntries));
10551066
}
10561067

0 commit comments

Comments
 (0)