Skip to content

Commit 9f14001

Browse files
committed
Revert "feat(node): Name Mastra server routes from their route pattern"
This reverts commit 06e931c.
1 parent 06e931c commit 9f14001

7 files changed

Lines changed: 28 additions & 366 deletions

File tree

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Work in this release was contributed by @psh4607, @thijsw, @trinitiwowka, @nehap
1616
- `DenoMysql` => `Mysql`
1717
- `DenoPostgres` => `Postgres`
1818
- feat(node): Add first-party Mastra integration ([#23823](https://github.com/getsentry/sentry-javascript/pull/23823)). Enabled by default; disable with `defaultIntegrations: integrations => integrations.filter(i => i.name !== 'Mastra')`.
19-
- feat(node): Name Mastra server routes from their route pattern. The Mastra integration now names incoming requests' `http.server` span after the matched Hono route (e.g. `POST /api/agents/:agentId/generate`, `GET /echo/:id`) with `http.route` set and name source `route`, instead of the raw URL — for both built-in API routes and custom `registerApiRoute`s. This keeps route transactions low-cardinality. Disable with `mastraIntegration({ instrumentServerRoutes: false })`.
2019
- feat(node): Enable the `dataloader` and `knex` integrations by default. Both were previously opt-in — `dataloader` was removed from the defaults in v8 due to an upstream OpenTelemetry bug that has since been fixed, and `knex` was never enabled by default. You no longer need to add `dataloaderIntegration()` or `knexIntegration()` manually. Disable either with `defaultIntegrations: integrations => integrations.filter(i => i.name !== 'Dataloader' /* or 'Knex' */)`.
2120
- **feat(browser): Add `bfcacheMetricsIntegration` to track back/forward cache health**
2221

dev-packages/e2e-tests/test-applications/node-mastra/src/mastra/index.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,22 @@ import { WEATHER_AGENT, weatherAgent } from './agents/weather-agent.js';
77
// endpoint (see tests/utils.ts). `dataloader` (orchestrion-instrumented) is now
88
// exercised through the agent's `count_items` tool (see src/mastra/tools/count-items.ts).
99

10-
// A plain (static) custom route: verifies requests to app-registered routes are wrapped in an
11-
// `http.server` span. With the Mastra integration's route naming, the span is named `GET /manual-route`
12-
// (name source `route`, `http.route` set) — see tests/manual-route.test.ts.
10+
// A plain custom route to verify that requests to app-registered routes are
11+
// wrapped in an `http.server` span with the correct route attributes (method,
12+
// route pattern, status code) — independent of the agent/AI instrumentation.
1313
const manualRoute = registerApiRoute('/manual-route', {
1414
method: 'GET',
1515
handler(c) {
1616
return c.json({ ok: true });
1717
},
1818
});
1919

20-
// A parametrized custom route: verifies the integration names the `http.server` span from the matched
21-
// route *pattern* (`GET /echo/:id`), not the raw URL — so `/echo/42` and `/echo/99` collapse to one
22-
// low-cardinality transaction with `http.route: /echo/:id`.
23-
const echoRoute = registerApiRoute('/echo/:id', {
24-
method: 'GET',
25-
handler(c) {
26-
return c.json({ id: c.req.param('id') });
27-
},
28-
});
29-
3020
export const mastra = new Mastra({
3121
agents: { [WEATHER_AGENT]: weatherAgent },
3222
storage: new LibSQLStore({ id: 'mastra-storage', url: ':memory:' }),
3323
server: {
3424
port: 4111,
35-
apiRoutes: [manualRoute, echoRoute],
25+
apiRoutes: [manualRoute],
3626
},
3727
});
3828

@@ -42,4 +32,5 @@ export const mastra = new Mastra({
4232
*
4333
1. Bubbled-up tool errors aren't captured as issues — only reflected on the span (status + error.type); the exporter leaves captureException to the app.
4434
2. Mastra runs tools with inactive spans, so the `count_items` tool must open its own active span (startSpan) for dataloader's `cache.get` span to emit.
35+
3. parametrized routes for mastra?
4536
*/

dev-packages/e2e-tests/test-applications/node-mastra/tests/manual-route.test.ts

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,35 @@ const APP = 'node-mastra';
55

66
const attrValue = (span: SerializedStreamedSpan, key: string): unknown => span.attributes?.[key]?.value;
77

8-
// Custom routes registered on the Mastra (Hono) server — see `src/mastra/index.ts`. These verify the
9-
// Mastra integration's zero-config route naming: incoming requests get an `http.server` span named
10-
// from the matched route *pattern* (name source `route`, `http.route` set), for both static and
11-
// parametrized routes, with no app-level middleware. Runs in both the prod (`mastra start`) and dev
12-
// (`mastra dev`) variants.
13-
const serverSpanForUrl =
14-
(urlIncludes: string) =>
15-
(span: SerializedStreamedSpan): boolean =>
16-
getSpanOp(span) === 'http.server' && String(attrValue(span, 'url.full') ?? '').includes(urlIncludes);
17-
18-
test('names a static custom route from its route pattern', async ({ baseURL }) => {
19-
const spansPromise = collectStreamedSpans(APP, spansOfTrace => spansOfTrace.some(serverSpanForUrl('/manual-route')));
8+
// A plain custom route registered on the Mastra (Hono) server — see the
9+
// `/manual-route` handler in src/mastra/index.ts. This verifies that ordinary HTTP
10+
// requests to app-registered routes are wrapped in an `http.server` span with the
11+
// expected attributes, independent of the agent / AI instrumentation. Runs in both
12+
// the prod (`mastra start`) and dev (`mastra dev`) variants.
13+
const isManualRouteServerSpan = (span: SerializedStreamedSpan): boolean =>
14+
getSpanOp(span) === 'http.server' && String(attrValue(span, 'url.full') ?? '').includes('/manual-route');
15+
16+
test('wraps a custom Mastra route in an http.server span with correct attributes', async ({ baseURL }) => {
17+
const spansPromise = collectStreamedSpans(APP, spansOfTrace => spansOfTrace.some(isManualRouteServerSpan));
2018

2119
const res = await fetch(`${baseURL}/manual-route`, { method: 'GET' });
2220
expect(res.status).toBe(200);
2321
await res.json();
2422

25-
const serverSpan = (await spansPromise).find(serverSpanForUrl('/manual-route'));
23+
const spans = await spansPromise;
24+
const serverSpan = spans.find(isManualRouteServerSpan);
2625

2726
expect(serverSpan).toBeDefined();
2827
expect(getSpanOp(serverSpan!)).toBe('http.server');
28+
expect(serverSpan!.name).toBe('GET');
2929
expect(attrValue(serverSpan!, 'http.request.method')).toBe('GET');
3030
expect(attrValue(serverSpan!, 'http.response.status_code')).toBe(200);
31-
32-
// The Mastra integration upgrades the span from the raw URL to the route pattern: for a static
33-
// route these are identical, but the name source is `route` and `http.route` is set.
34-
expect(serverSpan!.name).toBe('GET /manual-route');
35-
expect(attrValue(serverSpan!, 'http.route')).toBe('/manual-route');
36-
expect(attrValue(serverSpan!, 'sentry.segment.name.source')).toBe('route');
37-
});
38-
39-
test('names a parametrized custom route from its route pattern (low cardinality)', async ({ baseURL }) => {
40-
// Two different ids must collapse to the same `/echo/:id` transaction.
41-
for (const id of ['42', '99']) {
42-
const spansPromise = collectStreamedSpans(APP, spansOfTrace =>
43-
spansOfTrace.some(span => serverSpanForUrl(`/echo/${id}`)(span)),
44-
);
45-
46-
const res = await fetch(`${baseURL}/echo/${id}`, { method: 'GET' });
47-
expect(res.status).toBe(200);
48-
await res.json();
49-
50-
const serverSpan = (await spansPromise).find(serverSpanForUrl(`/echo/${id}`));
51-
52-
expect(serverSpan).toBeDefined();
53-
// Name and route are the pattern, not the concrete URL (`/echo/42`).
54-
expect(serverSpan!.name).toBe('GET /echo/:id');
55-
expect(attrValue(serverSpan!, 'http.route')).toBe('/echo/:id');
56-
expect(attrValue(serverSpan!, 'sentry.segment.name.source')).toBe('route');
57-
}
31+
expect(String(attrValue(serverSpan!, 'url.full') ?? '')).toContain('/manual-route');
32+
33+
// Codifies current behavior: the transaction name is derived from the URL path,
34+
// not a route pattern. Mastra serves custom routes through Hono, which Sentry
35+
// does not route-instrument the way it does Express — so there is no `http.route`
36+
// attribute and the name source is `url` (an Express route would give `route`).
37+
expect(attrValue(serverSpan!, 'sentry.segment.name.source')).toBe('url');
38+
expect(attrValue(serverSpan!, 'http.route')).toBeUndefined();
5839
});

packages/server-utils/src/integrations/mastra-route-naming.ts

Lines changed: 0 additions & 122 deletions
This file was deleted.

packages/server-utils/src/integrations/mastra.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,13 @@ import { CHANNELS } from '../orchestrion/channels';
1717
import { mastraModuleNames } from '../orchestrion/config/mastra';
1818
import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation';
1919
import { safeChannelCallback } from '../tracing-channel';
20-
import { injectMastraRouteNamingMiddleware } from './mastra-route-naming';
2120

2221
export interface MastraOptions extends MastraExporterOptions {
2322
/**
2423
* Construct a Mastra observability pipeline when the app has not configured one. Defaults to
2524
* `true`. Uses an `@mastra/observability` the app already has; the SDK never installs it.
2625
*/
2726
bootstrapObservability?: boolean;
28-
29-
/**
30-
* Give the incoming `http.server` span the matched route pattern (e.g.
31-
* `POST /api/agents/:agentId/generate`) with `http.route` and name source `route`, instead of the
32-
* raw URL. Works for Mastra's built-in API routes and custom `registerApiRoute`s. Defaults to
33-
* `true`. Set `false` to leave route naming to the app.
34-
*/
35-
instrumentServerRoutes?: boolean;
3627
}
3728

3829
interface MastraObservabilityInstance {
@@ -69,20 +60,7 @@ const _mastraIntegration = ((options: MastraOptions = {}) => {
6960
}) satisfies IntegrationFn;
7061

7162
function instrumentMastra(options: MastraOptions): void {
72-
const channel = diagnosticsChannel.tracingChannel<ConstructorChannelContext>(CHANNELS.MASTRA_CONSTRUCTOR);
73-
74-
// `start` fires before the constructor body reads `config.server`, so mutating the config here adds
75-
// our route-naming middleware to the Hono server without needing a reference to the (internal) app.
76-
if (options.instrumentServerRoutes !== false) {
77-
channel.start.subscribe(message => {
78-
safeChannelCallback(() => {
79-
const { arguments: constructorArgs } = message as ConstructorChannelContext;
80-
injectMastraRouteNamingMiddleware(constructorArgs?.[0]);
81-
});
82-
});
83-
}
84-
85-
channel.end.subscribe(message => {
63+
diagnosticsChannel.tracingChannel<ConstructorChannelContext>(CHANNELS.MASTRA_CONSTRUCTOR).end.subscribe(message => {
8664
safeChannelCallback(() => {
8765
const { self } = message as ConstructorChannelContext;
8866
attachExporter(self, options);
@@ -102,11 +80,7 @@ function attachExporter(instance: unknown, options: MastraOptions): void {
10280
return;
10381
}
10482

105-
const {
106-
bootstrapObservability: _bootstrapObservability,
107-
instrumentServerRoutes: _instrumentServerRoutes,
108-
...exporterOptions
109-
} = options;
83+
const { bootstrapObservability: _bootstrapObservability, ...exporterOptions } = options;
11084
const exporter = new SentryMastraExporter(exporterOptions);
11185

11286
const defaultInstance = mastra.observability?.getDefaultInstance?.();

packages/server-utils/test/integrations/mastra/route-naming-optout.test.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)