-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sentry NodeJS setUser per request #13205
Comments
Also, not sure why this has package:browser tag. I am pretty sure i typed |
Hey, thanks for filing this and providing extensive reproduction steps. We are looking into this and will get back to you. |
Thanks @andreiborza ! To take the above example a step forward, I have tweaked the demo API and sample test script to illustrate that when authenticated requests are handled in parallel, the To confirm that Sentry reports an incorrect User when handling multiple authenticated requests, try running the following example API: import * as Sentry from '@sentry/node';
import bodyParser from 'body-parser';
import express, { Application, NextFunction, Request, Response, Router } from 'express';
Sentry.init({
dsn: 'XXX',
release: 'sentry-scope-testing',
environment: 'local',
skipOpenTelemetrySetup: true
});
const app: Application = express();
const router = Router();
router.use(bodyParser.urlencoded({ extended: true, limit: '500kb' }));
router.use(bodyParser.json({ limit: '500kb' }));
const Users: { id: string; email: string; name: string }[] = [
{ id: '1', email: '[email protected]', name: 'foo example' },
{ id: '2', email: '[email protected]', name: 'foo example2' },
{ id: '3', email: '[email protected]', name: 'foo example3' },
{ id: '4', email: '[email protected]', name: 'foo example4' }
];
const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
router.use('/users', function (req, res, next) {
try {
const reqUser = Users.find((u) => u.id === req.headers['authorization']);
if (reqUser) {
Sentry.setTag('Authenticated', true);
Sentry.setUser(reqUser);
const randomNum = Math.random() * 3000;
sleep(randomNum).then(() => {
Sentry.withScope((scope) => {
// extras will include correct User.
// This allows a reference point to check the reported Issue User and the Additional Data reqUser
// Our tests confirm that this is often wrong when the api is dealing with requests in parallel
scope.setExtras({ reqUser, randomNum });
Sentry.captureMessage('sentry-scope-testing!');
});
res.json(Users);
});
} else {
throw new Error('Authentication Error');
}
} catch (err) {
next(err);
}
});
app.use('/api', router);
app.use(function (err: Error, req: Request, res: Response, next: NextFunction) {
const { method, originalUrl, params, query, body } = req;
const { statusCode, locals } = res;
Sentry.withScope((scope) => {
scope.setExtras({
request: { method, originalUrl, params, query, body },
response: { statusCode, locals }
});
const eventId = Sentry.captureException(err);
(res as { sentry?: string }).sentry = eventId;
});
next(err);
});
// Or just use this, which is identical to above, without `extras`
// Sentry.setupExpressErrorHandler(app);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`API running @ http://localhost:${PORT}`);
}); After starting the above server, create (& run) a shell script containing the following commands to invoke some authenticated requests in parallel: curl --header "authorization: 4" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 2" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 3" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 4" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 3" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 3" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 2" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 2" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 4" --header "content-type: application/json" http://localhost:3000/api/users &
curl --header "authorization: 2" --header "content-type: application/json" http://localhost:3000/api/users &
wait |
Here's a repo for ease of running my above mentioned examples: https://github.com/sbriceland/sentry-debug |
Awesome, thank you for providing even more detail :) |
Hey, had another look and indeed this is an issue IF If you have your own Otel setup, please see https://docs.sentry.io/platforms/javascript/guides/node/opentelemetry/custom-setup/ on how to set that up properly. The scope isolation should work then. |
I don't know this is the root cause, defaultIntegrations including httpIntegration are skipped if dsn is omitted. sentry-javascript/packages/node/src/sdk/index.ts Lines 147 to 149 in b29d771
|
@andreiborza - we do need
The documentation in Sentry has improved to get through some of the hurdles mentioned, but we were able to figure out the correct setup of OTEL + Sentry. The problem isn't getting it working, it's that the SentrySampler forces OTEL TraceProvider to use the sentry configured Sample Rate for all span processors. |
@terasaka2k - I thought that could have been an issue. So, I ended up putting together a more robust reproduction example in a repo: https://github.com/sbriceland/sentry-debug. More importantly, I made the DSN configurable so that we could test the case of sending reports up to Sentry just in case: https://github.com/sbriceland/sentry-debug/blob/main/src/server.ts#L5. We still experienced the same issues with a valid DSN. |
@sbriceland thank you for elaborating this (and thus documenting it for future users coming across this issue). As the issue isn't really with |
Not sure this is the problem only in the sample codes, but just to be sure, the setup must follow https://docs.sentry.io/platforms/javascript/guides/node/install/esm/ (or similar) so that httpIntegration works correctly for incoming requests. |
@andreiborza - This is a bug with scopes though right? I'm not sure why opting out of Sentry's open telemetry would cause something like Scopes to be unreliable across requests. |
FYI the problem here is most likely that the automatic request isolation happens after middlewares run. I added docs for this here: getsentry/sentry-docs#11378, we may investigate if we can somehow fix this but I don't know if we'll find a way -so for now you'll need to wrap this manually. |
It should be also noted that users should use witIsolationScope on WebSocket endpoint. It too mangles up user label. In Fastify, for example, withIsolationScope should be used onRequest hook. app.get(
‘/ws’,
{
websocket: true,
onRequest: (_, __, done) => withIsolationScope(done),
},
(ws) => {...}
) |
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/node
SDK Version
8.20.0
Framework Version
@sentry/node
Link to Sentry event
No response
Reproduction Example/SDK Setup
How to set User per request in Node?
From Scopes Documentation:
... The documentation matches exactly the behavior needed. However, it simply does not work.
setUser
setUser
for this request (scope) because we don't have one, and throw an error manuallyProblems
This example using Express should demonstrate...
Steps to Reproduce
Send the following requests:
Expected Result
Logs show different
user
context. Also, this is only a very simple issue. We've seen manyuser
s mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.Actual Result
same
user
for requests with different authThe text was updated successfully, but these errors were encountered: