package plugins import javax.inject.Inject import com.typesafe.config.Config import play.api._ import play.api.mvc.RequestHeader import play.api.libs.ws.StandaloneWSClient import play.api.inject.{Binding, Module} import scala.collection.JavaConversions._ import scala.concurrent.{ExecutionContext, Future} import scala.util.Try import play.api.libs.ws.XMLBodyWritables /** * https://github.com/nelsonblaha/play-airbrake */ class Airbrake @Inject() (config: Config, environment: Environment, wsClient: StandaloneWSClient, implicit val ec: ExecutionContext) extends XMLBodyWritables { val enabled: Boolean = Try(config.getBoolean("airbrake.enabled")) getOrElse { environment.mode == Mode.Prod } val apiKey: String = config.getString("airbrake.apiKey") val ssl: Boolean = Try(config.getBoolean("airbrake.ssl")).getOrElse(false) val endpoint: String = Try(config.getString("airbrake.endpoint")) getOrElse "api.airbrake.io/notifier_api/v2/notices" /** * Scala API * * {{{ * // app/Global.scala * override def onError(request: RequestHeader, ex: Throwable) = { * Airbrake.notify(request, ex) * super.onError(request, ex) * } * }}} */ def notify(request: RequestHeader, th: Throwable): Unit = if(enabled) _notify(request.method, request.uri, request.session.data, th, Some(request.headers.toMap), Some(request.queryString)) /** * Java API * * {{{ * // app/Global.java * @Override * public Result onError(RequestHeader request, Throwable t) { * Airbrake.notify(request, t); * return super.onError(request, t); * } * }}} */ def notify(request: play.mvc.Http.RequestHeader, th: Throwable): Unit = if(enabled){ val data = request.getHeaders.toMap.mapValues(_.toList.toString).toMap _notify(request.method, request.uri, data, th, None, None) } /** * Notify when not a Play-related error. */ def notify(description: String, th: Throwable, method: Option[String], uri: Option[String]): Unit = if (enabled) { _notify(method getOrElse "(none)", (uri getOrElse "(none)"), Map(("description" -> description)), th, None, None) } protected def _notify(method: String, uri: String, data: Map[String, String], th: Throwable, headers: Option[Map[String, Seq[String]]], params: Option[Map[String, Seq[String]]]): Future[Unit] = Future.successful { val scheme = if(ssl) "https" else "http" val body = formatNotice(environment.mode.toString, apiKey, method, uri, data, liftThrowable(th), headers, params) wsClient.url(scheme + "://" + endpoint).post(body).onComplete { response => Logger.error("Exception notice sent to Airbrake", th) } } def js = if(enabled) { """ """.format(apiKey, environment.mode.toString) } else "" protected def liftThrowable(th: Throwable) = th match { case e: PlayException => e case e => UnexpectedException(unexpected = Some(e)) } protected def formatNotice(mode: String, apiKey: String, method: String, uri: String, data: Map[String,String], ex: UsefulException, headers: Option[Map[String, Seq[String]]], params: Option[Map[String, Seq[String]]]) = { { apiKey } play-airbrake 0.3.1-SNAPSHOT http://github.com/teamon/play-airbrake { ex.title } { ex.description } { ex.cause.getStackTrace.flatMap(e => formatStacktrace(e)) } { method + " " + uri } { formatParams(params) } { formatSession(data) } { formatHeaders(headers) } { mode } } protected def formatParams(data: Option[Map[String, Seq[String]]]) = if (data.isDefined && !data.get.isEmpty) { formatValuesMap(data.get) } else Nil protected def formatHeaders(data: Option[Map[String, Seq[String]]]) = if (data.isDefined && !data.get.isEmpty) { formatValuesMap(data.get) } else Nil protected def formatValuesMap(map: Map[String, Seq[String]]) = map.flatMap { case (key, seq) => seq map { value => { value } } } protected def formatSession(vars: Map[String, String]) = if(!vars.isEmpty) { formatVars(vars) } else Nil protected def formatVars(vars: Map[String, String]) = vars.map { case (key, value) => { value } } protected def formatStacktrace(e: StackTraceElement) = { val line = "%s.%s(%s)" format (e.getClassName, e.getMethodName, e.getFileName) } } class AirbrakeModule @Inject() (config: Config, environment: Environment, wsClient: StandaloneWSClient, ec: ExecutionContext) extends Module { val airbrake: Airbrake = new Airbrake(config, environment, wsClient, ec) override def bindings(environment: Environment, playConfig: Configuration): Seq[Binding[_]] = { Seq.empty } def notify(request: RequestHeader, th: Throwable): Unit = airbrake.notify(request, th) def notify(request: play.mvc.Http.RequestHeader, th: Throwable): Unit = airbrake.notify(request, th) }