Skip to content

Commit

Permalink
Merge pull request #3325 from gastonschabas/3315/upgrade-prometheus-c…
Browse files Browse the repository at this point in the history
…lient-java

#3315 - upgrade prometheus client java to 1.1.0
  • Loading branch information
adamw authored Nov 20, 2023
2 parents 4aec152 + 3da8ac2 commit 79f504b
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 88 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,8 @@ lazy val prometheusMetrics: ProjectMatrix = (projectMatrix in file("metrics/prom
.settings(
name := "tapir-prometheus-metrics",
libraryDependencies ++= Seq(
"io.prometheus" % "simpleclient_common" % "0.16.0",
"io.prometheus" % "prometheus-metrics-core" % "1.1.0",
"io.prometheus" % "prometheus-metrics-exposition-formats" % "1.1.0",
scalaTest.value % Test
)
)
Expand Down
16 changes: 8 additions & 8 deletions doc/server/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Add the following dependency:
"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "@VERSION@"
```

`PrometheusMetrics` encapsulates `CollectorReqistry` and `Metric` instances. It provides several ready to use metrics as
`PrometheusMetrics` encapsulates `PrometheusReqistry` and `Metric` instances. It provides several ready to use metrics as
well as an endpoint definition to read the metrics & expose them to the Prometheus server.

For example, using `NettyFutureServerInterpreter`:
Expand Down Expand Up @@ -92,18 +92,18 @@ To create and add custom metrics:
```scala mdoc:compile-only
import sttp.tapir.server.metrics.prometheus.PrometheusMetrics
import sttp.tapir.server.metrics.{EndpointMetric, Metric}
import io.prometheus.client.{CollectorRegistry, Counter}
import io.prometheus.metrics.core.metrics.{Counter, Gauge, Histogram}
import io.prometheus.metrics.model.registry.PrometheusRegistry
import scala.concurrent.Future

// Metric for counting responses labeled by path, method and status code
val responsesTotal = Metric[Future, Counter](
Counter
.build()
.namespace("tapir")
.name("responses_total")
.builder()
.name("tapir_responses_total")
.help("HTTP responses")
.labelNames("path", "method", "status")
.register(CollectorRegistry.defaultRegistry),
.register(PrometheusRegistry.defaultRegistry),
onRequest = { (req, counter, _) =>
Future.successful(
EndpointMetric()
Expand All @@ -112,14 +112,14 @@ val responsesTotal = Metric[Future, Counter](
val path = ep.showPathTemplate()
val method = req.method.method
val status = res.code.toString()
counter.labels(path, method, status).inc()
counter.labelValues(path, method, status).inc()
}
}
)
}
)

val prometheusMetrics = PrometheusMetrics[Future]("tapir", CollectorRegistry.defaultRegistry)
val prometheusMetrics = PrometheusMetrics[Future]("tapir", PrometheusRegistry.defaultRegistry)
.addCustom(responsesTotal)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object PrometheusMetricsExample extends App with StrictLogging {
val endpoints =
List(
personEndpoint,
// Exposes GET endpoint under `metrics` path for prometheus and serializes metrics from `CollectorRegistry` to plain text response
// Exposes GET endpoint under `metrics` path for prometheus and serializes metrics from `PrometheusRegistry` to plain text response
prometheusMetrics.metricsEndpoint
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
package sttp.tapir.server.metrics.prometheus

import io.prometheus.client.exporter.common.TextFormat
import io.prometheus.client.{CollectorRegistry, Counter, Gauge, Histogram}
import io.prometheus.metrics.core.metrics.{Counter, Gauge, Histogram}
import io.prometheus.metrics.expositionformats.ExpositionFormats
import io.prometheus.metrics.model.registry.PrometheusRegistry
import sttp.monad.MonadError
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir._
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.interceptor.metrics.MetricsRequestInterceptor
import sttp.tapir.server.metrics.{EndpointMetric, Metric, MetricLabels}

import java.io.StringWriter
import java.io.ByteArrayOutputStream
import java.time.{Clock, Duration}

case class PrometheusMetrics[F[_]](
namespace: String = "tapir",
registry: CollectorRegistry = CollectorRegistry.defaultRegistry,
registry: PrometheusRegistry = PrometheusRegistry.defaultRegistry,
metrics: List[Metric[F, _]] = List.empty[Metric[F, _]],
endpointPrefix: EndpointInput[Unit] = "metrics"
) {
import PrometheusMetrics._

/** An endpoint exposing the current metric values. */
lazy val metricsEndpoint: ServerEndpoint[Any, F] = ServerEndpoint.public(
endpoint.get.in(endpointPrefix).out(plainBody[CollectorRegistry]),
(monad: MonadError[F]) => (_: Unit) => monad.eval(Right(registry): Either[Unit, CollectorRegistry])
endpoint.get.in(endpointPrefix).out(plainBody[PrometheusRegistry]),
(monad: MonadError[F]) => (_: Unit) => monad.eval(Right(registry): Either[Unit, PrometheusRegistry])
)

/** Registers a `$namespace_request_active{path, method}` gauge (assuming default labels). */
Expand All @@ -48,16 +49,20 @@ case class PrometheusMetrics[F[_]](

object PrometheusMetrics {

implicit val schemaForCollectorRegistry: Schema[CollectorRegistry] = Schema.string[CollectorRegistry]
implicit val schemaForPrometheusRegistry: Schema[PrometheusRegistry] = Schema.string[PrometheusRegistry]

implicit val collectorRegistryCodec: Codec[String, CollectorRegistry, CodecFormat.TextPlain] =
Codec.anyString(TextPlain())(_ => DecodeResult.Value(new CollectorRegistry()))(r => {
val output = new StringWriter()
TextFormat.write004(output, r.metricFamilySamples)
private val prometheusExpositionFormat = ExpositionFormats.init()

implicit val prometheusRegistryCodec: Codec[String, PrometheusRegistry, CodecFormat.TextPlain] =
Codec.anyString(TextPlain())(_ => DecodeResult.Value(new PrometheusRegistry()))(r => {
val output = new ByteArrayOutputStream()
prometheusExpositionFormat.getPrometheusTextFormatWriter.write(output, r.scrape())
output.close()
output.toString
})

private def metricNameWithNamespace(namespace: String, metricName: String) = s"${namespace}_${metricName}"

/** Using the default namespace and labels, registers the following metrics:
*
* - `$namespace_request_active{path, method}` (gauge)
Expand All @@ -69,7 +74,7 @@ object PrometheusMetrics {
*/
def default[F[_]](
namespace: String = "tapir",
registry: CollectorRegistry = CollectorRegistry.defaultRegistry,
registry: PrometheusRegistry = PrometheusRegistry.defaultRegistry,
labels: MetricLabels = MetricLabels.Default
): PrometheusMetrics[F] =
PrometheusMetrics(
Expand All @@ -82,57 +87,55 @@ object PrometheusMetrics {
)
)

def requestActive[F[_]](registry: CollectorRegistry, namespace: String, labels: MetricLabels): Metric[F, Gauge] =
def requestActive[F[_]](registry: PrometheusRegistry, namespace: String, labels: MetricLabels): Metric[F, Gauge] =
Metric[F, Gauge](
Gauge
.build()
.namespace(namespace)
.name("request_active")
.builder()
.name(metricNameWithNamespace(namespace, "request_active"))
.help("Active HTTP requests")
.labelNames(labels.namesForRequest: _*)
.create()
.register(registry),
onRequest = { (req, gauge, m) =>
m.unit {
EndpointMetric()
.onEndpointRequest { ep => m.eval(gauge.labels(labels.valuesForRequest(ep, req): _*).inc()) }
.onResponseBody { (ep, _) => m.eval(gauge.labels(labels.valuesForRequest(ep, req): _*).dec()) }
.onException { (ep, _) => m.eval(gauge.labels(labels.valuesForRequest(ep, req): _*).dec()) }
.onEndpointRequest { ep => m.eval(gauge.labelValues(labels.valuesForRequest(ep, req): _*).inc()) }
.onResponseBody { (ep, _) => m.eval(gauge.labelValues(labels.valuesForRequest(ep, req): _*).dec()) }
.onException { (ep, _) => m.eval(gauge.labelValues(labels.valuesForRequest(ep, req): _*).dec()) }
}
}
)

def requestTotal[F[_]](registry: CollectorRegistry, namespace: String, labels: MetricLabels): Metric[F, Counter] =
def requestTotal[F[_]](registry: PrometheusRegistry, namespace: String, labels: MetricLabels): Metric[F, Counter] =
Metric[F, Counter](
Counter
.build()
.namespace(namespace)
.name("request_total")
.builder()
.name(metricNameWithNamespace(namespace, "request_total"))
.help("Total HTTP requests")
.labelNames(labels.namesForRequest ++ labels.namesForResponse: _*)
.register(registry),
onRequest = { (req, counter, m) =>
m.unit {
EndpointMetric()
.onResponseBody { (ep, res) =>
m.eval(counter.labels(labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(res): _*).inc())
m.eval(counter.labelValues(labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(res): _*).inc())
}
.onException { (ep, ex) =>
m.eval(counter.labelValues(labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(ex): _*).inc())
}
.onException { (ep, ex) => m.eval(counter.labels(labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(ex): _*).inc()) }
}
}
)

def requestDuration[F[_]](
registry: CollectorRegistry,
registry: PrometheusRegistry,
namespace: String,
labels: MetricLabels,
clock: Clock = Clock.systemUTC()
): Metric[F, Histogram] =
Metric[F, Histogram](
Histogram
.build()
.namespace(namespace)
.name("request_duration_seconds")
.builder()
.name(metricNameWithNamespace(namespace, "request_duration_seconds"))
.help("Duration of HTTP requests")
.labelNames(labels.namesForRequest ++ labels.namesForResponse ++ List(labels.forResponsePhase.name): _*)
.register(registry),
Expand All @@ -144,7 +147,7 @@ object PrometheusMetrics {
.onResponseHeaders { (ep, res) =>
m.eval(
histogram
.labels(
.labelValues(
labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(res) ++ List(labels.forResponsePhase.headersValue): _*
)
.observe(duration)
Expand All @@ -153,14 +156,18 @@ object PrometheusMetrics {
.onResponseBody { (ep, res) =>
m.eval(
histogram
.labels(labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(res) ++ List(labels.forResponsePhase.bodyValue): _*)
.labelValues(
labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(res) ++ List(labels.forResponsePhase.bodyValue): _*
)
.observe(duration)
)
}
.onException { (ep, ex) =>
m.eval(
histogram
.labels(labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(ex) ++ List(labels.forResponsePhase.bodyValue): _*)
.labelValues(
labels.valuesForRequest(ep, req) ++ labels.valuesForResponse(ex) ++ List(labels.forResponsePhase.bodyValue): _*
)
.observe(duration)
)
}
Expand Down
Loading

0 comments on commit 79f504b

Please sign in to comment.