[@sentry/tracing] Nested express custom middlewares detection
See original GitHub issuePackage + Version
@sentry/tracing
5.29.2
Description
At first I had the problem described in #2968 but I saw #2972 and added the methods I wanted to log:
new Tracing.Integrations.Express({ app, methods: ['get', 'post'] }),
This works great, except that it doesn’t seem to handle nested middlewares.
I checked the code source and indeed it seems the wrapping only applies to the app
or router
parameter. https://github.com/getsentry/sentry-javascript/blob/0ddd74f03035c9b4cbbaf8cdd5e7e77fbd7fda46/packages/tracing/src/integrations/express.ts#L201
That means considering a code like this:
// index.ts
const app = express();
Sentry.init({
dsn: "...",
integrations: [
new Tracing.Integrations.Express({ app, methods: ["get"] }),
],
tracesSampleRate: 1.0,
});
app.get("/test", someHandler); // first endpoint
app.use("/api", require("./api")); // nested router
// api.ts
const router = express.Router();
router.get("/users", someHandler); // second endpoint
export default router;
- If we call
/test
we’ll indeed getmiddleware.get
corresponding to our first endpoint in the list of spans. - If we call
/api/users
we’ll only getmiddleware.use router
corresponding to the nested router, but the second endpoint will not appear at all in the list of spans.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:7
- Comments:7 (3 by maintainers)
Top Results From Across the Web
node.js - How does one implement Sentry Performance ...
I have poked around @sentry/tracing code and there seems to be an Express integration available. Based on the integration source code, it looks ......
Read more >Express.js Middleware Can Be Arbitrarily Nested Within A ...
Ben Nadel demonstrates that middleware can be arbitrarily nested within an Express.js route definition in Node.js. This is because Express ...
Read more >Using middleware - Express.js
Bind application-level middleware to an instance of the app object by using the app.use() and app.METHOD() functions, where METHOD is the HTTP method...
Read more >Express - Sentry Documentation
Learn about using Sentry with Express. ... The request handler must be the first middleware on the app app.use(Sentry.Handlers.requestHandler()) ...
Read more >@sentry/tracing | Yarn - Package Manager
[react] feat: Expose eventId on ErrorBoundary component (#2704); [node] fix: Extract transaction from nested express paths correctly (#2714) ...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Hi, all. I took some time to look into this, and unfortunately, Kamil is right - without a near-total rewrite of the Express integration, there’s no good way to solve this problem. I think I might have a hack which could solve part of it (the labeling of transactions, accounting for the substitution problem Kamil mentioned), and it’s conceivable a similar method could work to solve the original issue mentioned here (that of missing spans), but to really make it work, we’ll have to adopt a different approach. That’s not off the table, but moves this out of the category of “small bug fix” and into the category of “work that has to be planned for,” which is why it hasn’t happened yet, unfortunately.
A little more detail, in case it’s helpful:
The first things to know are:
.use()
,.get()
, and the like, which are at their heart registration methods - “when there’s aGET
request to/xyz
, please run this handler” - and which therefore are only run once, at app startup.The root cause of the problem is that by the time the SDK comes along to do its wrapping, all but the top-level routers have already registered their handlers: as soon as you import a router, its code runs and all of the sub-routers get registered. But of course, you have to import the router in order to be able to give it to the SDK to wrap, so by definition that will happen before the SDK can do its thing. Only calls to
.use()
,.get()
, etc which occur afterSentry.init()
get wrapped, so the upshot is that only routers the top-most level of the router tree can either create spans or provide a parameterized version of their part of the path. (Further, at the moment, the integration only accepts a single router, the root of the tree, and so nothing below that top level could be wrapped in any case.) The only way to get around this would be to modify the integration to accept multiple routers, and then to create and configure all routers in the same file as theSentry.init()
call, making sure that all.use()
,.get()
, etc calls happen once the SDK is already running. Gross, and also unrealistic.I spent more time than I care to admit stepping through Express routing code, and I think there may be a spot we could hook into, as the parameters are being processed, which would let us reconstruct each part of the parameterized path as it’s matched. (It’s similar to the substitution Kamil mentioned, but with the ability to disambiguate identical parameter names, as well as parameter values which match hard-coded parts of the path.) I haven’t had a chance to fully test it out, though, nor to see if similar hackery could be employed for span creation. Ultimately, I think our better bet will be to continue to accept only the top-level router, but to then actually walk the router tree after it’s fully formed.
I know this doesn’t solve the problem, but hopefully it at least gives you context for why it hasn’t been solved yet. I’ll update here as anything changes.
@adamup928 agreed, I’ll make sure that this issue gets addressed as soon as possible.