Skip to content

Commit 6a35f85

Browse files
authored
feat(frameworks)!: Use function op for framework functions (#23047)
Framework function spans previously each carried a granular, per-framework op (`function.sveltekit.load`, `function.react_router.loader`, `function.nextjs`, `function.remix.document_request`, `function.server_action`, `function.tanstackstart`, …). This unifies them onto the standardized `function` op from conventions The detail that lived in the op now lives in `sentry.origin` and `code.function.name` (the wrapped function's real name), added where the origin alone no longer distinguishes spans. Affected: `sveltekit`, `react-router`, `nextjs`, `remix`, `solidstart`, `tanstackstart-react`. part of #22446
1 parent 4c71b6d commit 6a35f85

48 files changed

Lines changed: 285 additions & 348 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.

dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/server-transactions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test('Sends two linked transactions (server & client) to Sentry', async ({ page
4040
const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
4141
const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
4242
const loaderSpanId = httpServerTransaction?.spans?.find(
43-
span => span.data && span.data['code.function'] === 'loader',
43+
span => span.data && span.data['code.function.name'] === 'loader',
4444
)?.span_id;
4545

4646
const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;

dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/server-transactions.test.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ test('Sends parameterized transaction name to Sentry', async ({ page }) => {
1919

2020
test('Sends form data with action span', async ({ page }) => {
2121
const formdataActionTransaction = waitForTransaction('create-remix-app-express', transactionEvent => {
22-
return transactionEvent?.spans?.some(span => span.data && span.data['code.function'] === 'action') || false;
22+
return transactionEvent?.spans?.some(span => span.data && span.data['code.function.name'] === 'action') || false;
2323
});
2424

2525
await page.goto('/action-formdata');
@@ -34,11 +34,12 @@ test('Sends form data with action span', async ({ page }) => {
3434
await page.locator('button[type=submit]').click();
3535

3636
const actionSpan = (await formdataActionTransaction)?.spans?.find(
37-
span => span.data && span.data['code.function'] === 'action',
37+
span => span.data && span.data['code.function.name'] === 'action',
3838
);
3939

4040
expect(actionSpan).toBeDefined();
41-
expect(actionSpan?.op).toBe('action.remix');
41+
expect(actionSpan?.op).toBe('function');
42+
expect(actionSpan?.data?.['code.function.name']).toBe('action');
4243
expect(actionSpan?.data).toMatchObject({
4344
'formData.text': 'test',
4445
'formData.file': 'file.txt',
@@ -47,17 +48,18 @@ test('Sends form data with action span', async ({ page }) => {
4748

4849
test('Sends a loader span to Sentry', async ({ page }) => {
4950
const loaderTransactionPromise = waitForTransaction('create-remix-app-express', transactionEvent => {
50-
return transactionEvent?.spans?.some(span => span.data && span.data['code.function'] === 'loader') || false;
51+
return transactionEvent?.spans?.some(span => span.data && span.data['code.function.name'] === 'loader') || false;
5152
});
5253

5354
await page.goto('/');
5455

5556
const loaderSpan = (await loaderTransactionPromise)?.spans?.find(
56-
span => span.data && span.data['code.function'] === 'loader',
57+
span => span.data && span.data['code.function.name'] === 'loader',
5758
);
5859

5960
expect(loaderSpan).toBeDefined();
60-
expect(loaderSpan?.op).toBe('loader.remix');
61+
expect(loaderSpan?.op).toBe('function');
62+
expect(loaderSpan?.data?.['code.function.name']).toBe('loader');
6163
});
6264

6365
test('Propagates trace when ErrorBoundary is triggered', async ({ page }) => {
@@ -83,7 +85,7 @@ test('Propagates trace when ErrorBoundary is triggered', async ({ page }) => {
8385
const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
8486
const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
8587
const loaderSpanId = httpServerTransaction?.spans?.find(
86-
span => span.data && span.data['code.function'] === 'loader',
88+
span => span.data && span.data['code.function.name'] === 'loader',
8789
)?.span_id;
8890

8991
const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
@@ -111,7 +113,7 @@ test('Parameterizes a 2-level nested route on the server', async ({ page }) => {
111113
const transaction = await transactionPromise;
112114

113115
expect(transaction.contexts?.trace?.data?.['sentry.source']).toBe('route');
114-
expect(transaction.spans?.some(s => s.data?.['code.function'] === 'loader' && s.op === 'loader.remix')).toBe(true);
116+
expect(transaction.spans?.some(s => s.data?.['code.function.name'] === 'loader' && s.op === 'function')).toBe(true);
115117
});
116118

117119
test('Parameterizes a 3-level nested API route on the server', async ({ page }) => {
@@ -160,19 +162,21 @@ test('Records action and loader spans on a parameterized action route', async ({
160162
const transaction = await transactionPromise;
161163

162164
const actionSpan = transaction.spans?.find(
163-
s => s.data?.['code.function'] === 'action' && s.data?.['match.route.id'] === 'routes/action-json-response.$id',
165+
s =>
166+
s.data?.['code.function.name'] === 'action' && s.data?.['match.route.id'] === 'routes/action-json-response.$id',
164167
);
165168
expect(actionSpan).toBeDefined();
166-
expect(actionSpan?.op).toBe('action.remix');
169+
expect(actionSpan?.op).toBe('function');
167170
expect(actionSpan?.data?.['match.params.id']).toBe('123123');
168171

169172
const rootLoaderSpan = transaction.spans?.find(
170-
s => s.data?.['code.function'] === 'loader' && s.data?.['match.route.id'] === 'root',
173+
s => s.data?.['code.function.name'] === 'loader' && s.data?.['match.route.id'] === 'root',
171174
);
172175
expect(rootLoaderSpan).toBeDefined();
173176

174177
const routeLoaderSpan = transaction.spans?.find(
175-
s => s.data?.['code.function'] === 'loader' && s.data?.['match.route.id'] === 'routes/action-json-response.$id',
178+
s =>
179+
s.data?.['code.function.name'] === 'loader' && s.data?.['match.route.id'] === 'routes/action-json-response.$id',
176180
);
177181
expect(routeLoaderSpan).toBeDefined();
178182

@@ -191,7 +195,9 @@ test('Records loader spans on a deferred loader response', async ({ page }) => {
191195
expect(transaction.contexts?.trace?.data?.['sentry.source']).toBe('route');
192196
expect(
193197
transaction.spans?.some(
194-
s => s.data?.['code.function'] === 'loader' && s.data?.['match.route.id'] === 'routes/loader-defer-response.$id',
198+
s =>
199+
s.data?.['code.function.name'] === 'loader' &&
200+
s.data?.['match.route.id'] === 'routes/loader-defer-response.$id',
195201
),
196202
).toBe(true);
197203
});
@@ -263,7 +269,9 @@ test('Sends two linked transactions (server & client) to Sentry', async ({ page
263269
const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
264270
const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
265271

266-
const loaderSpan = httpServerTransaction?.spans?.find(span => span.data && span.data['code.function'] === 'loader');
272+
const loaderSpan = httpServerTransaction?.spans?.find(
273+
span => span.data && span.data['code.function.name'] === 'loader',
274+
);
267275
const loaderSpanId = loaderSpan?.span_id;
268276
const loaderParentSpanId = loaderSpan?.parent_span_id;
269277

dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/tests/server-transactions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ test('Sends two linked transactions (server & client) to Sentry', async ({ page
3939
const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
4040
const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
4141
const loaderSpanId = httpServerTransaction?.spans?.find(
42-
span => span.data && span.data['code.function'] === 'loader',
42+
span => span.data && span.data['code.function.name'] === 'loader',
4343
)?.span_id;
4444

4545
const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;

dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/server-transactions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ test('Sends two linked transactions (server & client) to Sentry', async ({ page
3939
const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
4040
const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
4141
const loaderSpanId = httpServerTransaction?.spans?.find(
42-
span => span.data && span.data['code.function'] === 'loader',
42+
span => span.data && span.data['code.function.name'] === 'loader',
4343
)?.span_id;
4444

4545
const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;

dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ test('Should set a "not_found" status on a server component span when notFound()
8383
expect(transactionEvent.spans).toContainEqual(
8484
expect.objectContaining({
8585
description: 'resolve page server component "/server-component/not-found"',
86-
op: 'function.nextjs',
86+
op: 'function',
8787
data: expect.objectContaining({
8888
'sentry.nextjs.ssr.function.type': 'Page',
8989
'sentry.nextjs.ssr.function.route': '/server-component/not-found',
@@ -122,7 +122,7 @@ test('Should capture an error and transaction for a app router page', async ({ p
122122
expect(transactionEvent.spans).toContainEqual(
123123
expect.objectContaining({
124124
description: 'resolve page server component "/server-component/faulty"',
125-
op: 'function.nextjs',
125+
op: 'function',
126126
data: expect.objectContaining({
127127
'sentry.nextjs.ssr.function.type': 'Page',
128128
'sentry.nextjs.ssr.function.route': '/server-component/faulty',

dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/errors/errors.server.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,15 @@ test.describe('server - instrumentation API error capture', () => {
5959
const transaction = await txPromise;
6060

6161
// Find the loader span
62-
const loaderSpan = transaction?.spans?.find(
63-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
64-
);
62+
const loaderSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'loader');
6563

6664
expect(loaderSpan).toMatchObject({
6765
data: {
6866
'sentry.origin': 'auto.function.react_router.instrumentation_api',
69-
'sentry.op': 'function.react_router.loader',
67+
'sentry.op': 'function',
68+
'code.function.name': 'loader',
7069
},
71-
op: 'function.react_router.loader',
70+
op: 'function',
7271
});
7372
});
7473

dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/fetcher.client.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { waitForTransaction } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
// As of React Router 7.15+, HydratedRouter invokes the client `fetch` hook in Framework Mode.
6-
// A fetcher submission produces a `function.react_router.fetcher` transaction
7-
// (origin `auto.function.react_router.instrumentation_api`) that nests the client action/loader
8-
// spans and the `http.client` spans for the underlying `.data` requests.
6+
// A fetcher submission produces a `function` transaction (origin
7+
// `auto.function.react_router.instrumentation_api`, `code.function.name` `fetcher`) that nests the
8+
// client action/loader spans and the `http.client` spans for the underlying `.data` requests.
99
// See: https://github.com/remix-run/react-router/discussions/13749
1010

1111
test.describe('client - instrumentation API fetcher', () => {
@@ -20,7 +20,7 @@ test.describe('client - instrumentation API fetcher', () => {
2020
});
2121

2222
const fetcherTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
23-
return transactionEvent.contexts?.trace?.op === 'function.react_router.fetcher';
23+
return transactionEvent.contexts?.trace?.data?.['code.function.name'] === 'fetcher';
2424
});
2525

2626
await page.goto(`/performance/fetcher-test`);
@@ -35,9 +35,9 @@ test.describe('client - instrumentation API fetcher', () => {
3535
// The fetcher transaction nests the client action span and the http.client span(s) for the
3636
// underlying `.data` request(s) - i.e. the OTel/browser fetch span is parented by the fetcher
3737
// span, not emitted standalone.
38-
const spanOps = (fetcherTx.spans ?? []).map(span => span.op);
39-
expect(spanOps).toContain('function.react_router.client_action');
40-
expect(spanOps).toContain('http.client');
38+
const spans = fetcherTx.spans ?? [];
39+
expect(spans.some(span => span.data?.['code.function.name'] === 'clientAction')).toBe(true);
40+
expect(spans.map(span => span.op)).toContain('http.client');
4141
});
4242

4343
test('should still send server action transaction when fetcher submits', async ({ page }) => {

dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/lazy.server.test.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,21 @@ test.describe('server - instrumentation API lazy loading', () => {
4141
});
4242

4343
// Find the lazy span
44-
const lazySpan = transaction?.spans?.find(
45-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.lazy',
46-
);
44+
const lazySpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'lazy');
4745

4846
expect(lazySpan).toMatchObject({
4947
span_id: expect.any(String),
5048
trace_id: expect.any(String),
5149
data: {
5250
'sentry.origin': 'auto.function.react_router.instrumentation_api',
53-
'sentry.op': 'function.react_router.lazy',
51+
'sentry.op': 'function',
52+
'code.function.name': 'lazy',
5453
},
5554
description: 'Lazy Route Load',
5655
parent_span_id: expect.any(String),
5756
start_timestamp: expect.any(Number),
5857
timestamp: expect.any(Number),
59-
op: 'function.react_router.lazy',
58+
op: 'function',
6059
origin: 'auto.function.react_router.instrumentation_api',
6160
});
6261
});
@@ -71,19 +70,18 @@ test.describe('server - instrumentation API lazy loading', () => {
7170
const transaction = await txPromise;
7271

7372
// Find the loader span that runs after lazy loading
74-
const loaderSpan = transaction?.spans?.find(
75-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
76-
);
73+
const loaderSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'loader');
7774

7875
expect(loaderSpan).toMatchObject({
7976
span_id: expect.any(String),
8077
trace_id: expect.any(String),
8178
data: {
8279
'sentry.origin': 'auto.function.react_router.instrumentation_api',
83-
'sentry.op': 'function.react_router.loader',
80+
'sentry.op': 'function',
81+
'code.function.name': 'loader',
8482
},
8583
description: '/performance/lazy-route',
86-
op: 'function.react_router.loader',
84+
op: 'function',
8785
origin: 'auto.function.react_router.instrumentation_api',
8886
});
8987
});
@@ -97,13 +95,9 @@ test.describe('server - instrumentation API lazy loading', () => {
9795

9896
const transaction = await txPromise;
9997

100-
const lazySpan = transaction?.spans?.find(
101-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.lazy',
102-
);
98+
const lazySpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'lazy');
10399

104-
const loaderSpan = transaction?.spans?.find(
105-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
106-
);
100+
const loaderSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'loader');
107101

108102
expect(lazySpan).toBeDefined();
109103
expect(loaderSpan).toBeDefined();

dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/middleware.server.test.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,24 @@ test.describe('server - instrumentation API middleware', () => {
3737
});
3838

3939
// Find the middleware span
40-
const middlewareSpan = transaction?.spans?.find(
41-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.middleware',
42-
);
40+
const middlewareSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'middleware');
4341

4442
expect(middlewareSpan).toBeDefined();
4543
expect(middlewareSpan).toMatchObject({
4644
span_id: expect.any(String),
4745
trace_id: expect.any(String),
4846
data: expect.objectContaining({
4947
'sentry.origin': 'auto.function.react_router.instrumentation_api',
50-
'sentry.op': 'function.react_router.middleware',
48+
'sentry.op': 'middleware',
49+
'code.function.name': 'middleware',
5150
'react_router.route.id': 'routes/performance/with-middleware',
5251
'http.route': '/performance/with-middleware',
5352
'react_router.middleware.index': 0,
5453
}),
5554
parent_span_id: expect.any(String),
5655
start_timestamp: expect.any(Number),
5756
timestamp: expect.any(Number),
58-
op: 'function.react_router.middleware',
57+
op: 'middleware',
5958
origin: 'auto.function.react_router.instrumentation_api',
6059
});
6160

@@ -73,13 +72,9 @@ test.describe('server - instrumentation API middleware', () => {
7372

7473
const transaction = await txPromise;
7574

76-
const middlewareSpan = transaction?.spans?.find(
77-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.middleware',
78-
);
75+
const middlewareSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'middleware');
7976

80-
const loaderSpan = transaction?.spans?.find(
81-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
82-
);
77+
const loaderSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'loader');
8378

8479
expect(middlewareSpan).toBeDefined();
8580
expect(loaderSpan).toBeDefined();
@@ -100,9 +95,7 @@ test.describe('server - instrumentation API middleware', () => {
10095
await expect(page.locator('#multi-middleware-title')).toBeVisible();
10196
await expect(page.locator('#multi-middleware-content')).toHaveText('This route has 3 middlewares');
10297

103-
const middlewareSpans = transaction?.spans?.filter(
104-
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.middleware',
105-
);
98+
const middlewareSpans = transaction?.spans?.filter(span => span.data?.['code.function.name'] === 'middleware');
10699

107100
expect(middlewareSpans).toHaveLength(3);
108101

dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/performance.server.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,21 +103,22 @@ test.describe('server - instrumentation API performance', () => {
103103
const transaction = await txPromise;
104104

105105
// Find the loader span
106-
const loaderSpan = transaction?.spans?.find(span => span.data?.['sentry.op'] === 'function.react_router.loader');
106+
const loaderSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'loader');
107107

108108
expect(loaderSpan).toMatchObject({
109109
span_id: expect.any(String),
110110
trace_id: expect.any(String),
111111
data: {
112112
'sentry.origin': 'auto.function.react_router.instrumentation_api',
113-
'sentry.op': 'function.react_router.loader',
113+
'sentry.op': 'function',
114+
'code.function.name': 'loader',
114115
},
115116
description: '/performance/server-loader',
116117
parent_span_id: expect.any(String),
117118
start_timestamp: expect.any(Number),
118119
timestamp: expect.any(Number),
119120
status: 'ok',
120-
op: 'function.react_router.loader',
121+
op: 'function',
121122
origin: 'auto.function.react_router.instrumentation_api',
122123
});
123124
});
@@ -133,21 +134,22 @@ test.describe('server - instrumentation API performance', () => {
133134
const transaction = await txPromise;
134135

135136
// Find the action span
136-
const actionSpan = transaction?.spans?.find(span => span.data?.['sentry.op'] === 'function.react_router.action');
137+
const actionSpan = transaction?.spans?.find(span => span.data?.['code.function.name'] === 'action');
137138

138139
expect(actionSpan).toMatchObject({
139140
span_id: expect.any(String),
140141
trace_id: expect.any(String),
141142
data: {
142143
'sentry.origin': 'auto.function.react_router.instrumentation_api',
143-
'sentry.op': 'function.react_router.action',
144+
'sentry.op': 'function',
145+
'code.function.name': 'action',
144146
},
145147
description: '/performance/server-action',
146148
parent_span_id: expect.any(String),
147149
start_timestamp: expect.any(Number),
148150
timestamp: expect.any(Number),
149151
status: 'ok',
150-
op: 'function.react_router.action',
152+
op: 'function',
151153
origin: 'auto.function.react_router.instrumentation_api',
152154
});
153155
});

0 commit comments

Comments
 (0)