Matthew Lindfield Seager

Matthew Lindfield Seager

Notify via Bugsnag When an Exception Hasn’t Been Raised

In a REST API I was writing, I wanted certain unlikely failures effecting customers to get logged to BugSnag as warnings so we would know if there was a pattern of failures, even if customers didn’t follow the instructions on the page to let us know.

From what I could tell reading the docs, BugSnag wanted me to pass it an error (exception) of some kind but these failures weren’t raising exceptions, they were just returning appropriate 4xx HTTP error codes.

There’s probably a better way of doing it but my eventual solution involved creating, but not raising, an error:

e = StandardError.new(‘Verified successfully but couldn’t fetch any records’)
Bugsnag.notify(e)

By the way, I would normally use a more descriptive variable name but I think this is one of those rare exceptions (pun not intended) where the meaning is so obvious and the variable is so short lived that it’s acceptable. A bit like using i and j as variables in a loop.

I tested this code from the production console to make sure it worked and that notifications came through to our internal chat app. What I noticed is that, perhaps because I didn’t raise the errors, the Bugsnags didn’t include helpful backtrace information like file names, line numbers or method names. The docs revealed a set_backtrace method and StackOverflow pointed me in the direction of [caller](https://ruby-doc.org/core-2.5.3/Kernel.html).

Of course I found myself using this same code 4 times in the same file, an obvious candidate to split into a method. Of those 4 times, they were split evenly between warnings and errors so the method needed to allow for that. I also wanted to be able to add a tab to Bugsnag with arbitrary information. Skipping to the finished product, the end result was:

  def notify_via_bugsnag(message:, severity: 'warning', aditional_info: {})
    e = RuntimeError.new message
    e.set_backtrace caller(2)
    Bugsnag.notify(e) do |report|
      report.severity = severity
      report.add_tab(:appname_API, additional_info) if additional_info.present?
    end
  end

The main thing to note is the addition of (2) to caller. Because I’m setting the backtrace from within a called method I want it to start one frame higher in the stack.

I then consumed this method in the controller with code like this:

      notify_via_bugsnag(message: 'Requestor was verified but student data wasn\'t saved',
        severity: 'error',
        additional_info: {
          student_id: params[:id],
        })
      head :unprocessable_entity