akka-http-metrics

Scala CI Maven Central Software License

Easily collect and expose metrics in your akka-http server.

The following implementations are supported:

Versions

Version Release date Akka Http version Scala versions
1.1.1 2020-06-10 10.1.12 2.13.2, 2.12.11
1.1.0 2020-04-18 10.1.11 2.13.1, 2.12.10
1.0.0 2020-03-14 10.1.11 2.13.1, 2.12.10

The complete list can be found in the CHANGELOG file.

Getting akka-http-metrics

Libraries are published to Maven Central. Add to your build.sbt:

libraryDependencies += "fr.davit" %% "akka-http-metrics-<backend>" % <version>

Important: Since akka-http 10.1.0, akka-stream transitive dependency is marked as provided. You should now explicitly include it in your build.

[...] we changed the policy not to depend on akka-stream explicitly anymore but mark it as a provided dependency in our build. That means that you will always have to add a manual dependency to akka-stream. Please make sure you have chosen and added a dependency to akka-stream when updating to the new version

libraryDependencies += "com.typesafe.akka" %% "akka-stream" % <version> // Only Akka 2.5 supported

For more details, see the akka-http 10.1.x release notes

Server metrics

The library enables you to easily record the following metrics from an akka-http server into a registry. The following labeled metrics are recorded:

  • requests (counter)
  • active requests (gauge)
  • request sizes (histogram)
  • responses (counter) [status group | path]
  • errors [status group | path]
  • durations (histogram) [status group | path]
  • response sizes (histogram) [status group | path]
  • connections (counter)
  • active connections (gauge)

Record metrics from your akka server by importing the implicits from HttpMetricsRoute. Convert your route to the flow that will handle requests with recordMetrics and bind your server to the desired port.

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
import fr.davit.akka.http.metrics.core.{HttpMetricsRegistry, HttpMetricsSettings}
import fr.davit.akka.http.metrics.core.scaladsl.server.HttpMetricsRoute._

implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()

val settings: HttpMetricsSettings = HttpMetricsSettings
                                      .default
                                      .withNamespace("com.example.service")

val registry: HttpMetricsRegistry = ... // concrete registry implementation

val route: Route = ... // your route

Http().bindAndHandle(route.recordMetrics(registry), "localhost", 8080)

By default, the errored request counter will be incremented when the served response is an Server error (5xx). You can override this behaviour in the settings.

val settings = HttpMetricsSettings
  .default
  .withDefineError(_.status.isFailure)

In this example, all responses with status >= 400 are considered as errors.

For HTTP2 you must convert the Route to the handler function with recordMetricsAsync. In this case the connection metrics won't be available.

Http2().bindAndHandleAsync(route.recordMetricsAsync(registry), "localhost", 8080)

Labels

By default metrics labels are disabled. You can enable them in the settings.

val settings = HttpMetricsSettings.default
  .withIncludeMethodDimension(true)
  .withIncludePathDimension(true)
  .withIncludeStatusDimension(true)
Method

The method of the request is used as dimension on the metrics. eg. GET

Path

Matched path of the request is used as dimension on the metrics.

When enabled, all metrics will get unlabelled as path dimension by default, You must use the labelled path directives defined in HttpMetricsDirectives to set the dimension value.

You must also be careful about cardinality: see here. If your path contains unbounded dynamic segments, you must give an explicit label to override the dynamic part:

import fr.davit.akka.http.metrics.core.scaladsl.server.HttpMetricsDirectives._

val route = pathPrefixLabel("api") {
  pathLabeled("user" / JavaUUID, "user/:user-id") { userId =>
    ...
  }
}

Moreover, all unhandled requests will have path dimension set to unhandled.

Status group

The status group creates the following dimensions on the metrics: 1xx|2xx|3xx|4xx|5xx|other

Expose metrics

Expose the metrics from the registry on an http endpoint with the metrics directive.

import fr.davit.akka.http.metrics.core.scaladsl.server.HttpMetricsDirectives._

val route = (get & path("metrics")) {
  metrics(registry)
}

Of course, you will also need to have the implicit marshaller for your registry in scope.

Implementations

Datadog

metric name
requests requests_count
active requests requests_active
request sizes requests_bytes
responses responses_count
errors responses_errors_count
durations responses_duration
response sizes response_bytes
connections connections_count
active connections connections_active

The DatadogRegistry is just a facade to publish to your StatsD server. The registry itself not located in the JVM, for this reason it is not possible to expose the metrics in your API.

Add to your build.sbt:

libraryDependencies += "fr.davit" %% "akka-http-metrics-datadog" % <version>

Create your registry

import com.timgroup.statsd.StatsDClient
import fr.davit.akka.http.metrics.datadog.DatadogRegistry

val client: StatsDClient = ... // your statsd client

val registry = DatadogRegistry(client)

See datadog's documentation on how to create a StatsD client.

Dropwizard

metric name
requests requests
active requests requests.active
request sizes requests.bytes
responses responses
errors responses.errors
durations responses.duration
response sizes responses.bytes
connections connections
active connections connections.active

Important: The DropwizardRegistry works with tags. This feature is only supported since dropwizard v5.

Add to your build.sbt:

libraryDependencies += "fr.davit" %% "akka-http-metrics-dropwizard" % <version>

Create your registry

import io.dropwizard.metrics5.MetricRegistry
import fr.davit.akka.http.metrics.dropwizard.DropwizardRegistry

val dropwizard: MetricRegistry = ... // your dropwizard registry

val registry = DropwizardRegistry(dropwizard) // or DropwizardRegistry() to use a fresh registry

Expose the metrics

import fr.davit.akka.http.metrics.dropwizard.marshalling.DropwizardMarshallers._

val route = (get & path("metrics"))(metrics(registry))

Graphite

metric name
requests requests
active requests requests.active
request sizes requests.bytes
responses responses
errors responses.errors
durations responses.duration
response sizes responses.bytes
connections connections
active connections connections.active

Add to your build.sbt:

libraryDependencies += "fr.davit" %% "akka-http-metrics-graphite" % <version>

Create your carbon client and your registry

import fr.davit.akka.http.metrics.graphite.{CarbonClient, GraphiteRegistry}

val carbonClient: CarbonClient = CarbonClient("hostname", 2003)

val registry = GraphiteRegistry(carbonClient)

Prometheus

metric name
requests requests_total
active requests requests_active
request sizes requests_size_bytes
responses responses_total
errors responses_errors_total
durations responses_duration_seconds
response sizes responses_size_bytes
connections connections_total
active connections connections_active

Add to your build.sbt:

libraryDependencies += "fr.davit" %% "akka-http-metrics-prometheus" % <version>

Create your registry

import io.prometheus.client.CollectorRegistry
import fr.davit.akka.http.metrics.prometheus.{PrometheusRegistry, PrometheusSettings}

val settings: PrometheusSettings = ... // your http metrics settings
val prometheus: CollectorRegistry = ... // your prometheus registry

val registry = PrometheusRegistry(prometheus, settings) // or PrometheusRegistry(settings = settings) to use the default registry

You can fine-tune the histogram/summary configuration of buckets/quantiles for the request sizes, durations and response sizes metrics.

val settings: PrometheusSettings = PrometheusSettings
  .default
  .withDurationConfig(Buckets(1, 2, 3, 5, 8, 13, 21, 34))
  .withReceivedBytesConfig(Quantiles(0.5, 0.75, 0.9, 0.95, 0.99))
  .withSentBytesConfig(PrometheusSettings.DefaultQuantiles)

Expose the metrics

import fr.davit.akka.http.metrics.prometheus.marshalling.PrometheusMarshallers._

val route = (get & path("metrics"))(metrics(registry))