Skip to content

Commit 6c74347

Browse files
authored
Merge branch 'develop' into abhi-bun-sentry-node
2 parents 8cb3c0f + d09d184 commit 6c74347

File tree

97 files changed

+3357
-1892
lines changed

Some content is hidden

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

97 files changed

+3357
-1892
lines changed

.craft.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ targets:
9191
- name: npm
9292
id: '@sentry/serverless'
9393
includeNames: /^sentry-serverless-\d.*\.tgz$/
94+
- name: npm
95+
id: '@sentry/google-cloud'
96+
includeNames: /^sentry-google-cloud-\d.*\.tgz$/
9497
- name: npm
9598
id: '@sentry/bun'
9699
includeNames: /^sentry-bun-\d.*\.tgz$/

.github/ISSUE_TEMPLATE/bug.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ body:
4343
- '@sentry/react'
4444
- '@sentry/remix'
4545
- '@sentry/serverless'
46+
- '@sentry/google-cloud'
4647
- '@sentry/svelte'
4748
- '@sentry/sveltekit'
4849
- '@sentry/vue'

MIGRATION.md

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,43 @@ The following previously deprecated API has been removed from the `@sentry/nextj
546546
- `IS_BUILD`
547547
- `isBuild`
548548

549+
#### Merging of the Sentry Webpack Plugin options and SDK Build options
550+
551+
With version 8 of the Sentry Next.js SDK, `withSentryConfig` will no longer accept 3 arguments. The second argument
552+
(holding options for the Sentry Webpack plugin) and the third argument (holding options for SDK build-time
553+
configuration) should now be passed as one:
554+
555+
```ts
556+
// OLD
557+
const nextConfig = {
558+
// Your Next.js options...
559+
};
560+
561+
module.exports = withSentryConfig(
562+
nextConfig,
563+
{
564+
// Your Sentry Webpack Plugin Options...
565+
},
566+
{
567+
// Your Sentry SDK options...
568+
},
569+
);
570+
571+
// NEW
572+
const nextConfig = {
573+
// Your Next.js options...
574+
};
575+
576+
module.exports = withSentryConfig(nextConfig, {
577+
// Your Sentry Webpack Plugin Options...
578+
// AND your Sentry SDK options...
579+
});
580+
```
581+
549582
#### Removal of the `sentry` property in your Next.js options (next.config.js)
550583

551584
With version 8 of the Sentry Next.js SDK, the SDK will no longer support passing Next.js options with a `sentry`
552-
property to `withSentryConfig`. Please use the third argument of `withSentryConfig` to configure the SDK instead:
585+
property to `withSentryConfig`. Please use the second argument of `withSentryConfig` to configure the SDK instead:
553586

554587
```ts
555588
// v7
@@ -572,21 +605,25 @@ const nextConfig = {
572605
// Your Next.js options...
573606
};
574607

575-
module.exports = withSentryConfig(
576-
nextConfig,
577-
{
578-
// Your Sentry Webpack Plugin Options...
579-
},
580-
{
581-
// Your Sentry SDK options...
582-
},
583-
);
608+
module.exports = withSentryConfig(nextConfig, {
609+
// Your Sentry Webpack Plugin Options...
610+
// AND your Sentry SDK options...
611+
});
584612
```
585613

586614
The reason for this change is to have one consistent way of defining the SDK options. We hope that this change will
587615
reduce confusion when setting up the SDK, with the upside that the explicit option is properly typed and will therefore
588616
have code completion.
589617

618+
#### Updated the `@sentry/webpack-plugin` dependency to version 2
619+
620+
We bumped the internal usage of `@sentry/webpack-plugin` to a new major version. This comes with multiple upsides like a
621+
simpler configuration interface and the use of new state of the art Debug ID technology. Debug IDs will simplify the
622+
setup for source maps in Sentry and will not require you to match stack frame paths to uploaded artifacts anymore.
623+
624+
To see the new options, check out the docs at https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/,
625+
or look at the TypeScript type definitions of `withSentryConfig`.
626+
590627
### Astro SDK
591628

592629
#### Removal of `trackHeaders` option for Astro middleware

dev-packages/e2e-tests/test-applications/angular-17/src/app/app.routes.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { Routes } from '@angular/router';
2+
import { cancelGuard } from './cancel-guard.guard';
3+
import { CancelComponent } from './cancel/cancel.components';
4+
import { ComponentTrackingComponent } from './component-tracking/component-tracking.components';
25
import { HomeComponent } from './home/home.component';
36
import { UserComponent } from './user/user.component';
47

@@ -11,6 +14,15 @@ export const routes: Routes = [
1114
path: 'home',
1215
component: HomeComponent,
1316
},
17+
{
18+
path: 'cancel',
19+
component: CancelComponent,
20+
canActivate: [cancelGuard],
21+
},
22+
{
23+
path: 'component-tracking',
24+
component: ComponentTrackingComponent,
25+
},
1426
{
1527
path: 'redirect1',
1628
redirectTo: '/redirect2',
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
2+
3+
export const cancelGuard: CanActivateFn = (_next: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
4+
return false;
5+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-cancel',
5+
standalone: true,
6+
template: `<div></div>`,
7+
})
8+
export class CancelComponent {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { AfterViewInit, Component, OnInit } from '@angular/core';
2+
import { TraceClassDecorator, TraceMethodDecorator, TraceModule } from '@sentry/angular-ivy';
3+
import { SampleComponent } from '../sample-component/sample-component.components';
4+
5+
@Component({
6+
selector: 'app-cancel',
7+
standalone: true,
8+
imports: [TraceModule, SampleComponent],
9+
template: `<app-sample-component [trace]="'sample-component'"></app-sample-component>`,
10+
})
11+
@TraceClassDecorator()
12+
export class ComponentTrackingComponent implements OnInit, AfterViewInit {
13+
@TraceMethodDecorator()
14+
ngOnInit() {}
15+
16+
@TraceMethodDecorator()
17+
ngAfterViewInit() {}
18+
}

dev-packages/e2e-tests/test-applications/angular-17/src/app/home/home.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { RouterLink } from '@angular/router';
1111
<ul>
1212
<li> <a id="navLink" [routerLink]="['/users', '123']">Visit User 123</a> </li>
1313
<li> <a id="redirectLink" [routerLink]="['/redirect1']">Redirect</a> </li>
14+
<li> <a id="cancelLink" [routerLink]="['/cancel']">Cancel</a> </li>
15+
<li> <a id="nonExistentLink" [routerLink]="['/non-existent']">Error</a> </li>
16+
<li> <a id="componentTracking" [routerLink]="['/component-tracking']">Error</a> </li>
1417
</ul>
1518
<button id="errorBtn" (click)="throwError()">Throw error</button>
1619
</main>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component, OnInit } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-sample-component',
5+
standalone: true,
6+
template: `<div></div>`,
7+
})
8+
export class SampleComponent implements OnInit {
9+
ngOnInit() {
10+
console.log('SampleComponent');
11+
}
12+
}

dev-packages/e2e-tests/test-applications/angular-17/tests/performance.test.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect, test } from '@playwright/test';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
23
import { waitForTransaction } from '../event-proxy-server';
34

45
test('sends a pageload transaction with a parameterized URL', async ({ page }) => {
@@ -126,3 +127,178 @@ test('groups redirects within one navigation root span', async ({ page }) => {
126127
expect(routingSpan).toBeDefined();
127128
expect(routingSpan?.description).toBe('/redirect1');
128129
});
130+
131+
test.describe('finish routing span', () => {
132+
test('finishes routing span on navigation cancel', async ({ page }) => {
133+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
134+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
135+
});
136+
137+
await page.goto(`/`);
138+
139+
// immediately navigate to a different route
140+
const [_, navigationTxn] = await Promise.all([page.locator('#cancelLink').click(), navigationTxnPromise]);
141+
142+
expect(navigationTxn).toMatchObject({
143+
contexts: {
144+
trace: {
145+
op: 'navigation',
146+
origin: 'auto.navigation.angular',
147+
},
148+
},
149+
transaction: '/cancel',
150+
transaction_info: {
151+
source: 'url',
152+
},
153+
});
154+
155+
const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
156+
157+
expect(routingSpan).toBeDefined();
158+
expect(routingSpan?.description).toBe('/cancel');
159+
});
160+
161+
test('finishes routing span on navigation error', async ({ page }) => {
162+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
163+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
164+
});
165+
166+
await page.goto(`/`);
167+
168+
// immediately navigate to a different route
169+
const [_, navigationTxn] = await Promise.all([page.locator('#nonExistentLink').click(), navigationTxnPromise]);
170+
171+
const nonExistentRoute = '/non-existent';
172+
173+
expect(navigationTxn).toMatchObject({
174+
contexts: {
175+
trace: {
176+
op: 'navigation',
177+
origin: 'auto.navigation.angular',
178+
},
179+
},
180+
transaction: nonExistentRoute,
181+
transaction_info: {
182+
source: 'url',
183+
},
184+
});
185+
186+
const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
187+
188+
expect(routingSpan).toBeDefined();
189+
expect(routingSpan?.description).toBe(nonExistentRoute);
190+
});
191+
});
192+
193+
test.describe('TraceDirective', () => {
194+
test('creates a child tracingSpan with component name as span name on ngOnInit', async ({ page }) => {
195+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
196+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
197+
});
198+
199+
await page.goto(`/`);
200+
201+
// immediately navigate to a different route
202+
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
203+
204+
const traceDirectiveSpan = navigationTxn.spans?.find(
205+
span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
206+
);
207+
208+
expect(traceDirectiveSpan).toBeDefined();
209+
expect(traceDirectiveSpan).toEqual(
210+
expect.objectContaining({
211+
data: {
212+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
213+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
214+
},
215+
description: '<sample-component>',
216+
op: 'ui.angular.init',
217+
origin: 'auto.ui.angular.trace_directive',
218+
start_timestamp: expect.any(Number),
219+
timestamp: expect.any(Number),
220+
}),
221+
);
222+
});
223+
});
224+
225+
test.describe('TraceClassDecorator', () => {
226+
test('adds init span for decorated class', async ({ page }) => {
227+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
228+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
229+
});
230+
231+
await page.goto(`/`);
232+
233+
// immediately navigate to a different route
234+
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
235+
236+
const classDecoratorSpan = navigationTxn.spans?.find(
237+
span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_class_decorator',
238+
);
239+
240+
expect(classDecoratorSpan).toBeDefined();
241+
expect(classDecoratorSpan).toEqual(
242+
expect.objectContaining({
243+
data: {
244+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
245+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator',
246+
},
247+
// todo: right now, it shows the minified version of the component name - we will add a name input to the Decorator
248+
description: expect.any(String),
249+
op: 'ui.angular.init',
250+
origin: 'auto.ui.angular.trace_class_decorator',
251+
start_timestamp: expect.any(Number),
252+
timestamp: expect.any(Number),
253+
}),
254+
);
255+
});
256+
});
257+
258+
test.describe('TraceMethodDecorator', () => {
259+
test('instruments decorated methods (`ngOnInit` and `ngAfterViewInit`)', async ({ page }) => {
260+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
261+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
262+
});
263+
264+
await page.goto(`/`);
265+
266+
// immediately navigate to a different route
267+
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
268+
269+
const ngInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngOnInit');
270+
const ngAfterViewInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngAfterViewInit');
271+
272+
expect(ngInitSpan).toBeDefined();
273+
expect(ngInitSpan).toEqual(
274+
expect.objectContaining({
275+
data: {
276+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngOnInit',
277+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
278+
},
279+
// todo: right now, it shows the minified version of the component name - we will add a name input to the Decorator
280+
description: expect.any(String),
281+
op: 'ui.angular.ngOnInit',
282+
origin: 'auto.ui.angular.trace_method_decorator',
283+
start_timestamp: expect.any(Number),
284+
timestamp: expect.any(Number),
285+
}),
286+
);
287+
288+
expect(ngAfterViewInitSpan).toBeDefined();
289+
expect(ngAfterViewInitSpan).toEqual(
290+
expect.objectContaining({
291+
data: {
292+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngAfterViewInit',
293+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
294+
},
295+
// todo: right now, it shows the minified version of the component name - we will add a name input to the Decorator
296+
description: expect.any(String),
297+
op: 'ui.angular.ngAfterViewInit',
298+
origin: 'auto.ui.angular.trace_method_decorator',
299+
start_timestamp: expect.any(Number),
300+
timestamp: expect.any(Number),
301+
}),
302+
);
303+
});
304+
});

dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ test('Should record exceptions and transactions for faulty route handlers', asyn
5252
expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server');
5353

5454
expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-error');
55-
expect(routehandlerError.transaction).toBe('PUT /route-handlers/[param]/error');
55+
// TODO: Uncomment once we update the scope transaction name on the server side
56+
// expect(routehandlerError.transaction).toBe('PUT /route-handlers/[param]/error');
5657
});
5758

5859
test.describe('Edge runtime', () => {

dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@sentry/astro": "latest || *",
2020
"@sentry/nextjs": "latest || *",
2121
"@sentry/serverless": "latest || *",
22+
"@sentry/google-cloud": "latest || *",
2223
"@sentry/bun": "latest || *",
2324
"@sentry/types": "latest || *",
2425
"@types/node": "18.15.1",

0 commit comments

Comments
 (0)