Conf42 JavaScript 2024 - Online

- premiere 5PM GMT

Supercharged production stacktraces with local variables

Abstract

Via the Node.js inspector API, we can attach local variables, state of variables, at the time of an error to a stacktrace, so you can get a debugger-like experience with your error monitoring tools. This talk walks through how we built this for Sentry SDKs, and some of the challenges we encountered.

Summary

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hey everyone, my name is Abhijit, I'm a software engineer at Sentry, and welcome to my talk, Supercharged Production Stack Traces with Local Variables. So a little bit about myself, I'm Abhijit. I currently work at Sentry, and I help maintain Sentry's open source JavaScript SDKs. Sentry as a product helps you monitor your applications in production, take a look at the errors and performance data that's happening, so you can understand and fix problems faster. One thing we've learned at Sentry is that stack traces are really important to understand how and when errors happen to help debug them. You know this locally, and they're even more important in production if your server goes down or your site doesn't work. At Sentry, we try to make it as easy as possible to view your production stack traces. And so you can see that when an error happens on a site, or on a server somewhere, it gets thrown, Sentry, the Sentry SDK will capture it, and then surface it to you, real time, with the exact stack trace. So you can see the exact line of code that's causing your issue. But, the stack trace sometimes is an issue. enough in terms of what it gives you. There's still a vital piece of debugging context missing. That debugging context is what exactly the arguments or state was at the time the exception happened. not just looking at the kind of frames and what your code was doing, but what variables or classes were being instantiated at the time of your error. there's a solution for this, and a lot of debuggers give this to you. It's to allow you to see the local variables, or the variable state at the time of when a debugger is paused or when an error is thrown. So here is an example, I just attached a debugger to a really simple Node. js program, and it gives me the state of the variables that were defined at when I paused the debugger. We can actually grab this local variable state when an error is thrown, and attach it. Alongside the stack frame, so you can see this in production. And we support this in Sentry for languages like Python, PHP, and Ruby. But, for the longest time, we never really supported this in Node. js. Because there was no built in method to grab this data. There's no API for this, exposed by the Node or JavaScript standard libraries. And this is really important. a lot of people kept asking for this. It's very useful to debug an error in production. If you know exactly what the variables were defined as. But we always kept saying, hey, no, we can't do it, because it's just not part of the standard library. It's not possible within the JavaScript runtime. But, a little while ago last year, we learned that it is actually possible. We can leverage the same strategy debuggers use. and take advantage of the Node. js inspector standard library. And so the V8 inspector is built on top of the kind of, Chrome V8, debugger, and allows you to access a lot of information about the Node. js process, and manipulate it in ways to get this local variable information for you. So it turns out, via this inspector standard library, with a little bit of work and massaging the data, you We can use it in production to grab local variables and attach them to Node. js stack traces. let's walk through some code to see how this would work. here, at the start, we can, instantiate and start, the inspector and open it. this will start an inspector process, essentially a debugger process attached to, your main Node. js process. And In order to grab local variables from the inspector and attach it to stack traces that then we send to Sentry for processing, we need some kind of worker thread. So here we can spin up a worker, that really its job is to look at the inspector, grab the local variables and put it on, and give it to the main thread so that it can be attached to stack traces. so here is the debugger, that's being instantiated. in this debugger, we listen on a couple of events, which are exposed from the inspector standard library. most notably, whenever an exception is thrown, it varies if it's caught or uncaught, we pause the debugger and handle it. So essentially what we're doing is, When an error is thrown in your main Node. js process, we pause it, grab the application state, the local variables, attach it somewhere, and then resume it. And, although it can be weird, you're actually pausing the process, it's actually not that expensive to do this. here's what happens when we're actually pausing. So an error is thrown, the debugger, via the inspector, pauses the main process, We, and this is simplified. There's a lot of edge cases that you normally have to deal with. But what we're doing here is, going through making sure that, it's a valid error reason. And then we take a look at the frames of a stack trace in an error. Kind of grabbing all of the local variables for each frame and then attaching it to that associated frame. This is actually super powerful because it means we can access the local variables. Not just of the topmost frame in the error, but all of the frames wherever they're applicable. We then can use this inspector API to essentially call a fake function that will assign these frames and their associated local variables to a global variable that then the main process can then use later on when it's actually sending an error to Sentry. we have to make sure we release the object, otherwise, there would be memory allocation issues. what does getLocalVariables exactly do? It essentially uses, this runtime. getProperties method to be able to get the local variables via the inspector, we then have to massage the data a little bit. So to make sure that we deal with objects or arrays or similar, but really we're relying mostly on the inspector to do the heavy lifting for us here. So after we've written the local variables to an object and then the process resumes, the error is captured by the sentry SDK. Okay. and then transformed into an error event that can be sent to Sentry. what Sentry then does is it can look up on the error in this global here, and see, oh, were there local variables, and if they were, attach them to the outgoing error event accordingly. And, This all seems pretty complicated because you have to instantiate a worker, start the inspector, set up all these processes. But to a user who wants to start using this today in Sentry for Node. js, they simply just have to add include local variables true to their initialization and this all works for you. And here's what this would look like. So this is an example. Express app where I just threw a bunch of, I just threw an error with a bunch of local variables in scope, and you can see them directly attached to the event. Super powerful because now you know exactly what lines of code, plus what the variable state was when an error happened, so you can debug things even faster. while we were implementing this, there were a lot of challenges to work through. we had problems with worker overhead. Of course, we were instantiating a new worker process and we were instantiating this inspector instance. So we really had to make sure that the, memory and CPU levels were manageable. as we were, Writing things into the main process and pausing stuff. We also had to deal with memory pressure. unhandled exceptions was also something to deal with. There's different kind of behavior for pausing the debugger between handled versus unhandled exceptions. So calling new. error and explicitly handling that versus, throwing an error and letting it bubble up to the global error handlers. ESM versus CJS actually is still a problem that we're struggling with. They have, both have different behaviors and there's actually an active bug in ESM where, this doesn't work or local variable support doesn't work perfectly because of a Node. js bug. Currently, the local variables, setup does not support minified variables. So if you have some kind of build process, like transforming from TypeScript to JavaScript, and then you also minify, let's say because you're deploying to a Lambda or something. currently we don't support this, but the SourceMap spec is taking a look at how they can adopt Minified variables and minified local state and source mapping that. And so whenever that, is introduced into the source map spec, we can take advantage of that. If you are using this strategy, local variables, it obviously will, it's using a debugger inspector instance. So if you try to use your own to debug process, it doesn't work. So you have to use one or the other. And, unfortunately, because we're relying on this V8 inspector and the Node standard library, Currently, this only works for Node. js, so if you wanted similar support in the browser or for runtimes like Bunn or Deno, it doesn't work, but we're always open for ideas to see if we can make that work for those other. JavaScript runtimes and that's it. Thank you so much for listening in on how local variables really help with debugging, your production, JavaScript stack traces. Hopefully you learned a little, if you want to see the example code that was shown off and how this is written. There's a ton of edge cases, again, that we have to think about. this is all available in our open source JavaScript SDKs, linked at the very bottom. I have some links that you can reach out to me, if you have any questions. Otherwise, if you're interested in trying out Sentry and seeing how these local variables work, you can scan that QR code and use the code below to get started. Thank you.
...

Abhijeet Prasad

Software Engineer @ sentry.io

Abhijeet Prasad's LinkedIn account Abhijeet Prasad's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways