-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ref(node): Improve Express URL Parameterization (#5450)
Improve the URL parameterization for transaction names in our Express integration. Previously, we would only obtain a parameterized version of the incoming request's URL **after** the registered handler(s) finished their job, right before we called `transaction.finish()`. This is definitely too late for DSC propagation, if the handlers made any outgoing request. In that case, the DSC (propagated via the `baggage` header) would not contain a transaction name. As of this PR, we patch the `Express.Router` prototype, more precisely the `process_params` function. This function contains a reference to the incoming `req` object as well as to the `layer` that matched the route. We hook into the function to extract the matched and parameterized partial route of the layer and we attach it to the `req` object. This happens for every matched layer, until the entire route is resolved, in which stichtched together the entire parameterized route. For the time being, we deliberately ignore route registrations with wildcards (e.g. `app.get('/*', ...)`) when stitching together the parameterized route. After we added a new part to the reconstructed route, we check if it has the same number of segments as the original (raw) URL. In case it has, we assume that the parameterized route is complete and we update the active transaction with the parameterized name. Additionally, we set the transaction source to `route` at this point. In case we never get to the point where we have an equal amount of URL segments, the transaction name is never updated, meaning its source stays `url` (and therefore, it's not added to the DSC). In that case, we continue to update the transaction name like before right before the end of the transaction. After reading the Express source code, we confirmed that the process for resolving parts of a route and handling it is performed sequentially **for each matched route segment**. This means that each matched layer's handle function is invoked before the next part of the URL is matched. We therefore still have timing issues around DSC population if any of those intermediate handler functions make outgoing requests. A simple example: The incoming request is `/api/users/123` * We intercept the request and start the transaction with the name `/api/users/123` and source `url` * The first layer that matches is matches the route `/*`. * We start reconstructing the parameterized route with `/` * The handle function of this layer is invoked (e.g. to check authentication) * the handler makes an XHR request * at this point, we populate and freeze the DSC (without transaction name) * The second handler matches the route `/api/users/123` * We obtain the parameterized route and our reconstructed route is now `/api/users/:id` * Now we have 3 segments in the original and in the reconstructed route * We update the transaction name with `/api/users/:id` and set its source to `route` * The handle function of this layer is invoked * every request that might happen here will still propagate the DSC from layer 1 because it can't be modified * We finish the transaction This example shows that we still have timing issues w.r.t DSC propagation. That is, assuming that the Node SDK is in this case the head of the trace and it didn't intercept incoming DSC from the incoming request. However, with this PR we now at least have the chance to get the correct transaction name early enough. ref: #5342
- Loading branch information
Showing
9 changed files
with
292 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...de-integration-tests/suites/express/sentry-trace/baggage-header-out-bad-tx-name/server.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as Sentry from '@sentry/node'; | ||
import * as Tracing from '@sentry/tracing'; | ||
import cors from 'cors'; | ||
import express from 'express'; | ||
import http from 'http'; | ||
|
||
const app = express(); | ||
|
||
export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/1337', | ||
release: '1.0', | ||
environment: 'prod', | ||
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], | ||
tracesSampleRate: 1.0, | ||
}); | ||
|
||
Sentry.setUser({ id: 'user123', segment: 'SegmentA' }); | ||
|
||
app.use(Sentry.Handlers.requestHandler()); | ||
app.use(Sentry.Handlers.tracingHandler()); | ||
|
||
app.use(cors()); | ||
|
||
app.get('/test/express', (_req, res) => { | ||
const transaction = Sentry.getCurrentHub().getScope()?.getTransaction(); | ||
if (transaction) { | ||
transaction.traceId = '86f39e84263a4de99c326acab3bfe3bd'; | ||
transaction.metadata.source = undefined; | ||
} | ||
const headers = http.get('http://somewhere.not.sentry/').getHeaders(); | ||
|
||
// Responding with the headers outgoing request headers back to the assertions. | ||
res.send({ test_data: headers }); | ||
}); | ||
|
||
app.use(Sentry.Handlers.errorHandler()); | ||
|
||
export default app; |
19 changes: 19 additions & 0 deletions
19
...node-integration-tests/suites/express/sentry-trace/baggage-header-out-bad-tx-name/test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import * as path from 'path'; | ||
|
||
import { getAPIResponse, runServer } from '../../../../utils/index'; | ||
import { TestAPIResponse } from '../server'; | ||
|
||
test('Does not include transaction name if transaction source is not set', async () => { | ||
const url = await runServer(__dirname, `${path.resolve(__dirname, '.')}/server.ts`); | ||
|
||
const response = (await getAPIResponse(new URL(`${url}/express`))) as TestAPIResponse; | ||
const baggageString = response.test_data.baggage; | ||
|
||
expect(response).toBeDefined(); | ||
expect(response).toMatchObject({ | ||
test_data: { | ||
host: 'somewhere.not.sentry', | ||
}, | ||
}); | ||
expect(baggageString).not.toContain('sentry-transaction='); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.