
com.twitter.finagle.stats.ExceptionStatsHandler.scala Maven / Gradle / Ivy
package com.twitter.finagle.stats
import com.twitter.util.Throwables
/**
* API for deciding where request exceptions are reported in stats.
* Typical implementations may report any cancellations or validation
* errors separately so success rate can from valid non cancelled requests.
*/
object ExceptionStatsHandler {
private[stats] val Failures = "failures"
private[stats] val SourcedFailures = "sourcedfailures"
/**
* Returns the set of paths to record an exception against.
*
* @param t the exception being recorded
* @param labels the category, usually 'failures' and 'sourcedfailures/service'
* @param rollup whether to replicate RollupStatsReceiver
* @return the complete set of stat paths to record against
*/
private[stats] def statPaths(
t: Throwable,
labels: Seq[Seq[String]],
rollup: Boolean
): Seq[Seq[String]] = {
val exceptionChain = Throwables.mkString(t)
val suffixes = if (rollup) {
exceptionChain.inits.toSeq
} else {
Seq(exceptionChain, Nil)
}
labels.flatMap { prefix =>
suffixes.map { suffix =>
prefix ++ suffix
}
}
}
}
/**
* Exception Stats Recorder.
*/
trait ExceptionStatsHandler {
def record(statsReceiver: StatsReceiver, t: Throwable): Unit
}
/**
* Basic implementation of exception stat recording that
* allows exceptions to be categorised and optional rollup.
*
* @param categorizer function for customer categories, None defaults to 'failures'
* @param sourceFunction function for extracting a source when configured
* @param rollup whether to report chained exceptions like RollupStatsReceiver
*/
class CategorizingExceptionStatsHandler(
categorizer: Throwable => Option[String] = _ => None,
sourceFunction: Throwable => Option[String] = _ => None,
rollup: Boolean = true)
extends ExceptionStatsHandler
{
import ExceptionStatsHandler._
private[this] val underlying: ExceptionStatsHandler = {
val mkLabel: Throwable => String =
t => categorizer(t).getOrElse(Failures)
new MultiCategorizingExceptionStatsHandler(mkLabel, _ => Set.empty, sourceFunction, rollup)
}
def record(statsReceiver: StatsReceiver, t: Throwable): Unit =
underlying.record(statsReceiver, t)
}
/**
* Basic implementation of exception stat recording that
* supports flags and source labeling, and rollup.
*
* For example,
*
* {{{
* import java.net.{ConnectException, InetSocketAddress}
* val f1 =
* Failure.adapt(
* new CancelledConnectionException(new ConnectException),
* Failure.Restartable|Failure.Interrupted)
* val f2 =
* new IndividualRequestTimeoutException(1.seconds)
* f2.serviceName = "myservice"
*
* val handler =
* new MultiCategorizingExceptionStatsHandler(
* mkFlags = Failure.flagsOf,
* mkSource= SourcedException.unapply)
*
* }}}
*
* `f1` is recorded as:
* failures: 1
* failures/interrupted: 1
* failures/interrupted/com.twitter.finagle.Failure: 1
* failures/interrupted/com.twitter.finagle.Failure/com.twitter.finagle.CancelledConnectionException: 1
* failures/interrupted/com.twitter.finagle.Failure/com.twitter.finagle.CancelledConnectionException/java.net.ConnectException: 1
* failures/restartable: 1
* failures/restartable/com.twitter.finagle.Failure: 1
* failures/restartable/com.twitter.finagle.Failure/com.twitter.finagle.CancelledConnectionException: 1
* failures/restartable/com.twitter.finagle.Failure/com.twitter.finagle.CancelledConnectionException/java.net.ConnectException: 1
*
* `f2` is recorded as:
* failures
* failures/com.twitter.finagle.IndividualRequestTimeoutException
* sourcedfailures/myservice
* sourcedfailures/myservice/com.twitter.finagle.IndividualRequestTimeoutException
*
* @param mkLabel label prefix, default to 'failures'
* @param mkFlags extracting flags if the Throwable is a Failure
* @param mkSource extracting source when configured
* @param rollup whether to report chained exceptions like RollupStatsReceiver
*
*/
private[finagle] class MultiCategorizingExceptionStatsHandler(
mkLabel: Throwable => String = _ => ExceptionStatsHandler.Failures,
mkFlags: Throwable => Set[String] = _ => Set.empty,
mkSource: Throwable => Option[String] = _ => None,
rollup: Boolean = true)
extends ExceptionStatsHandler
{
import ExceptionStatsHandler._
def record(statsReceiver: StatsReceiver, t: Throwable): Unit = {
val parentLabel: String = mkLabel(t)
val flags = mkFlags(t)
val flagLabels: Seq[Seq[String]] =
if (flags.isEmpty) Seq(Seq(parentLabel))
else flags.toSeq.map(Seq(parentLabel, _))
val labels: Seq[Seq[String]] = mkSource(t) match {
case Some(service) => flagLabels :+ Seq(SourcedFailures, service)
case None => flagLabels
}
val paths: Seq[Seq[String]] = statPaths(t, labels, rollup)
if (flags.nonEmpty) statsReceiver.counter(parentLabel).incr()
paths.foreach { path =>
statsReceiver.counter(path: _*).incr()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy