From 6bed377de5a517c48ff3498a12874e60f4103257 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 3 Feb 2025 11:52:57 +0100 Subject: [PATCH] Adjust metrics backends --- .../client4/listener/ListenerBackend.scala | 9 +++- .../client4/listener/RequestListener.scala | 29 ++++++++++--- .../main/scala/sttp/client4/logging/Log.scala | 7 ++-- .../sttp/client4/logging/LoggingBackend.scala | 2 +- docs/backends/wrappers/custom.md | 2 +- .../opentelemetry/OpenTelemetryDefaults.scala | 3 +- .../OpenTelemetryMetricsBackend.scala | 29 ++++++------- .../OpenTelemetryMetricsConfig.scala | 37 ++++++++-------- .../prometheus/PrometheusBackend.scala | 42 +++++++++---------- .../client4/prometheus/PrometheusConfig.scala | 14 +++---- .../prometheus/PrometheusBackendTest.scala | 13 ++++-- 11 files changed, 108 insertions(+), 79 deletions(-) diff --git a/core/src/main/scala/sttp/client4/listener/ListenerBackend.scala b/core/src/main/scala/sttp/client4/listener/ListenerBackend.scala index 726e4c955f..8f2f29c670 100644 --- a/core/src/main/scala/sttp/client4/listener/ListenerBackend.scala +++ b/core/src/main/scala/sttp/client4/listener/ListenerBackend.scala @@ -15,9 +15,14 @@ abstract class ListenerBackend[F[_], P, L]( listener.beforeRequest(request).flatMap { case (requestToSend, tag) => monad .handleError(delegate.send(requestToSend)) { case e: Exception => - listener.requestException(requestToSend, tag, e).flatMap(_ => monad.error(e)) + monad.flatMap { + ResponseException.find(e) match { + case Some(re) => listener.requestSuccessful(requestToSend, re.response, tag, Some(re)) + case None => listener.requestException(requestToSend, tag, e) + } + } { _ => monad.error(e) } } - .flatMap(response => listener.requestSuccessful(requestToSend, response, tag).map(_ => response)) + .flatMap(response => listener.requestSuccessful(requestToSend, response, tag, None).map(_ => response)) } } diff --git a/core/src/main/scala/sttp/client4/listener/RequestListener.scala b/core/src/main/scala/sttp/client4/listener/RequestListener.scala index 76d46ddff0..bd68c6643e 100644 --- a/core/src/main/scala/sttp/client4/listener/RequestListener.scala +++ b/core/src/main/scala/sttp/client4/listener/RequestListener.scala @@ -1,8 +1,10 @@ package sttp.client4.listener import sttp.monad.MonadError -import sttp.client4.{GenericRequest, Response} +import sttp.client4.GenericRequest import sttp.shared.Identity +import sttp.model.ResponseMetadata +import sttp.client4.ResponseException /** A listener to be used by the [[ListenerBackend]] to get notified on request lifecycle events. The request can be * enriched before being sent, e.g. to capture additional metrics. @@ -13,8 +15,18 @@ import sttp.shared.Identity */ trait RequestListener[F[_], L] { def beforeRequest[T, R](request: GenericRequest[T, R]): F[(GenericRequest[T, R], L)] - def requestException(request: GenericRequest[_, _], tag: L, e: Exception): F[Unit] - def requestSuccessful(request: GenericRequest[_, _], response: Response[_], tag: L): F[Unit] + def requestException(request: GenericRequest[_, _], tag: L, e: Throwable): F[Unit] + + /** @param e + * A [[ResponseException]] that might occur when handling the response: when the raw response is successfully + * received via the network, but e.g. a parsing or decompression exception occurs. + */ + def requestSuccessful( + request: GenericRequest[_, _], + response: ResponseMetadata, + tag: L, + e: Option[ResponseException[_]] + ): F[Unit] } object RequestListener { @@ -22,9 +34,14 @@ object RequestListener { new RequestListener[F, L] { override def beforeRequest[T, R](request: GenericRequest[T, R]): F[(GenericRequest[T, R], L)] = monadError.eval(delegate.beforeRequest(request)) - override def requestException(request: GenericRequest[_, _], tag: L, e: Exception): F[Unit] = + override def requestException(request: GenericRequest[_, _], tag: L, e: Throwable): F[Unit] = monadError.eval(delegate.requestException(request, tag, e)) - override def requestSuccessful(request: GenericRequest[_, _], response: Response[_], tag: L): F[Unit] = - monadError.eval(delegate.requestSuccessful(request, response, tag)) + override def requestSuccessful( + request: GenericRequest[_, _], + response: ResponseMetadata, + tag: L, + e: Option[ResponseException[_]] + ): F[Unit] = + monadError.eval(delegate.requestSuccessful(request, response, tag, e)) } } diff --git a/core/src/main/scala/sttp/client4/logging/Log.scala b/core/src/main/scala/sttp/client4/logging/Log.scala index 7b51b84160..47e349310a 100644 --- a/core/src/main/scala/sttp/client4/logging/Log.scala +++ b/core/src/main/scala/sttp/client4/logging/Log.scala @@ -5,20 +5,21 @@ import sttp.client4.Response import sttp.model.ResponseMetadata import scala.concurrent.duration.Duration +import sttp.client4.ResponseException /** Performs logging before requests are sent and after requests complete successfully or with an exception. */ trait Log[F[_]] { def beforeRequestSend(request: GenericRequest[_, _]): F[Unit] /** @param exception - * An exception that might occur when processing the response (e.g. parsing). + * A [[ResponseException]] that might occur when handling the response (e.g. parsing). */ def response( request: GenericRequest[_, _], response: ResponseMetadata, responseBody: Option[String], timings: Option[ResponseTimings], - exception: Option[Throwable] + exception: Option[ResponseException[_]] ): F[Unit] def requestException(request: GenericRequest[_, _], timing: Option[Duration], exception: Throwable): F[Unit] @@ -55,7 +56,7 @@ class DefaultLog[F[_]](logger: Logger[F], config: LogConfig, logContext: LogCont response: ResponseMetadata, responseBody: Option[String], timings: Option[ResponseTimings], - exception: Option[Throwable] + exception: Option[ResponseException[_]] ): F[Unit] = { val responseWithBody = Response( responseBody.getOrElse(""), diff --git a/core/src/main/scala/sttp/client4/logging/LoggingBackend.scala b/core/src/main/scala/sttp/client4/logging/LoggingBackend.scala index b7bba25f53..a58438fa94 100644 --- a/core/src/main/scala/sttp/client4/logging/LoggingBackend.scala +++ b/core/src/main/scala/sttp/client4/logging/LoggingBackend.scala @@ -35,7 +35,7 @@ class LoggingBackend[F[_], P]( } { case e: Exception => monad.flatMap { ResponseException.find(e) match { - case Some(re) => log.response(request, re.response, None, tag.map(toResponseTimings), Some(e)) + case Some(re) => log.response(request, re.response, None, tag.map(toResponseTimings), Some(re)) case None => log.requestException(request, tag.map(toResponseTimings).map(_.bodyHandled), e) } } { _ => monad.error(e) } diff --git a/docs/backends/wrappers/custom.md b/docs/backends/wrappers/custom.md index ad079582db..35cc9a5948 100644 --- a/docs/backends/wrappers/custom.md +++ b/docs/backends/wrappers/custom.md @@ -20,7 +20,7 @@ Backends, or backend wrappers can use attributes e.g. for logging, passing a met ## Listener backend -The `sttp.client4.listener.ListenerBackend` can make it easier to create backend wrappers which need to be notified about request lifecycle events: when a request is started, and when it completes either successfully or with an exception. This is possible by implementing a `sttp.client4.listener.RequestListener`. This is how e.g. the [slf4j backend](logging.md) is implemented. +The `sttp.client4.listener.ListenerBackend` can make it easier to create backend wrappers which need to be notified about request lifecycle events: when a request is started, and when it completes either successfully or with an exception. This is possible by implementing a `sttp.client4.listener.RequestListener`. A request listener can associate a value with a request, which will then be passed to the request completion notification methods. diff --git a/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryDefaults.scala b/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryDefaults.scala index 7b03204e75..4d382dc7d3 100644 --- a/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryDefaults.scala +++ b/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryDefaults.scala @@ -8,6 +8,7 @@ import io.opentelemetry.semconv.UrlAttributes import io.opentelemetry.semconv.ErrorAttributes import io.opentelemetry.semconv.ServerAttributes import io.opentelemetry.api.common.AttributesBuilder +import sttp.model.ResponseMetadata object OpenTelemetryDefaults { @@ -32,7 +33,7 @@ object OpenTelemetryDefaults { .put(ServerAttributes.SERVER_PORT, request.uri.port.getOrElse(80)) /** @see https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#http-client */ - def responseAttributes(request: GenericRequest[_, _], response: Response[_]): Attributes = + def responseAttributes(request: GenericRequest[_, _], response: ResponseMetadata): Attributes = Attributes.builder .put(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, response.code.code.toLong: java.lang.Long) .build() diff --git a/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsBackend.scala b/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsBackend.scala index 74420625fe..d4d975b45d 100644 --- a/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsBackend.scala +++ b/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsBackend.scala @@ -9,6 +9,7 @@ import sttp.client4._ import sttp.client4.listener.ListenerBackend import sttp.client4.listener.RequestListener import sttp.client4.wrappers.FollowRedirectsBackend +import sttp.model.ResponseMetadata import sttp.shared.Identity import java.util.concurrent.ConcurrentHashMap @@ -92,7 +93,12 @@ private class OpenTelemetryMetricsListener(config: OpenTelemetryMetricsConfig) (request, config.requestToLatencyHistogramMapper(request).map { _ => config.clock.millis() }) } - override def requestSuccessful(request: GenericRequest[_, _], response: Response[_], tag: Option[Long]): Unit = { + override def requestSuccessful( + request: GenericRequest[_, _], + response: ResponseMetadata, + tag: Option[Long], + e: Option[ResponseException[_]] + ): Unit = { val requestAttributes = config.requestAttributes(request) val responseAttributes = config.responseAttributes(request, response) @@ -113,22 +119,17 @@ private class OpenTelemetryMetricsListener(config: OpenTelemetryMetricsConfig) updateInProgressCounter(request, -1, requestAttributes) } - override def requestException(request: GenericRequest[_, _], tag: Option[Long], e: Exception): Unit = { + override def requestException(request: GenericRequest[_, _], tag: Option[Long], e: Throwable): Unit = { val requestAttributes = config.requestAttributes(request) val errorAttributes = config.errorAttributes(e) - ResponseException.find(e) match { - case Some(re) => - requestSuccessful(request, Response((), re.response.code, request.onlyMetadata), tag) - case _ => - incrementCounter(config.requestToFailureCounterMapper(request, e), errorAttributes) - recordHistogram( - config.requestToLatencyHistogramMapper(request), - tag.map(config.clock.millis() - _), - errorAttributes - ) - updateInProgressCounter(request, -1, requestAttributes) - } + incrementCounter(config.requestToFailureCounterMapper(request, e), errorAttributes) + recordHistogram( + config.requestToLatencyHistogramMapper(request), + tag.map(config.clock.millis() - _), + errorAttributes + ) + updateInProgressCounter(request, -1, requestAttributes) } private def updateInProgressCounter[R, T](request: GenericRequest[T, R], delta: Long, attributes: Attributes): Unit = diff --git a/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsConfig.scala b/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsConfig.scala index c161b182a3..7387b29c93 100644 --- a/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsConfig.scala +++ b/observability/opentelemetry-backend/src/main/scala/sttp/client4/opentelemetry/OpenTelemetryMetricsConfig.scala @@ -7,19 +7,20 @@ import sttp.client4._ import sttp.client4.opentelemetry.OpenTelemetryMetricsBackend._ import java.time.Clock +import sttp.model.ResponseMetadata final case class OpenTelemetryMetricsConfig( meter: Meter, clock: Clock, requestToLatencyHistogramMapper: GenericRequest[_, _] => Option[HistogramCollectorConfig], requestToInProgressCounterMapper: GenericRequest[_, _] => Option[CollectorConfig], - responseToSuccessCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig], - requestToErrorCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig], + responseToSuccessCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig], + requestToErrorCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig], requestToFailureCounterMapper: (GenericRequest[_, _], Throwable) => Option[CollectorConfig], requestToSizeHistogramMapper: GenericRequest[_, _] => Option[HistogramCollectorConfig], - responseToSizeHistogramMapper: (GenericRequest[_, _], Response[_]) => Option[HistogramCollectorConfig], + responseToSizeHistogramMapper: (GenericRequest[_, _], ResponseMetadata) => Option[HistogramCollectorConfig], requestAttributes: GenericRequest[_, _] => Attributes, - responseAttributes: (GenericRequest[_, _], Response[_]) => Attributes, + responseAttributes: (GenericRequest[_, _], ResponseMetadata) => Attributes, errorAttributes: Throwable => Attributes ) @@ -38,10 +39,10 @@ object OpenTelemetryMetricsConfig { ), requestToInProgressCounterMapper: GenericRequest[_, _] => Option[CollectorConfig] = (_: GenericRequest[_, _]) => Some(CollectorConfig(DefaultRequestsActiveCounterName)), - responseToSuccessCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (_: GenericRequest[_, _], _: Response[_]) => Some(CollectorConfig(DefaultSuccessCounterName)), - responseToErrorCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (_: GenericRequest[_, _], _: Response[_]) => Some(CollectorConfig(DefaultErrorCounterName)), + responseToSuccessCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (_: GenericRequest[_, _], _: ResponseMetadata) => Some(CollectorConfig(DefaultSuccessCounterName)), + responseToErrorCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (_: GenericRequest[_, _], _: ResponseMetadata) => Some(CollectorConfig(DefaultErrorCounterName)), requestToFailureCounterMapper: (GenericRequest[_, _], Throwable) => Option[CollectorConfig] = (_: GenericRequest[_, _], _: Throwable) => Some(CollectorConfig(DefaultFailureCounterName)), requestToSizeHistogramMapper: GenericRequest[_, _] => Option[HistogramCollectorConfig] = @@ -53,8 +54,8 @@ object OpenTelemetryMetricsConfig { unit = HistogramCollectorConfig.Bytes ) ), - responseToSizeHistogramMapper: (GenericRequest[_, _], Response[_]) => Option[HistogramCollectorConfig] = - (_: GenericRequest[_, _], _: Response[_]) => + responseToSizeHistogramMapper: (GenericRequest[_, _], ResponseMetadata) => Option[HistogramCollectorConfig] = + (_: GenericRequest[_, _], _: ResponseMetadata) => Some( HistogramCollectorConfig( DefaultResponseSizeHistogramName, @@ -64,7 +65,7 @@ object OpenTelemetryMetricsConfig { ), spanName: GenericRequest[_, _] => String = OpenTelemetryDefaults.spanName _, requestAttributes: GenericRequest[_, _] => Attributes = OpenTelemetryDefaults.requestAttributes _, - responseAttributes: (GenericRequest[_, _], Response[_]) => Attributes = + responseAttributes: (GenericRequest[_, _], ResponseMetadata) => Attributes = OpenTelemetryDefaults.responseAttributes _, errorAttributes: Throwable => Attributes = OpenTelemetryDefaults.errorAttributes _ ): OpenTelemetryMetricsConfig = usingMeter( @@ -99,10 +100,10 @@ object OpenTelemetryMetricsConfig { ), requestToInProgressCounterMapper: GenericRequest[_, _] => Option[CollectorConfig] = (_: GenericRequest[_, _]) => Some(CollectorConfig(DefaultRequestsActiveCounterName)), - responseToSuccessCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (_: GenericRequest[_, _], _: Response[_]) => Some(CollectorConfig(DefaultSuccessCounterName)), - responseToErrorCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (_: GenericRequest[_, _], _: Response[_]) => Some(CollectorConfig(DefaultErrorCounterName)), + responseToSuccessCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (_: GenericRequest[_, _], _: ResponseMetadata) => Some(CollectorConfig(DefaultSuccessCounterName)), + responseToErrorCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (_: GenericRequest[_, _], _: ResponseMetadata) => Some(CollectorConfig(DefaultErrorCounterName)), requestToFailureCounterMapper: (GenericRequest[_, _], Throwable) => Option[CollectorConfig] = (_: GenericRequest[_, _], _: Throwable) => Some(CollectorConfig(DefaultFailureCounterName)), requestToSizeHistogramMapper: GenericRequest[_, _] => Option[HistogramCollectorConfig] = @@ -114,8 +115,8 @@ object OpenTelemetryMetricsConfig { unit = HistogramCollectorConfig.Bytes ) ), - responseToSizeHistogramMapper: (GenericRequest[_, _], Response[_]) => Option[HistogramCollectorConfig] = - (_: GenericRequest[_, _], _: Response[_]) => + responseToSizeHistogramMapper: (GenericRequest[_, _], ResponseMetadata) => Option[HistogramCollectorConfig] = + (_: GenericRequest[_, _], _: ResponseMetadata) => Some( HistogramCollectorConfig( DefaultResponseSizeHistogramName, @@ -124,7 +125,7 @@ object OpenTelemetryMetricsConfig { ) ), requestAttributes: GenericRequest[_, _] => Attributes = OpenTelemetryDefaults.requestAttributes _, - responseAttributes: (GenericRequest[_, _], Response[_]) => Attributes = + responseAttributes: (GenericRequest[_, _], ResponseMetadata) => Attributes = OpenTelemetryDefaults.responseAttributes _, errorAttributes: Throwable => Attributes = OpenTelemetryDefaults.errorAttributes _ ): OpenTelemetryMetricsConfig = diff --git a/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusBackend.scala b/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusBackend.scala index c9222a66d9..1daf8aaba1 100644 --- a/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusBackend.scala +++ b/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusBackend.scala @@ -12,6 +12,7 @@ import sttp.model.StatusCode import sttp.shared.Identity import java.util.concurrent.ConcurrentHashMap +import sttp.model.ResponseMetadata object PrometheusBackend { // Metrics names and model for Prometheus is based on these two specifications: @@ -66,11 +67,11 @@ object PrometheusBackend { new PrometheusListener( (req: GenericRequest[_, _]) => config.requestToHistogramNameMapper(req), (req: GenericRequest[_, _]) => config.requestToInProgressGaugeNameMapper(req), - (rr: (GenericRequest[_, _], Response[_])) => config.responseToSuccessCounterMapper(rr._1, rr._2), - (rr: (GenericRequest[_, _], Response[_])) => config.responseToErrorCounterMapper(rr._1, rr._2), + (rr: (GenericRequest[_, _], ResponseMetadata)) => config.responseToSuccessCounterMapper(rr._1, rr._2), + (rr: (GenericRequest[_, _], ResponseMetadata)) => config.responseToErrorCounterMapper(rr._1, rr._2), (r: (GenericRequest[_, _], Throwable)) => config.requestToFailureCounterMapper(r._1, r._2), (req: GenericRequest[_, _]) => config.requestToSizeSummaryMapper(req), - (rr: (GenericRequest[_, _], Response[_])) => config.responseToSizeSummaryMapper(rr._1, rr._2), + (rr: (GenericRequest[_, _], ResponseMetadata)) => config.responseToSizeSummaryMapper(rr._1, rr._2), config.prometheusRegistry, cacheFor(histograms, config.prometheusRegistry), cacheFor(gauges, config.prometheusRegistry), @@ -103,7 +104,7 @@ object PrometheusBackend { * @return * The modified collector config. The config can be used when configuring the backend using [[apply]]. */ - def addStatusLabel[T <: BaseCollectorConfig](config: T, resp: Response[_]): config.T = { + def addStatusLabel[T <: BaseCollectorConfig](config: T, resp: ResponseMetadata): config.T = { val statusLabel: Option[(String, String)] = if (config.labels.map(_._1.toLowerCase).contains(DefaultStatusLabel)) { None @@ -169,11 +170,11 @@ object PrometheusBackend { class PrometheusListener( requestToHistogramNameMapper: GenericRequest[_, _] => Option[HistogramCollectorConfig], requestToInProgressGaugeNameMapper: GenericRequest[_, _] => Option[CollectorConfig], - requestToSuccessCounterMapper: ((GenericRequest[_, _], Response[_])) => Option[CollectorConfig], - requestToErrorCounterMapper: ((GenericRequest[_, _], Response[_])) => Option[CollectorConfig], - requestToFailureCounterMapper: ((GenericRequest[_, _], Exception)) => Option[CollectorConfig], + requestToSuccessCounterMapper: ((GenericRequest[_, _], ResponseMetadata)) => Option[CollectorConfig], + requestToErrorCounterMapper: ((GenericRequest[_, _], ResponseMetadata)) => Option[CollectorConfig], + requestToFailureCounterMapper: ((GenericRequest[_, _], Throwable)) => Option[CollectorConfig], requestToSizeSummaryMapper: GenericRequest[_, _] => Option[CollectorConfig], - responseToSizeSummaryMapper: ((GenericRequest[_, _], Response[_])) => Option[CollectorConfig], + responseToSizeSummaryMapper: ((GenericRequest[_, _], ResponseMetadata)) => Option[CollectorConfig], prometheusRegistry: PrometheusRegistry, histogramsCache: ConcurrentHashMap[String, Histogram], gaugesCache: ConcurrentHashMap[String, Gauge], @@ -201,21 +202,18 @@ class PrometheusListener( override def requestException( request: GenericRequest[_, _], requestCollectors: RequestCollectors, - e: Exception - ): Unit = - ResponseException.find(e) match { - case Some(re) => - requestSuccessful(request, Response((), re.response.code, request.onlyMetadata), requestCollectors) - case _ => - requestCollectors.maybeTimer.foreach(_.observeDuration()) - requestCollectors.maybeGauge.foreach(_.dec()) - incCounterIfMapped((request, e), requestToFailureCounterMapper) - } + e: Throwable + ): Unit = { + requestCollectors.maybeTimer.foreach(_.observeDuration()) + requestCollectors.maybeGauge.foreach(_.dec()) + incCounterIfMapped((request, e), requestToFailureCounterMapper) + } override def requestSuccessful( request: GenericRequest[_, _], - response: Response[_], - requestCollectors: RequestCollectors + response: ResponseMetadata, + requestCollectors: RequestCollectors, + e: Option[ResponseException[_]] ): Unit = { requestCollectors.maybeTimer.foreach(_.observeDuration()) requestCollectors.maybeGauge.foreach(_.dec()) @@ -238,8 +236,8 @@ class PrometheusListener( private def observeResponseContentLengthSummaryIfMapped( request: GenericRequest[_, _], - response: Response[_], - mapper: ((GenericRequest[_, _], Response[_])) => Option[BaseCollectorConfig] + response: ResponseMetadata, + mapper: ((GenericRequest[_, _], ResponseMetadata)) => Option[BaseCollectorConfig] ): Unit = mapper((request, response)).foreach { data => response.contentLength.map(_.toDouble).foreach { size => diff --git a/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusConfig.scala b/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusConfig.scala index 9b7705ea3a..5114fb7fe1 100644 --- a/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusConfig.scala +++ b/observability/prometheus-backend/src/main/scala/sttp/client4/prometheus/PrometheusConfig.scala @@ -2,19 +2,19 @@ package sttp.client4.prometheus import io.prometheus.metrics.model.registry.PrometheusRegistry import sttp.client4.GenericRequest -import sttp.client4.Response import sttp.client4.prometheus.PrometheusBackend._ +import sttp.model.ResponseMetadata final case class PrometheusConfig( requestToHistogramNameMapper: GenericRequest[_, _] => Option[HistogramCollectorConfig] = (req: GenericRequest[_, _]) => Some(addMethodLabel(HistogramCollectorConfig(DefaultHistogramName), req)), requestToInProgressGaugeNameMapper: GenericRequest[_, _] => Option[CollectorConfig] = (req: GenericRequest[_, _]) => Some(addMethodLabel(CollectorConfig(DefaultRequestsActiveGaugeName), req)), - responseToSuccessCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (req: GenericRequest[_, _], resp: Response[_]) => + responseToSuccessCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (req: GenericRequest[_, _], resp: ResponseMetadata) => Some(addStatusLabel(addMethodLabel(CollectorConfig(DefaultSuccessCounterName), req), resp)), - responseToErrorCounterMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (req: GenericRequest[_, _], resp: Response[_]) => + responseToErrorCounterMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (req: GenericRequest[_, _], resp: ResponseMetadata) => Some(addStatusLabel(addMethodLabel(CollectorConfig(DefaultErrorCounterName), req), resp)), requestToFailureCounterMapper: (GenericRequest[_, _], Throwable) => Option[CollectorConfig] = ( req: GenericRequest[_, _], @@ -22,8 +22,8 @@ final case class PrometheusConfig( ) => Some(addMethodLabel(CollectorConfig(DefaultFailureCounterName), req)), requestToSizeSummaryMapper: GenericRequest[_, _] => Option[CollectorConfig] = (req: GenericRequest[_, _]) => Some(addMethodLabel(CollectorConfig(DefaultRequestSizeName), req)), - responseToSizeSummaryMapper: (GenericRequest[_, _], Response[_]) => Option[CollectorConfig] = - (req: GenericRequest[_, _], resp: Response[_]) => + responseToSizeSummaryMapper: (GenericRequest[_, _], ResponseMetadata) => Option[CollectorConfig] = + (req: GenericRequest[_, _], resp: ResponseMetadata) => Some(addStatusLabel(addMethodLabel(CollectorConfig(DefaultResponseSizeName), req), resp)), prometheusRegistry: PrometheusRegistry = PrometheusRegistry.defaultRegistry ) diff --git a/observability/prometheus-backend/src/test/scala/sttp/client4/prometheus/PrometheusBackendTest.scala b/observability/prometheus-backend/src/test/scala/sttp/client4/prometheus/PrometheusBackendTest.scala index 3fd7a96507..6031d02aa9 100644 --- a/observability/prometheus-backend/src/test/scala/sttp/client4/prometheus/PrometheusBackendTest.scala +++ b/observability/prometheus-backend/src/test/scala/sttp/client4/prometheus/PrometheusBackendTest.scala @@ -19,6 +19,7 @@ import java.util.stream.Collectors import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{blocking, Future} import scala.collection.immutable.Seq +import sttp.model.ResponseMetadata class PrometheusBackendTest extends AnyFlatSpec @@ -429,10 +430,14 @@ class PrometheusBackendTest import sttp.client4.prometheus.PrometheusBackend.{DefaultFailureCounterName, addMethodLabel} val HostLabel = "Host" - def addHostLabel[T <: BaseCollectorConfig](config: T, resp: Response[_]): config.T = { + def addHostLabel[T <: BaseCollectorConfig]( + config: T, + req: GenericRequest[_, _], + resp: ResponseMetadata + ): config.T = { val hostLabel: Option[(String, String)] = if (config.labels.map(_._1.toLowerCase).contains(HostLabel)) None - else Some((HostLabel, resp.request.uri.host.getOrElse("-"))) + else Some((HostLabel, req.uri.host.getOrElse("-"))) config.addLabels(hostLabel.toList) } @@ -440,8 +445,8 @@ class PrometheusBackendTest val backend = PrometheusBackend( backendStub, PrometheusConfig.Default.copy( - responseToErrorCounterMapper = (req: GenericRequest[_, _], resp: Response[_]) => - Some(addHostLabel(addMethodLabel(CollectorConfig(DefaultFailureCounterName), req), resp)) + responseToErrorCounterMapper = (req: GenericRequest[_, _], resp: ResponseMetadata) => + Some(addHostLabel(addMethodLabel(CollectorConfig(DefaultFailureCounterName), req), req, resp)) ) )