diff --git a/metrics/opentelemetry-metrics/src/main/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetrics.scala b/metrics/opentelemetry-metrics/src/main/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetrics.scala index 2e20fd48e1..d52368fd2e 100644 --- a/metrics/opentelemetry-metrics/src/main/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetrics.scala +++ b/metrics/opentelemetry-metrics/src/main/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetrics.scala @@ -7,7 +7,7 @@ import OpenTelemetryMetrics._ import io.opentelemetry.api.OpenTelemetry import sttp.tapir.model.ServerRequest import sttp.tapir.server.interceptor.metrics.MetricsRequestInterceptor -import sttp.tapir.server.metrics.{EndpointMetric, Metric, MetricLabels} +import sttp.tapir.server.metrics.{EndpointMetric, Metric, MetricLabels, OTELMetricLabels} import sttp.tapir.server.model.ServerResponse import java.time.{Duration, Instant} @@ -15,15 +15,15 @@ import java.time.{Duration, Instant} case class OpenTelemetryMetrics[F[_]](meter: Meter, metrics: List[Metric[F, _]]) { /** Registers a `request_active{path, method}` up-down-counter (assuming default labels). */ - def addRequestsActive(labels: MetricLabels = MetricLabels.Default): OpenTelemetryMetrics[F] = + def addRequestsActive(labels: MetricLabels = OTELMetricLabels.Default): OpenTelemetryMetrics[F] = copy(metrics = metrics :+ requestActive(meter, labels)) /** Registers a `request_total{path, method, status}` counter (assuming default labels). */ - def addRequestsTotal(labels: MetricLabels = MetricLabels.Default): OpenTelemetryMetrics[F] = + def addRequestsTotal(labels: MetricLabels = OTELMetricLabels.Default): OpenTelemetryMetrics[F] = copy(metrics = metrics :+ requestTotal(meter, labels)) /** Registers a `request_duration_seconds{path, method, status, phase}` histogram (assuming default labels). */ - def addRequestsDuration(labels: MetricLabels = MetricLabels.Default): OpenTelemetryMetrics[F] = + def addRequestsDuration(labels: MetricLabels = OTELMetricLabels.Default): OpenTelemetryMetrics[F] = copy(metrics = metrics :+ requestDuration(meter, labels)) /** Registers a custom metric. */ @@ -50,7 +50,7 @@ object OpenTelemetryMetrics { * measured separately up to the point where the headers are determined, and then once again when the whole response body is complete. */ def default[F[_]](otel: OpenTelemetry): OpenTelemetryMetrics[F] = - default(defaultMeter(otel), MetricLabels.Default) + default(defaultMeter(otel), OTELMetricLabels.Default) /** Registers default metrics (see other variants) using custom labels. */ def default[F[_]](otel: OpenTelemetry, labels: MetricLabels): OpenTelemetryMetrics[F] = default(defaultMeter(otel), labels) @@ -64,10 +64,10 @@ object OpenTelemetryMetrics { * Status is by default the status code class (1xx, 2xx, etc.), and phase can be either `headers` or `body` - request duration is * measured separately up to the point where the headers are determined, and then once again when the whole response body is complete. */ - def default[F[_]](meter: Meter): OpenTelemetryMetrics[F] = default(meter, MetricLabels.Default) + def default[F[_]](meter: Meter): OpenTelemetryMetrics[F] = default(meter, OTELMetricLabels.Default) /** Registers default metrics (see other variants) using custom labels. */ - def default[F[_]](meter: Meter, labels: MetricLabels = MetricLabels.Default): OpenTelemetryMetrics[F] = + def default[F[_]](meter: Meter, labels: MetricLabels = OTELMetricLabels.Default): OpenTelemetryMetrics[F] = OpenTelemetryMetrics( meter, List[Metric[F, _]]( @@ -87,21 +87,8 @@ object OpenTelemetryMetrics { onRequest = (req, counter, m) => { m.unit { EndpointMetric() - .onEndpointRequest { ep => - m.eval { - val attrs = asOpenTelemetryAttributes(labels, ep, req) - println(s"[DEBUG] Incrementing active requests with attributes: $attrs") - - counter.add(1, attrs) - } - } - .onResponseBody { (ep, _) => - m.eval { - val attrs = asOpenTelemetryAttributes(labels, ep, req) - println(s"[DEBUG] Decrementing active requests with attributes: $attrs") - counter.add(-1, attrs) - } - } + .onEndpointRequest { ep => m.eval(counter.add(1, asOpenTelemetryAttributes(labels, ep, req))) } + .onResponseBody { (ep, _) => m.eval(counter.add(-1, asOpenTelemetryAttributes(labels, ep, req))) } .onException { (ep, _) => m.eval(counter.add(-1, asOpenTelemetryAttributes(labels, ep, req))) } } } @@ -119,11 +106,9 @@ object OpenTelemetryMetrics { EndpointMetric() .onResponseBody { (ep, res) => m.eval { - val otLabels: Attributes = + val otLabels = merge(asOpenTelemetryAttributes(labels, ep, req), asOpenTelemetryAttributes(labels, Right(res), None)) - println(s"[DEBUG] Incrementing total requests with labels: $otLabels") - counter.add(1, otLabels) } } @@ -138,7 +123,6 @@ object OpenTelemetryMetrics { } ) - def requestDuration[F[_]](meter: Meter, labels: MetricLabels): Metric[F, DoubleHistogram] = Metric[F, DoubleHistogram]( meter @@ -149,7 +133,7 @@ object OpenTelemetryMetrics { onRequest = (req, recorder, m) => m.eval { val requestStart = Instant.now() - def duration = Duration.between(requestStart, Instant.now()).toMillis.toDouble / 1000.0 + def duration = Duration.between(requestStart, Instant.now()).toMillis.toDouble EndpointMetric() .onResponseHeaders { (ep, res) => m.eval { diff --git a/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala b/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala index 4581b09594..093cd8d85f 100644 --- a/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala +++ b/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala @@ -101,8 +101,8 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { "GET", AttributeKey.stringKey("path"), "/person", - AttributeKey.longKey("http.response.status_code").asInstanceOf[AttributeKey[Long]], // Explicitly cast to Long - 200L + AttributeKey.stringKey("http.response.status_code"), + "200" ) && dp.getValue == 2 => true case dp @@ -111,8 +111,8 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { "GET", AttributeKey.stringKey("path"), "/person", - AttributeKey.longKey("http.response.status_code").asInstanceOf[AttributeKey[Long]], // Explicitly cast to Long - 400L + AttributeKey.stringKey("http.response.status_code"), + "400" ) && dp.getValue == 2 => true case _ => false @@ -154,8 +154,10 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { "GET", AttributeKey.stringKey("path"), "/person", - AttributeKey.longKey("http.response.status_code").asInstanceOf[AttributeKey[Long]], // Explicitly cast to Long - 200L + AttributeKey.stringKey("http.response.status_code"), + "200", + AttributeKey.stringKey("phase"), + "body" ) ) } @@ -209,8 +211,8 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { "GET", AttributeKey.stringKey("path"), "/person", - AttributeKey.longKey("http.response.status_code").asInstanceOf[AttributeKey[Long]], // Explicitly cast to Long - 500L + AttributeKey.stringKey("http.response.status_code"), + "500" ) point.getValue shouldBe 1 } diff --git a/server/core/src/main/scala/sttp/tapir/server/metrics/Metric.scala b/server/core/src/main/scala/sttp/tapir/server/metrics/Metric.scala index 8c90e9fb3b..05bb9762ed 100644 --- a/server/core/src/main/scala/sttp/tapir/server/metrics/Metric.scala +++ b/server/core/src/main/scala/sttp/tapir/server/metrics/Metric.scala @@ -64,3 +64,22 @@ object MetricLabels { ) ) } + +object OTELMetricLabels { + + /** Labels request by path and http.request.method, response by http.response.status_code */ + lazy val Default: MetricLabels = MetricLabels( + forRequest = List( + "http.request.method" -> { case (_, req) => req.method.method }, + "path" -> { case (ep, _) => ep.showPathTemplate(showQueryParam = None) } + ), + forResponse = List( + "http.response.status_code" -> { + // OpenTelemetry-compliant + case Right(r) => r.code.code.toString + // Default to 500 for exceptions + case Left(_) => "500" + } + ) + ) +}