-
Notifications
You must be signed in to change notification settings - Fork 19
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
create domain per request, so hub per request is created #21
create domain per request, so hub per request is created #21
Conversation
b025cb8
to
3135e10
Compare
Ok, rebased. Added some comments and domain clean up. Also removed [WIP] as now I think its legitimate PR :-). Removed test was testing sentryScope which is not used anymore, so removed that. |
In case you decide to go forward with this change, it probably should be major version bump, since that sentryScope is gone, which theoretically someone could use. Also anyone using domains should be aware of that update.. to basically ensure its not interfering with their use case in some bad way. Also unlikely, but possible. |
What about https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/handlers.ts#L247-L255? |
@guischdi Hi, thanks for looking into it. So with this assumption - if you want to catch such unhandled error with domain and still have context which request caused it - thats exactly what they are doing on these lines. With hapi its fine to rely on that request error event and report errors there. And with regards how the hub is interconnected with domain. Thats not stupid question, it took me sometime to understand that. Best is to look at the Hope it helps. And feel free to question it.. I might be also missing something. |
Thanks @jardakotesovec for your extensive explanation. I think I now understand it. Your approach seems to be working: I only get breadcrumbs of the specific request and global actions like The thing about the global actions is, that you sometimes might have other global jobs like health checks running from time to time. This jobs might manipulate the scope, which is then also used to be attached to a sentry event. You can find sample code to reproduce this behavior here: const hapi = require('hapi');
const plugin = require('./');
const Sentry = require('@sentry/node');
const dsn = '<dsn of a test project>';
const delay = t => new Promise(resolve => setTimeout(resolve, t));
(async () => {
const server = hapi.server({ port: 8080 });
server.route({
method: 'GET',
path: '/test-sentry',
async handler() {
Sentry.configureScope(scope => scope.setTag('foo', 'bar'));
console.log('Tag set to bar');
await delay(2000);
throw new Error('Oh no, bar is now 42!');
},
});
await server.register({
plugin,
options: {
client: { dsn },
},
});
await server.start();
console.log('Server started');
server.inject({ method: 'GET', url: '/test-sentry' });
await delay(1000);
Sentry.configureScope(scope => scope.setTag('foo', 42));
})(); Sentry gets the tag value 42 which is not set in the request handler but in some globally running job, as can be seen here: Maybe the deprecation warning (also to be found in the Sentry breadcrumbs of the event which was captured at first request made to the test server) gives you some hints how to fix that issue. Or maybe @kamilogorek has some advice how to limit the access to a Sentry hub (or is it only the scope which is accessible from somewhere else) to a single request. |
Can look at it more in detail on monday. But basically if there is no
active domain, which is the case in your ‘global’ example than it talks to
global hub and the hubs created in domain (within request) are somewhat
inherited from the global hub, so you can see it there.
If you want to report something to sentry which is specific for one event
you can always use that ‘withScope’ function and scope changes inside that
won’t leak to any other scope. Hope it somewhat answers your question.
|
And I think any global actions are better of separated in own process run
by cron or something. And if you want them run in same process you can just
create and enter the domain at the beggining (or run it inside domain.run)
to get same separation as requests have.
|
I didn't say, I WANT to access a scope used (in a request) from somewhere else. I meant, one COULD do so. So my concern was not about whether changes on a scope in a request handler could leak to some other part of the application. It's about the other direction: No matter if I use |
@guischdi Yes, I understand that. My thoughts on this particular scenario:
But you are right, something from global hub still can get inside requests, which in some cases might be intentional (like some global information about that node instance), but also can be unexpected in other use cases. On the other hand before this change it was significantly more mixed up and noone really complaint, so its not probably that big deal :-). And I think its also good to consider that domains are already pretty magic (one day will be replaced with async hooks in sentry) - when running things in one thread, its not easy to not mix up things without just passing them everywhere as arguments. |
Because of the way you are plugging in into the hapi error handlers, you can do it in 2 ways, which doesn't require domains. a) Hub based: create it and attach it to the request itself: server.ext({
type: 'onRequest',
method(request, h) {
const currentHub = Sentry.getCurrentHub()
const requestHub = new Sentry.Hub(
currentHub.getClient(),
Sentry.Scope.clone(currentHub.getScope())
)
Sentry.setHubOnCarrier(request, requestHub);
return h.continue;
},
});
// and then
server.events.on({ name: 'request', channels: opts.channels }, async (request, event) => {
const hub = Sentry.getHubFromCarrier(request)
hub.withScope(scope => {
scope.addEventProcessor(_sentryEvent => {
// do whatever
});
Hoek.applyToDefaults(scope, request.sentryScope);
hub.captureException(event.error);
});
}); b) Client/Scope based: use client directly and give it manually handled scope server.ext({
type: 'onRequest',
method(request, h) {
// Inherit data from the parent
request.sentryScope = Sentry.Scope.clone(currentHub.getScope());
return h.continue;
},
});
// and then
server.events.on({ name: 'request', channels: opts.channels }, async (request, event) => {
const client = Sentry.getCurrentHub().getClient()
const scope = request.sentryScope;
scope.addEventProcessor(_sentryEvent => {
// do whatever
});
// or set directly
scope.setExtra("what", "ever")
Hoek.applyToDefaults(scope, request.sentryScope);
client.captureException(event.error, {
// this is EventHint, so pass it whatever you want to expose
request,
event
}, scope);
}); |
@kamilogorek Thanks a lot for getting back to us. Attaching hub to req was option I considered, but was inclined to use domains because thats only way to also save breadcrumps and other integrations (which are using getCurrentHub) to request hub. Only reason why not to use domains I would think are potential performance/stability issues, but since its used a lot inside sentry I would think you don't find domains troublesome? |
@jardakotesovec you're right, and it should be totally fine to use them. |
Thanks @kamilogorek for your responses and having a look into the PR implementation! @jardakotesovec I understand, why using domains seems the right way of doing it.
P.S: Sorry for the small delay! |
@guischdi Yes, tests are always nice to have. I am on holidays for rest of the week and next week, hopefully I will be able to find some spare time to make progress on that then. |
@jardakotesovec You might want to rebase this against latest master after writing tests, so you can check if the new deps break anything |
3135e10
to
a53940e
Compare
Sorry for the latency, @jardakotesovec ! You may have a look at this. If everything is fine with you, we would release this changes with a new semver major (due to dependency updates performed in the meantime). I introduced a new option ( |
@guischdi Thanks! Looks good. Sorry for not responding I was on holidays. |
I suspect that creating
new Scope()
does not do anything.. its just empty object and it will stay empty. You can try - no breadcrumbs get recorded there.After bit digging, I think only way to isolate requests is to have hub per request, which is saved in domain. This is pretty much what express middleware does.
If you look at the getCurrentHub it creates new hub in domain if there is active domain. Otherwise it uses one global hub.
Using this would also add option to add some context anywhere within request lifecycle with
All these global functions like
configureScope
orwithScope
they get hub internally viagetCurrentHub
which gets the current hub from active domain, therefore should be scoped to the request.I will do some testing sometime soon, but interested in your feedback. This code is mainly just to get point across for now.
Hi @kamilogorek if you would have few minutes just to glance over this PR if it makes sense? It would be super helpful :-) Just trying to make the hapi integration right.