Skip to content

Commit b6cd79a

Browse files
Lms24claude
andcommitted
fix(tanstackstart-react): Set code.function.name in both trace lifecycles
The span starts with a `serverFn` placeholder because the function's name is not known until the global function middleware runs. That middleware only replaced the placeholder under span streaming, so static-lifecycle spans kept `serverFn` even though the real name was available. Resolve the attribute in both lifecycles and gate only `sentry.description`, which exists solely to carry the request path past Relay's streaming description template. This matches the ember and sveltekit `function` spans, which both set `code.function.name` unconditionally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 9c0d10e commit b6cd79a

2 files changed

Lines changed: 9 additions & 11 deletions

File tree

‎packages/tanstackstart-react/src/server/globalMiddleware.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,10 @@ function createSentryFunctionMiddlewareHandler(mechanismType: string) {
5656

5757
// With span streaming, a `function` span is named after the function it wraps.
5858
updateSpanName(activeSpan, hasSpanStreaming ? serverFnMeta.name : description);
59+
activeSpan.setAttribute(CODE_FUNCTION_NAME, serverFnMeta.name);
5960
if (hasSpanStreaming) {
6061
// Relay infers a `function` span's description from `code.function.name` alone.
61-
activeSpan.setAttributes({
62-
[CODE_FUNCTION_NAME]: serverFnMeta.name,
63-
[SENTRY_DESCRIPTION]: description,
64-
});
62+
activeSpan.setAttribute(SENTRY_DESCRIPTION, description);
6563
}
6664
activeSpan.setAttribute(SENTRY_SEGMENT_NAME_SOURCE, 'route');
6765
}

‎packages/tanstackstart-react/test/server/globalMiddleware.test.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ describe('sentryGlobalFunctionMiddleware', () => {
7979
});
8080

8181
describe('server function span naming', () => {
82-
const setUpActiveSpan = (): { setAttribute: ReturnType<typeof vi.fn>; setAttributes: ReturnType<typeof vi.fn> } => {
83-
const span = { setAttribute: vi.fn(), setAttributes: vi.fn() };
82+
const setUpActiveSpan = (): { setAttribute: ReturnType<typeof vi.fn> } => {
83+
const span = { setAttribute: vi.fn() };
8484
getActiveSpanSpy.mockReturnValue(span);
8585
spanToJSONSpy.mockReturnValue({
8686
name: 'GET /_serverFn/abc123',
@@ -102,10 +102,8 @@ describe('sentryGlobalFunctionMiddleware', () => {
102102
await serverFn({ next: vi.fn().mockResolvedValue('ok'), serverFnMeta: { name: 'testLog' } });
103103

104104
expect(updateSpanNameSpy).toHaveBeenCalledWith(span, 'testLog');
105-
expect(span.setAttributes).toHaveBeenCalledWith({
106-
'code.function.name': 'testLog',
107-
'sentry.description': 'GET /_serverFn/testLog',
108-
});
105+
expect(span.setAttribute).toHaveBeenCalledWith('code.function.name', 'testLog');
106+
expect(span.setAttribute).toHaveBeenCalledWith('sentry.description', 'GET /_serverFn/testLog');
109107
});
110108

111109
it('keeps the request path in the span name without span streaming', async () => {
@@ -116,7 +114,9 @@ describe('sentryGlobalFunctionMiddleware', () => {
116114
await serverFn({ next: vi.fn().mockResolvedValue('ok'), serverFnMeta: { name: 'testLog' } });
117115

118116
expect(updateSpanNameSpy).toHaveBeenCalledWith(span, 'GET /_serverFn/testLog');
119-
expect(span.setAttributes).not.toHaveBeenCalled();
117+
// The resolved function name is useful in both lifecycles; only the description is a streaming workaround.
118+
expect(span.setAttribute).toHaveBeenCalledWith('code.function.name', 'testLog');
119+
expect(span.setAttribute).not.toHaveBeenCalledWith('sentry.description', expect.anything());
120120
});
121121
});
122122
});

0 commit comments

Comments
 (0)