Skip to content

Commit 30da5c3

Browse files
authored
Merge pull request #8788 from getsentry/prepare-release/7.63.0
meta(changelog): Update changelog for 7.63.0
2 parents a9fe05c + 3439d39 commit 30da5c3

File tree

33 files changed

+850
-123
lines changed

33 files changed

+850
-123
lines changed

CHANGELOG.md

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

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

7+
## 7.63.0
8+
9+
- build(deps): bump @opentelemetry/instrumentation from 0.41.0 to 0.41.2
10+
- feat(eventbuilder): Export `exceptionFromError` for use in hybrid SDKs (#8766)
11+
- feat(node-experimental): Re-export from node (#8786)
12+
- feat(tracing): Add db connection attributes for mysql spans (#8775)
13+
- feat(tracing): Add db connection attributes for postgres spans (#8778)
14+
- feat(tracing): Improve data collection for mongodb spans (#8774)
15+
- fix(nextjs): Execute sentry config independently of `autoInstrumentServerFunctions` and `autoInstrumentAppDirectory` (#8781)
16+
- fix(replay): Ensure we do not flush if flush took too long (#8784)
17+
- fix(replay): Ensure we do not try to flush when we force stop replay (#8783)
18+
- fix(replay): Fix `hasCheckout` handling (#8782)
19+
- fix(replay): Handle multiple clicks in a short time (#8773)
20+
- ref(replay): Skip events being added too long after initial segment (#8768)
21+
722
## 7.62.0
823

924
### Important Changes
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button onclick="console.log('Test log')" id="button1">Click me</button>
8+
<button onclick="console.log('Test log 2')" id="button2">Click me</button>
9+
</body>
10+
</html>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { envelopeRequestParser } from '../../../utils/helpers';
5+
import {
6+
getDecompressedRecordingEvents,
7+
getReplaySnapshot,
8+
isReplayEvent,
9+
REPLAY_DEFAULT_FLUSH_MAX_DELAY,
10+
shouldSkipReplayTest,
11+
waitForReplayRequest,
12+
} from '../../../utils/replayHelpers';
13+
14+
sentryTest(
15+
'should stop recording when running into eventBuffer error',
16+
async ({ getLocalTestPath, page, forceFlushReplay }) => {
17+
if (shouldSkipReplayTest()) {
18+
sentryTest.skip();
19+
}
20+
21+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
22+
return route.fulfill({
23+
status: 200,
24+
});
25+
});
26+
27+
const url = await getLocalTestPath({ testDir: __dirname });
28+
await page.goto(url);
29+
30+
await waitForReplayRequest(page);
31+
const replay = await getReplaySnapshot(page);
32+
expect(replay._isEnabled).toBe(true);
33+
34+
await forceFlushReplay();
35+
36+
let called = 0;
37+
38+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
39+
const event = envelopeRequestParser(route.request());
40+
41+
// We only want to count replays here
42+
if (event && isReplayEvent(event)) {
43+
const events = getDecompressedRecordingEvents(route.request());
44+
// this makes sure we ignore e.g. mouse move events which can otherwise lead to flakes
45+
if (events.length > 0) {
46+
called++;
47+
}
48+
}
49+
50+
return route.fulfill({
51+
status: 200,
52+
});
53+
});
54+
55+
called = 0;
56+
57+
/**
58+
* We test the following here:
59+
* 1. First click should add an event (so the eventbuffer is not empty)
60+
* 2. Second click should throw an error in eventBuffer (which should lead to stopping the replay)
61+
* 3. Nothing should be sent to API, as we stop the replay due to the eventBuffer error.
62+
*/
63+
await page.evaluate(`
64+
window._count = 0;
65+
window._addEvent = window.Replay._replay.eventBuffer.addEvent.bind(window.Replay._replay.eventBuffer);
66+
window.Replay._replay.eventBuffer.addEvent = (...args) => {
67+
window._count++;
68+
if (window._count === 2) {
69+
throw new Error('provoked error');
70+
}
71+
window._addEvent(...args);
72+
};
73+
`);
74+
75+
void page.click('#button1');
76+
void page.click('#button2');
77+
78+
// Should immediately skip retrying and just cancel, no backoff
79+
// This waitForTimeout call should be okay, as we're not checking for any
80+
// further network requests afterwards.
81+
await page.waitForTimeout(REPLAY_DEFAULT_FLUSH_MAX_DELAY + 100);
82+
83+
expect(called).toBe(0);
84+
85+
const replay2 = await getReplaySnapshot(page);
86+
87+
expect(replay2._isEnabled).toBe(false);
88+
},
89+
);

packages/browser-integration-tests/utils/replayHelpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ function getOptionsEvents(replayRequest: Request): CustomRecordingEvent[] {
267267
return getAllCustomRrwebRecordingEvents(events).filter(data => data.tag === 'options');
268268
}
269269

270-
function getDecompressedRecordingEvents(resOrReq: Request | Response): RecordingSnapshot[] {
270+
export function getDecompressedRecordingEvents(resOrReq: Request | Response): RecordingSnapshot[] {
271271
const replayRequest = getRequest(resOrReq);
272272
return (
273273
(replayEnvelopeRequestParser(replayRequest, 5) as eventWithTime[])
@@ -302,7 +302,7 @@ const replayEnvelopeRequestParser = (request: Request | null, envelopeIndex = 2)
302302
return envelope[envelopeIndex] as Event;
303303
};
304304

305-
const replayEnvelopeParser = (request: Request | null): unknown[] => {
305+
export const replayEnvelopeParser = (request: Request | null): unknown[] => {
306306
// https://develop.sentry.dev/sdk/envelopes/
307307
const envelopeBytes = request?.postDataBuffer() || '';
308308

packages/browser/src/exports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export {
6161
opera11StackLineParser,
6262
winjsStackLineParser,
6363
} from './stack-parsers';
64-
export { eventFromException, eventFromMessage } from './eventbuilder';
64+
export { eventFromException, eventFromMessage, exceptionFromError } from './eventbuilder';
6565
export { createUserFeedbackEnvelope } from './userfeedback';
6666
export { defaultIntegrations, forceLoad, init, onLoad, showReportDialog, wrap, captureUserFeedback } from './sdk';
6767
export { GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations';

packages/nextjs/rollup.npm.config.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ export default [
2424
...makeNPMConfigVariants(
2525
makeBaseNPMConfig({
2626
entrypoints: [
27-
'src/config/templates/pageWrapperTemplate.ts',
2827
'src/config/templates/apiWrapperTemplate.ts',
2928
'src/config/templates/middlewareWrapperTemplate.ts',
30-
'src/config/templates/serverComponentWrapperTemplate.ts',
29+
'src/config/templates/pageWrapperTemplate.ts',
3130
'src/config/templates/requestAsyncStorageShim.ts',
31+
'src/config/templates/sentryInitWrapperTemplate.ts',
32+
'src/config/templates/serverComponentWrapperTemplate.ts',
3233
],
3334

3435
packageSpecificConfig: {
@@ -47,6 +48,7 @@ export default [
4748
external: [
4849
'@sentry/nextjs',
4950
'next/dist/client/components/request-async-storage',
51+
'__SENTRY_CONFIG_IMPORT_PATH__',
5052
'__SENTRY_WRAPPING_TARGET_FILE__',
5153
'__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__',
5254
],

packages/nextjs/src/config/loaders/wrappingLoader.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const requestAsyncStorageShimPath = path.resolve(__dirname, '..', 'templates', '
3030
const requestAsyncStorageModuleExists = moduleExists(NEXTJS_REQUEST_ASYNC_STORAGE_MODULE_PATH);
3131
let showedMissingAsyncStorageModuleWarning = false;
3232

33+
const sentryInitWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'sentryInitWrapperTemplate.js');
34+
const sentryInitWrapperTemplateCode = fs.readFileSync(sentryInitWrapperTemplatePath, { encoding: 'utf8' });
35+
3336
const serverComponentWrapperTemplatePath = path.resolve(
3437
__dirname,
3538
'..',
@@ -43,7 +46,7 @@ type LoaderOptions = {
4346
appDir: string;
4447
pageExtensionRegex: string;
4548
excludeServerRoutes: Array<RegExp | string>;
46-
wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component';
49+
wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component' | 'sentry-init';
4750
sentryConfigFilePath?: string;
4851
vercelCronsConfig?: VercelCronsConfig;
4952
};
@@ -83,7 +86,23 @@ export default function wrappingLoader(
8386

8487
let templateCode: string;
8588

86-
if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') {
89+
if (wrappingTargetKind === 'sentry-init') {
90+
templateCode = sentryInitWrapperTemplateCode;
91+
92+
// Absolute paths to the sentry config do not work with Windows: https://github.com/getsentry/sentry-javascript/issues/8133
93+
// Se we need check whether `this.resourcePath` is absolute because there is no contract by webpack that says it is absolute.
94+
// Examples where `this.resourcePath` could possibly be non-absolute are virtual modules.
95+
if (sentryConfigFilePath && path.isAbsolute(this.resourcePath)) {
96+
const sentryConfigImportPath = path
97+
.relative(path.dirname(this.resourcePath), sentryConfigFilePath)
98+
.replace(/\\/g, '/');
99+
templateCode = templateCode.replace(/__SENTRY_CONFIG_IMPORT_PATH__/g, sentryConfigImportPath);
100+
} else {
101+
// Bail without doing any wrapping
102+
this.callback(null, userCode, userModuleSourceMap);
103+
return;
104+
}
105+
} else if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') {
87106
// Get the parameterized route name from this page's filepath
88107
const parameterizedPagesRoute = path.posix
89108
.normalize(
@@ -207,15 +226,6 @@ export default function wrappingLoader(
207226
throw new Error(`Invariant: Could not get template code of unknown kind "${wrappingTargetKind}"`);
208227
}
209228

210-
// We check whether `this.resourcePath` is absolute because there is no contract by webpack that says it is absolute,
211-
// however we can only create relative paths to the sentry config from absolute paths.Examples where this could possibly be non - absolute are virtual modules.
212-
if (sentryConfigFilePath && path.isAbsolute(this.resourcePath)) {
213-
const sentryConfigImportPath = path
214-
.relative(path.dirname(this.resourcePath), sentryConfigFilePath) // Absolute paths do not work with Windows: https://github.com/getsentry/sentry-javascript/issues/8133
215-
.replace(/\\/g, '/');
216-
templateCode = `import "${sentryConfigImportPath}";\n`.concat(templateCode);
217-
}
218-
219229
// Replace the import path of the wrapping target in the template with a path that the `wrapUserCode` function will understand.
220230
templateCode = templateCode.replace(/__SENTRY_WRAPPING_TARGET_FILE__/g, WRAPPING_TARGET_MODULE_NAME);
221231

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @ts-ignore This will be replaced with the user's sentry config gile
2+
// eslint-disable-next-line import/no-unresolved
3+
import '__SENTRY_CONFIG_IMPORT_PATH__';
4+
5+
// @ts-ignore This is the file we're wrapping
6+
// eslint-disable-next-line import/no-unresolved
7+
export * from '__SENTRY_WRAPPING_TARGET_FILE__';
8+
9+
// @ts-ignore This is the file we're wrapping
10+
// eslint-disable-next-line import/no-unresolved
11+
export { default } from '__SENTRY_WRAPPING_TARGET_FILE__';

packages/nextjs/src/config/webpack.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,45 @@ export function constructWebpackConfigFunction(
140140
return path.normalize(absoluteResourcePath);
141141
};
142142

143+
const isPageResource = (resourcePath: string): boolean => {
144+
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
145+
return (
146+
normalizedAbsoluteResourcePath.startsWith(pagesDirPath + path.sep) &&
147+
!normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) &&
148+
dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext))
149+
);
150+
};
151+
152+
const isApiRouteResource = (resourcePath: string): boolean => {
153+
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
154+
return (
155+
normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) &&
156+
dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext))
157+
);
158+
};
159+
160+
const isMiddlewareResource = (resourcePath: string): boolean => {
161+
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
162+
return normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath;
163+
};
164+
165+
const isServerComponentResource = (resourcePath: string): boolean => {
166+
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
167+
168+
// ".js, .jsx, or .tsx file extensions can be used for Pages"
169+
// https://beta.nextjs.org/docs/routing/pages-and-layouts#pages:~:text=.js%2C%20.jsx%2C%20or%20.tsx%20file%20extensions%20can%20be%20used%20for%20Pages.
170+
return (
171+
normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) &&
172+
!!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/)
173+
);
174+
};
175+
143176
if (isServer && userSentryOptions.autoInstrumentServerFunctions !== false) {
144177
// It is very important that we insert our loaders at the beginning of the array because we expect any sort of transformations/transpilations (e.g. TS -> JS) to already have happened.
145178

146179
// Wrap pages
147180
newConfig.module.rules.unshift({
148-
test: resourcePath => {
149-
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
150-
return (
151-
normalizedAbsoluteResourcePath.startsWith(pagesDirPath + path.sep) &&
152-
!normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) &&
153-
dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext))
154-
);
155-
},
181+
test: isPageResource,
156182
use: [
157183
{
158184
loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'),
@@ -190,13 +216,7 @@ export function constructWebpackConfigFunction(
190216

191217
// Wrap api routes
192218
newConfig.module.rules.unshift({
193-
test: resourcePath => {
194-
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
195-
return (
196-
normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) &&
197-
dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext))
198-
);
199-
},
219+
test: isApiRouteResource,
200220
use: [
201221
{
202222
loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'),
@@ -211,12 +231,7 @@ export function constructWebpackConfigFunction(
211231

212232
// Wrap middleware
213233
newConfig.module.rules.unshift({
214-
test: resourcePath => {
215-
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
216-
return (
217-
normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath
218-
);
219-
},
234+
test: isMiddlewareResource,
220235
use: [
221236
{
222237
loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'),
@@ -232,22 +247,36 @@ export function constructWebpackConfigFunction(
232247
if (isServer && userSentryOptions.autoInstrumentAppDirectory !== false) {
233248
// Wrap page server components
234249
newConfig.module.rules.unshift({
235-
test: resourcePath => {
236-
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
250+
test: isServerComponentResource,
251+
use: [
252+
{
253+
loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'),
254+
options: {
255+
...staticWrappingLoaderOptions,
256+
wrappingTargetKind: 'server-component',
257+
},
258+
},
259+
],
260+
});
261+
}
237262

238-
// ".js, .jsx, or .tsx file extensions can be used for Pages"
239-
// https://beta.nextjs.org/docs/routing/pages-and-layouts#pages:~:text=.js%2C%20.jsx%2C%20or%20.tsx%20file%20extensions%20can%20be%20used%20for%20Pages.
263+
if (isServer) {
264+
// Import the Sentry config in every user file
265+
newConfig.module.rules.unshift({
266+
test: resourcePath => {
240267
return (
241-
normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) &&
242-
!!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/)
268+
isPageResource(resourcePath) ||
269+
isApiRouteResource(resourcePath) ||
270+
isMiddlewareResource(resourcePath) ||
271+
isServerComponentResource(resourcePath)
243272
);
244273
},
245274
use: [
246275
{
247276
loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'),
248277
options: {
249278
...staticWrappingLoaderOptions,
250-
wrappingTargetKind: 'server-component',
279+
wrappingTargetKind: 'sentry-init',
251280
},
252281
},
253282
],

0 commit comments

Comments
 (0)