One Year of Sentry Elixir
By Mitchell Henke on October 10th 2017
As I reflect on last month's ElixirConf, and look towards Sentry's Forge conference, it dawned on me that it's been just over a year since taking over Sentry's Elixir SDK. It's my first and only open source project with any significant amount of usage, so it's been a learning experience. I'd also be remiss if I didn't thank Jason Stiebs for his significant contributions and help along the way.
At RokkinCat, we've maintained the Android and Swift Sentry packages for a while, and when the Sentry folks heard we were big fans of Elixir, they asked if we'd like to maintain the Elixir package. We were fortunate to inherit the existing package from Stanislav Vishnevskiy, as it provided the important core functionality we could build on. Almost 200 issues or pull requests and 6 major versions later, we've tried to stay as close as possible to this ideal while also adding significant features and configuration flexibility for those that need it. Many features have been added, but there are a few I am especially happy to have included in Sentry Elixir.
The first is Sentry.Plug, which allows those using Plug-based applications like Phoenix to report errors and the context that led to them. It is designed for use alongside the core Plug module Plug.ErrorHandler. To use
Plug.ErrorHandler, one just has to put
use Plug.ErrorHandler in their Plug pipeline.
Sentry.Plug defines that method for you, and then will report any errors to Sentry in the format expected. The neat part isn't anything incredibly special, but rather the simplicity. Since it can make safe assumptions about how to interact with a Plug-based application, the code only amounts to about 100 lines. Adding the two lines necessary to use
Sentry.Plug gives an application the ability to catch and report errors within a Plug application with the stacktrace, loaded modules, and any custom metadata.
The concurrent nature of Elixir can make it more difficult to track down issues when there is a lot happening at once across complex supervision trees and applications. Figuring out how to report crashes for any one of them while retaining the context is harder than it would be on other platforms. An application may have a dynamic and asynchronous job queue, or an application-specific set of processes to handle and hold state, and you'd want to know if something went wrong in any of those.
There is more than one good and valid way to handle it, and we've done our best to make it easy for the common case, and have configuration options for the less common ones. Sentry.Logger fulfills this somewhat complex need by receiving structured reports from Erlang's error_logger when a process does not exit gracefully. Much like
Sentry.Plug, the code is small and hopefully understandable. We've wrestled with many issues around the functionality and implementation of the module since the start. The Erlang
error_logger module has been difficult to work with at times, and there have been multiple iterations on figuring it out, so I'm proud that we've worked through it to deliver a strong and necessary feature.
Source Code Context
Perhaps one of the most unique features we've added to Sentry Elixir is the ability to view source code around the cause of an error on Sentry's website was added earlier this year. Usually when reporting an error, you can see the line numbers and file where an error happened, and the version of your application it happened on. This is enough information to go find the code itself, but it can be easier and quicker if the source code is right there alongside the error. Enter Sentry.Sources. When enabled and configured, it allows errors to be reported with source code, so that you can see the exact code that led to an error on sentry.io:
Being a compiled language, Elixir does not necessarily deploy with its source code like its more interpretive counterparts. There didn't seem to be much prior art or code that attempted this, so it was something of an adventure. Ultimately, the solution we ended up at was to read in source code during compilation, and store it within the compiled application. There can be a bit of wonkiness and complexity around the configuration and setting it up, but the results are definitely neat to see!
If you're interested in seeing all changes, take a look at the changelog.