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

#3315 - upgrade prometheus client java to 1.1.0 #3325

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading