11
minute read
Paul Leader

Managing Ruby Warnings and Rails Deprecations in Bugsnag

Intro

With the arrival of Ruby 2.7 at the end of last year, deprecations and warnings have become a common topic in the Ruby and Rails worlds. Ruby 2.7 introduced a number of deprecation warnings in preparation for tightening up how parameters will be handled in Ruby 3. This has made tracking and triaging them a big job for many teams. In this post I’m going to walk you through how we use Bugsnag to capture and manage both Ruby warnings and Rails deprecations in our own Rails-based dashboard.

When you think of Bugsnag you normally think about bugs: things that are broken. But we use Bugsnag to track other issues, such as hits on deprecated APIs, or signups that are rejected by our bot prevention. Similarly, we use Bugsnag to efficiently identify and resolve deprecations and warnings. By bringing deprecations and warnings into Bugsnag we are able to use the same process we use to triage and manage bugs.

In this post I will explain:

  • how we capture Rails deprecations and Ruby warnings and send them to Bugsnag
  • how we configure the Bugsnag Ruby notifier to produce the most useful notifications
  • how we use Bugsnag to manage and triage those issues

Catching Rails Deprecations

By default, Rails sends deprecation warnings to the logs. But Rails can also make use of its internal publish/subscribe framework, [CODE]ActiveSupport::Notifications[/CODE], to let us intercept these warnings and handle them in a more sophisticated way. Instead of printing to the logs it will send a message to the [CODE]deprecation.rails[/CODE] topic, we simply subscribe to the channel and send a Bugsnag notification with the relevant information.

To switch Rails from logging to sending notifications, we add the following to each of the environments we want to monitor (e.g. [CODE]config/environments/production.rb[/CODE]):

-- CODE language-ruby --
config.active_support.deprecation = :notify

and then in [CODE]lib/warning_handlers/rails.rb[/CODE]:

-- CODE language-ruby --
module WarningHandlers
 module Rails
    class Deprecation < StandardError; end

    def self.register_deprecation_handler
      ActiveSupport::Notifications.subscribe('deprecation.rails') do |_name, _start, _finish, _id, payload|
        exception = Deprecation.new(payload[:message])

        Bugsnag.notify(exception) do |report|
          report.severity = 'warning'
          report.grouping_hash = payload[:message]
        end
      end
    end
  end
end

Then in an initializer (e.g. [CODE]config/initializers/deprecations.rb[/CODE]), we add:

-- CODE language-ruby --
require "#{Rails.root}/lib/warning_handlers/rails"
WarningHandlers::Rails.register_deprecation_handler

The call to [CODE]ActiveSupport::Notifications.subscribe('deprecation.rails')[/CODE] registers the block as a handler for the message. Within the handler we notify Bugsnag with the details of the deprecation from the message.

Setting the [CODE]grouping_hash[/CODE] parameter tells Bugsnag to treat all events with the same message as instances of the same error, making it easier to triage them. You can find out more about [CODE]grouping_hash[/CODE] from our notifier documentation.

We use a custom exception to make it easier to identify and filter deprecations on the dashboard.

Catching Ruby Warnings

As of version 2.5, Ruby provides a simple mechanism to modify the behaviour of warnings. The [CODE]Warning[/CODE] module (RubyDocs) allows you to override the behaviour of the [CODE]warn[/CODE] method. As with our Rails deprecation it’s just a matter of sending a Bugsnag notification from our custom implementation of [CODE]warn[/CODE].

-- CODE language-ruby --
module WarningHandlers
  module Ruby
    class Warning < StandardError; end

    def warn(message)
      exception = Warning.new(message)
      Bugsnag.notify(exception) do |report|
        report.severity = 'warning'
        report.grouping_hash = message
      end
    end
  end
end

if Rails.env.production? || Rails.env.staging?
  Warning.singleton_class.prepend(WarningHandlers::Ruby)
end

Here I’ve conditionally prepended it only for production and staging. Which environments you enable it for will ultimately depend on how your process works.

As with our Rails deprecations, we raise a custom exception and set our [CODE]grouping_hash[/CODE] appropriately so that we can group and triage our Ruby warnings efficiently.

In our [CODE]config/application.rb[/CODE] we include the following early on, before the line that requires bundled gems. If you don’t want notifications for warnings triggered by gems being required then move it to the line after.

-- CODE language-ruby --
if defined?(Bundler)
  ...
  require_relative '../lib/warning_handlers/ruby'
  ...
end

Focusing On the Code that Matters

When you explicitly call [CODE]Bugsnag.notify[/CODE] in your code the stacktrace that is sent will include the code that sent the notification as the top stack frame. This isn’t very useful when recording deprecations and warnings. In this case we’re more interested in identifying the line of code that caused the warning itself. To do this we do two things:

  • Make sure all our warning and deprecation handling code is in a lib directory
  • Tell the Bugsnag notifier to ignore it

We put all our handlers in [CODE]lib/warning_handlers[/CODE] and then add the following to our Bugsnag notifier configuration:

-- CODE language-ruby --
config.vendor_path = %r{^lib/warning_handlers}

The [CODE]vendor_path[/CODE] configuration is a regex that tells the notifier what code to ignore. Anything in the [CODE]lib/warning_handlers[/CODE] directory will be excluded from the stacktrace that is reported, making the errors much easier to interpret.

Managing Your Deprecations and Warnings

Create Bookmarks

Now that we have our deprecations and warning in Bugsnag, we apply the same techniques to managing them as we would to bugs and other issues. You can read about our best-practices for triaging software defects and bugs in more details here.

Deprecations and warnings will appear in the Bugsnag dashboard as handled warnings, which you can filter down to by specifying the exception type and setting the severity appropriately:

We now see errors in Bugsnag that look something like this:

From this we can see that it’s a Rails deprecation warning, being triggered in a particular initializer, and we can see what the issue is.

For both Rails deprecations and Ruby warnings we created shared bookmarks for the team so that we can see them at a glance.

To create a bookmark click on the “Bookmark current search” link in the sidebar

Give it a name and share it with your colleagues

Monitoring your Warnings

You can handle your deprecations and warnings in two ways, on a periodic basis, actively checking them as part of a regular schedule, or reactively as they occur. If you want to handle them reactively you can use the Bugsnag alerting and workflow engine to notify you as soon as a new warning is received. For example, we have a Slack notification for each new warning or Rails deprecation. You can learn more about the alerting and workflow engine in this article, or jump straight to the documentation. The bookmarks you created earlier will be used to filter those notifications to just issues you care about.

Triaging Warnings and Deprecations

Now your team has a view on your deprecations and warnings and can work together to triage them.

By their nature, most warnings and deprecations are not urgent, they generally refer to some future upgrade of a library, language or framework, however a small number may indicate an issue with your code that may result in an error in the right circumstances.

How you triage warnings will depend on the nature of the warning, your current process, and how you manage upgrades and maintenance work. At Bugsnag we use Jira to manage issues, so we create new tickets from each of the deprecations we receive

Because our integration with Jira is two-way, when the issue is completed, the associated Bugsnag will be automatically marked as fixed. You can learn more about using Bugsnag with your issue management tool, check out this article on our issue tracker integrations.

We manage upgrades, and other maintenance work, as mini projects within a quarterly plan. To manage these we create epics within Jira, to which warnings and deprecations can be assigned. Epics might be for a specific upgrade, for example Rails 6.1, or they might be more general batches of maintenance work scheduled around feature development. A small number of warnings may require more urgent attention and be handled on their own. These include warnings with security implications or ones that might result in an error in the right circumstances.

Closing Thoughts

The majority of deprecations and warnings are lower priority than fixing bugs or implementing new features. Unless they are in some way impeding development right now, they can probably wait. Bugsnag is all about helping you focus on what’s important, and managing everything else in a way that takes away the stress and strain of having to pay constant attention.

By sending our warnings and deprecations to Bugsnag we:

  • have one place to manage all our issues
  • get notified about the things we care about, through the channels that work best for our team
  • manage those issues through our established processes
Bugsnag helps you prioritize and fix software bugs while improving your application stability
Request a demo