Skip to content

Conversation

@nicohrubec
Copy link
Member

@nicohrubec nicohrubec commented Jan 2, 2026

This PR adds a middleware wrapper to the tanstackstart SDK that allows users to add tracing to their application middleware. Eventually we will want to patch this automatically, but that is a bit tricky since it requires build-time magic. This API provides a manual alternative for now and can later still act as a fallback for cases where auto-instrumentation doesn't work.

How it works
The wrapper patches the middleware options.server function that gets executed whenever a middleware is run. Each middleware invocation creates a span with:

  • op: middleware.tanstackstart
  • origin: manual.middleware.tanstackstart
  • name: The instrumentation automatically assigns the middleware name based on the variable name assigned to the middleware.

At first I had the issue that if multiple middlewares were used they would be nested (i.e. first middleware is parent of second etc.). This is because the middlewares call next() to move down the middleware chain, so trivially starting a span for the middleware execution would actually create a span that would last for the current middleware and any middlewares that come after in the middleware chain. I fixed that by also proxying next(), where I end the middleware span and then also reattach the middleware spans to the parent request span instead of the previous middleware span.

Usage

import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';

  const [wrappedAuth, wrappedLogging] = wrapMiddlewaresWithSentry({
    authMiddleware,
    loggingMiddleware,
  });

Tests

Added E2E tests for:

  • if multiple middlewares are executed we get spans for both and they are sibling spans (i.e. children of the same parent)
  • global request middleware
  • global function middleware
  • request middleware

Screenshots from sample app

Using two global request middlewares:

Screenshot 2026-01-05 at 16 19 03

Closes #18666

@nicohrubec nicohrubec marked this pull request as ready for review January 5, 2026 16:07
@nicohrubec nicohrubec changed the title feat(tanstackstart-react): Add wrappers for manual middleware instrumentation feat(tanstackstart-react): Add wrappers for manual instrumentation of servers-side middlewares Jan 8, 2026
@nicohrubec nicohrubec requested a review from s1gr1d January 8, 2026 06:57
@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,576 - 11,856 -28%
GET With Sentry 1,632 19% 2,093 -22%
GET With Sentry (error only) 5,920 69% 7,678 -23%
POST Baseline 1,186 - 1,312 -10%
POST With Sentry 547 46% 671 -18%
POST With Sentry (error only) 1,026 87% 1,172 -12%
MYSQL Baseline 3,204 - 3,612 -11%
MYSQL With Sentry 435 14% 549 -21%
MYSQL With Sentry (error only) 2,606 81% 3,059 -15%

View base workflow run

} catch (e) {
span.end();
throw e;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Span status not set when middleware throws error

Medium Severity

When a middleware throws an error, span.end() is called before re-throwing. Since startSpanManual's error handler only sets SPAN_STATUS_ERROR when span.isRecording() returns true, and isRecording() returns false for ended spans, the error status is never set. This causes middleware error spans to appear successful (with undefined status) instead of properly indicating failure. The span status needs to be set to error before calling span.end() in the catch block.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual middleware tracing

3 participants