Sentry is awesome, but their NodeJS Platform is slightly less great. It's basically entirely synchronous, so if you have a lot of async operations going on things like breadcrumbs and other context information will all get mixed up together.
I put this gist together to share with other people how I worked around this problem in our code base. It's not a perfect solution, but it works pretty well.
The way this works is that Sentry has a global store (global.__SENTRY__
) that includes a hub
property that stores the current hub. The hub has a stack of scopes that are the things you interact with when using things like Sentry.withScope
and Sentry.configureScope
. What I'm doing here is replacing that hub
property with a getter that return an async context local hub
instead of a global one. It does this by using the Node native AsyncLocalStorage module, which creates stores that stay coherent through asynchronous operations.
Using the withSentryHub
function creates a brand new hub
instance and then runs the wrapped code with that new hub provided as the "global" hub. Since we patched Sentry's global store to return the appropriate hub instance for each thread, any code that gets run in the "tree" of code spawned by the withSentryHub
call will get that specific hub instance returned, without affecting any other hubs that any other async contexts might be using.
What this means is that if use withSentryHub
to wrap a "transaction" function (such as the express middleware included at the bottom) then any exceptions captured within the scope of that request should only have breadcrumbs and spans related to that actual request. As bonus, this also makes it easy to have different hubs connected to different clients if you need that sort of thing.