Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5xx and 4xx responses go to unlabelled when handled with AKKA ExecutionDirectives.handleExceptions #223

Open
zzvara opened this issue Jan 16, 2022 · 0 comments

Comments

@zzvara
Copy link

zzvara commented Jan 16, 2022

All 2xx responses are recorded properly to their respective endpoint label, however, some 5xx and 4xx responses go to unlabelled:

image

image

The registry is created as follows:

protected def createPrometheusRegistry(
    metricsNamespace: String = "core_server"
  ): PrometheusRegistry =
    synchronized {
      assume(!metricsNamespace.endsWith("_"))
      val prometheusCollector = CollectorRegistry.defaultRegistry
      val prometheusSettings = PrometheusSettings
        .default
        .withNamespace(metricsNamespace)
        .withIncludeMethodDimension(true)
        .withIncludePathDimension(true)
        .withIncludeStatusDimension(true)
        .withDurationConfig(
          Buckets(0.005, 0.01, .025, .05, .075, .1, .25, .5, .75, 0.875, 1, 1.75, 2.5, 5, 7.5, 10,
            15, 20, 30)
        )
        .withReceivedBytesConfig {
          val buckets =
            Range(0, 1000, 100) ++ Range(1000, 10000, 1000) ++ Range(10000, 100000, 10000)
          Buckets(buckets.map(_.toDouble).toList)
        }
        .withSentBytesConfig {
          val buckets =
            Range(0, 1000, 100) ++ Range(1000, 10000, 1000) ++ Range(10000, 100000, 10000)
          Buckets(buckets.map(_.toDouble).toList)
        }

      PrometheusRegistry(prometheusCollector, prometheusSettings)
    }

And then we have the main route for that prometheusRegistry:

pathLabeled("metrics") {
            import fr.davit.akka.http.metrics.prometheus.marshalling.PrometheusMarshallers._
            metrics(prometheusRegistry)
          }

So that metrics are available on /metrics for the same AKKA server where we serve regular API calls. Therefore /api/v1/... (API calls) are routed through the load balancer to the end-user, so that the end-users can call paths that start with /api/v1, but /metrics is available only intra-cluster for Prometheus to scrape.

Below are examples on how we use pathPrefixLabeled and pathPrefix:

image

When a route responds with 400 for example, from its own route, let's say /api/v1/chat/ticket, the 4xx is properly recorded to route with label /api/v1/chat/ticket. However, when /api/v1/chat/ticket throws an exception and the exception is caught by AKKA's ExecutionDirectives.handleExceptions, when handleExceptions responds with 400, it is then recorded as unlabelled.

The exception handling encapsulator encapsulates all directives nearly, as follows:

handleRejections(rejectionHandler) {
      handleExceptions(exceptionHandler) {
        encodeResponse {
          cors(corsSettings) {
            pathPrefixLabeled("api" / Segment) {

The below code is from this very library, HttpMetricsDirectives. I can see that the response is patched with a PathLabel, but I figure this does not get passed when an exception is thrown from the path, because then there is no response.

private def rawPathPrefixLabeled[L](pm: PathMatcher[L], label: Option[String]): Directive[L] = {
    implicit val LIsTuple: Tuple[L] = pm.ev
    extractRequestContext.flatMap { ctx =>
      val pathCandidate = ctx.unmatchedPath.toString
      pm(ctx.unmatchedPath) match {
        case Matched(rest, values) =>
          tprovide(values) & mapRequestContext(_ withUnmatchedPath rest) & mapResponse { response =>
            val suffix = response.attribute(HttpMetrics.PathLabel).getOrElse("")
            val pathLabel = label match {
              case Some(l) => "/" + l + suffix // pm matches additional slash prefix
              case None    => pathCandidate.substring(0, pathCandidate.length - rest.charCount) + suffix
            }
            response.addAttribute(HttpMetrics.PathLabel, pathLabel)
          }
        case Unmatched =>
          reject
      }
    }
  }

Is it possible to label these responses originating from the exception handling encapsulator?

If not, my idea is to extend the pathPrefixLabeled and pathPrefix to include the handleExceptions(exceptionHandler) part themselves and then the response would be originating from the proper route /api/v1/chat/ticket?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant