Skip to content
Published November 7, 2017

Over the past year I’ve transitioned several personal projects away from Groovy on Grails to Scala on the Play Framework.  I won’t go into detail on that process, except to say that it’s been a net positive change.   There have been challenges though, and one of them has been finding a suitable crash report aggregator.  Historically, I’ve used the free tier of New Relic as it combines both crash reporting with performance and uptime monitoring.  Unfortunately, New Relic doesn’t support Akka 10. Support is supposedly in the works, but Akka 10 was released nearly a year ago and I prefer not be forced to defer infrastructure upgrades because my analytics integrations will break.

And so I decided to look for a replacement.  My basic criteria is this:

  • Must have a perpetually free tier with reasonable usage limits. (These are unproven personal projects after all)
  • Supports JVM languages, ideally Scala.
  • Includes full stack traces
  • Usable interface

After several hours of research I settled on Bugsnag.   I had previously been impressed by it when I used it in an Android app but had initially written it off because it lacked Scala support. Having failed to find a better alternative I decided to give it a shot.  And I’m so glad I did!

The integration process was extremely simple and took about maybe minutes from account creation to logging my first app exception.  The Bugsnag Java documentation is pretty straightfoward: Add the dependency, instantiate a Bugsnag instance and log events.  You’ll likely want to spend some time tuning your setup but it really is as simple as that to get started.  In the context of my Play / Scala app, here’s what I did:

Add the dependency to build.sbt:

libraryDependencies += "com.bugsnag" % "bugsnag" % "3.+"

Create a custom error handler:

package com.foo.utils

import javax.inject.{Inject, Provider, Singleton}

import com.bugsnag.callbacks.Callback
import com.bugsnag.{Bugsnag, Severity}
import play.api.{Configuration, Environment, OptionalSourceMapper, UsefulException}
import play.api.http.DefaultHttpErrorHandler
import play.api.mvc.{RequestHeader, Result}
import play.api.routing.Router

import scala.concurrent.Future

@Singleton()
class BugsnagErrorHandler @Inject()
(env: Environment,
 config: Configuration,
 sourceMapper: OptionalSourceMapper,
 router: Provider[Router]) extends DefaultHttpErrorHandler(env, config, sourceMapper, router) {

  private val bugsnag = new Bugsnag("ce66137b4a563bee1e03d23108cc9383")
  bugsnag.setAppVersion(config.get[String]("application.version"))
  bugsnag.setReleaseStage(config.get[String]("com.foo.stage"))
  
  // only report exceptions in staging and production environments
  bugsnag.setNotifyReleaseStages("staging", "production")

  private def notifyBugsnag(request: RequestHeader, exception: UsefulException) = {
    val callback: Callback = report => {
      // include requestId and exceptionId if available:
      request.headers.get("X-Request-ID").map { requestId =>
        report.addToTab("request", "requestId",requestId)
      }
      report.addToTab("request", "exceptionId", exception.id)
    }
    bugsnag.notify(exception, Severity.ERROR, callback)
  }

  override def onProdServerError(request: RequestHeader, exception: UsefulException): Future[Result] = {
    notifyBugsnag(request, exception)
    super.onProdServerError(request, exception)
  }
}

The important piece above is the onProdServerError override.  From there it’s just a matter of passing the data to Bugsnag via bugsnag.notify(…).  When you’re implementing this for the first time you may find it useful to override onDevServerError instead, so your local dev exceptions will be reported.

I’ve also added some extra detail to my logs by adding a report callback.  It’s not necessary, but if your app infrastructure generates request id’s etc. it’s handy to include them in the crash reports so you can use elastic search etc. to get additional detail about what went wrong.

Enable the custom error handler in application.conf:

play.http.errorHandler = "com.foo.utils.BugsnagErrorHandler"

And that’s it!  With the above setup you’ll get stack traces from staging and production environments but not local dev builds etc.

Leave a Reply

Your email address will not be published.