Two Keys to Faster Resolution of Ruby Errors

Nate Berkopec /

I’ve maintained Sentry’s Ruby client for the last couple of years. This all started back when I had a different job and we were using a non-Sentry monitoring service that just wasn’t working all that well. Logging into your account seemed to start up a new server somewhere, which meant it took something like 15 seconds for the app to turn on. Plus the interface was terrible. It was just no fun to use.

So I switched us over to Sentry. My immediate reaction was: “Wow, this is way, way better.” Since Sentry is open-source, I pretty quickly started contributing fixes back to the client and I ended up doing so much of this that David (Sentry’s CEO) reached out and said, “We should pay you to do this.” My response was, “Yes, sounds good,” and that’s what I’ve been doing ever since.

In the time I’ve spent working with Sentry, we’ve made tons of updates to the client that improve Ruby error tracking. Of all of these, there are two in particular that stick out as being simple to use while greatly increasing the value Sentry provides: transactions and async processing.

Transaction API

Many error reporting services simply group errors by their class name. “Such and such class raised such and such exception in such and such method.” This isn’t the whole story. If you’ve done a good job at making your architecture modular and object-oriented, there’s a good chance you have classes in use across multiple workflows or contexts. It could be the case that these classes only raise an exception when used in workflow A while being perfectly fine in workflows B, C, and D.

Also, classes are unlikely to be how you or anyone else would think of an error. It’s much more interesting and user-focused to know where in the process of using your app that a user encountered the error. Was it during the checkout process? Did it come from the background processor that handles subscription renewals? Transactions can tell you the exact event that threw the error. Combined with how Sentry automatically tracks how many unique users encountered particular exceptions, this can give you powerful insight into which areas of your application are having the most issues.

Having this information at hand right away makes it that much easier to get to the root of the issue and resolve it, freeing you up to spend less time focused on errors and more time actually working on improving your app. And while some of our APIs require a little extra user input to be useful, the Transaction API works from the moment the Ruby gem is installed. It doesn’t get too much more straightforward than that.

Transactions are part of Context, which allows additional info to be passed to the capture methods. That looks like this:

    Raven.capture_message 'My Event!',
      logger: 'logger',
      extra: {
        my_custom_variable: 'value'
      },
      tags: {
        foo: 'bar'
      }

To add transactions to this mix, you’re looking for the action in which the event occurred. In Rack, this is the request URL. In Rails, it’s the controller name and action (“HelloController#hello_world”). Background jobs, such as Sidekiq, can also define a transaction.

Transactions are modeled as a stack. The top item in the stack (i.e. the last element of the array) is used as the transaction for any events:

    Raven.context.transaction.push "User Import"
    # import some users
    Raven.context.transaction.pop

Transactions may also be overridden/set explicitly during event creation:

    Raven.capture_exception(exception, transaction: "User Import")

Asynchronous Workflows

Sentry packages your exceptions into a JSON format, then sends them via HTTP to a Sentry server. However, this means that Ruby will block on waiting for a response from Sentry. In most situations, this isn’t ideal. Your user or API consumer probably just wants their 500 response as quickly as possible, and you should take care of notifying Sentry about the exception asynchronously. With the async setting you can throw the HTTP part off to somewhere else, and we highly recommend that you do. A pretty common pattern that users follow in the Ruby client is this:

The web process captures the exception, packages it up into JSON, that JSON gets stored in a background job processor like Sidekiq, and then Sidekiq handles the HTTP part of posting to Sentry and receiving the response from Sentry.

Here’s an example for ActiveJob:

    config.async = lambda { |event|
      SentryJob.perform_later(event)
    }


    class SentryJob < ActiveJob::Base
      queue_as :default

      def perform(event)
        Raven.send_event(event)
      end
    end

If you’d like to learn more about async and other options configuration options, check out our config documentation. And if you want to learn more simply about the basics of using Sentry with Ruby, see here.