From eeebbd3e2c5c54aa3e6f39c4c9a73d97cee3764c Mon Sep 17 00:00:00 2001 From: Pask Date: Sun, 1 May 2022 19:21:47 +0200 Subject: [PATCH 01/13] Rename open-telemetry-backend to open-telemetry-tracing-backend --- build.sbt | 8 ++++---- .../tracing/OpenTelemetryTracingBackend.scala} | 8 ++++---- .../tracing/OpenTelemetryTracingBackendTest.scala} | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) rename metrics/{open-telemetry-backend/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryBackend.scala => open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala} (92%) rename metrics/{open-telemetry-backend/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryBackendTest.scala => open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala} (95%) diff --git a/build.sbt b/build.sbt index a5bb877630..a1ce93bf03 100644 --- a/build.sbt +++ b/build.sbt @@ -210,7 +210,7 @@ lazy val allAggregates = projectsWithOptionalNative ++ playJson.projectRefs ++ openTracingBackend.projectRefs ++ prometheusBackend.projectRefs ++ - openTelemetryBackend.projectRefs ++ + openTelemetryTracingBackend.projectRefs ++ finagleBackend.projectRefs ++ armeriaBackend.projectRefs ++ armeriaScalazBackend.projectRefs ++ @@ -904,10 +904,10 @@ lazy val openTracingBackend = (projectMatrix in file("metrics/open-tracing-backe .jvmPlatform(scalaVersions = scala2 ++ scala3) .dependsOn(core) -lazy val openTelemetryBackend = (projectMatrix in file("metrics/open-telemetry-backend")) +lazy val openTelemetryTracingBackend = (projectMatrix in file("metrics/open-telemetry-tracing-backend")) .settings(commonJvmSettings) .settings( - name := "opentelemetry-backend", + name := "opentelemetry-tracing-backend", libraryDependencies ++= Seq( "io.opentelemetry" % "opentelemetry-api" % "1.13.0", "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.13.0" % Test @@ -1043,7 +1043,7 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo http4sBackend, openTracingBackend, prometheusBackend, - openTelemetryBackend, + openTelemetryTracingBackend, slf4jBackend ) .jvmPlatform(scalaVersions = List(scala2_13)) diff --git a/metrics/open-telemetry-backend/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryBackend.scala b/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala similarity index 92% rename from metrics/open-telemetry-backend/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryBackend.scala rename to metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala index e47acbeb8e..49996f5704 100644 --- a/metrics/open-telemetry-backend/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryBackend.scala +++ b/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala @@ -1,4 +1,4 @@ -package sttp.client3.opentelemetry +package sttp.client3.opentelemetry.tracing import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.common.{AttributeKey, Attributes} @@ -12,7 +12,7 @@ import sttp.monad.syntax._ import scala.collection.mutable -private class OpenTelemetryBackend[F[_], P]( +private class OpenTelemetryTracingBackend[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, spanName: Request[_, _] => String @@ -72,11 +72,11 @@ private class OpenTelemetryBackend[F[_], P]( } -object OpenTelemetryBackend { +object OpenTelemetryTracingBackend { def apply[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, spanName: Request[_, _] => String = request => s"HTTP ${request.method.method}" ): SttpBackend[F, P] = - new OpenTelemetryBackend[F, P](delegate, openTelemetry, spanName) + new OpenTelemetryTracingBackend[F, P](delegate, openTelemetry, spanName) } diff --git a/metrics/open-telemetry-backend/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryBackendTest.scala b/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala similarity index 95% rename from metrics/open-telemetry-backend/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryBackendTest.scala rename to metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala index ec6fcdeeaf..ce45a3b4cf 100644 --- a/metrics/open-telemetry-backend/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryBackendTest.scala +++ b/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala @@ -1,4 +1,4 @@ -package sttp.client3.opentelemetry +package sttp.client3.opentelemetry.tracing import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator import io.opentelemetry.context.propagation.ContextPropagators @@ -18,7 +18,7 @@ import scala.collection.JavaConverters._ import scala.collection.mutable import scala.util.Try -class OpenTelemetryBackendTest extends AnyFlatSpec with Matchers with BeforeAndAfter { +class OpenTelemetryTracingBackendTest extends AnyFlatSpec with Matchers with BeforeAndAfter { private val recordedRequests = mutable.ListBuffer[Request[_, _]]() From bc7bbbf5995ec8359c523086d76f03005f3cae77 Mon Sep 17 00:00:00 2001 From: Pask Date: Sun, 1 May 2022 19:36:41 +0200 Subject: [PATCH 02/13] Added open-telemetry-metrics-backend --- build.sbt | 13 +++ .../metrics/OpenTelemetryMetricsBackend.scala | 32 +++++++ .../OpenTelemetryMetricsBackendTest.scala | 83 +++++++++++++++++++ .../OpenTelemetryTracingBackendTest.scala | 2 +- 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala create mode 100644 metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala diff --git a/build.sbt b/build.sbt index a1ce93bf03..d7f6ccee04 100644 --- a/build.sbt +++ b/build.sbt @@ -917,6 +917,19 @@ lazy val openTelemetryTracingBackend = (projectMatrix in file("metrics/open-tele .jvmPlatform(scalaVersions = List(scala2_12, scala2_13) ++ scala3) .dependsOn(core) +lazy val openTelemetryMetricsBackend = (projectMatrix in file("metrics/open-telemetry-metrics-backend")) + .settings(commonJvmSettings) + .settings( + name := "opentelemetry-metrics-backend", + libraryDependencies ++= Seq( + "io.opentelemetry" % "opentelemetry-api" % "1.13.0", + "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.13.0" % Test + ), + scalaTest + ) + .jvmPlatform(scalaVersions = List(scala2_12, scala2_13) ++ scala3) + .dependsOn(core) + lazy val scribeBackend = (projectMatrix in file("logging/scribe")) .settings(commonJvmSettings) .settings( diff --git a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala b/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala new file mode 100644 index 0000000000..990c81c2e6 --- /dev/null +++ b/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala @@ -0,0 +1,32 @@ +package sttp.client3.opentelemetry.metrics + +import io.opentelemetry.api.OpenTelemetry +import sttp.capabilities.Effect +import sttp.client3._ +import sttp.monad.MonadError + +private class OpenTelemetryMetricsBackend[F[_], P]( + delegate: SttpBackend[F, P], + openTelemetry: OpenTelemetry, + spanName: Request[_, _] => String +) extends SttpBackend[F, P] { + + private implicit val _monad: MonadError[F] = responseMonad + type PE = P with Effect[F] + + def send[T, R >: PE](request: Request[T, R]): F[Response[T]] = ??? + + override def close(): F[Unit] = delegate.close() + + override def responseMonad: MonadError[F] = delegate.responseMonad + +} + +object OpenTelemetryMetricsBackend { + def apply[F[_], P]( + delegate: SttpBackend[F, P], + openTelemetry: OpenTelemetry, + spanName: Request[_, _] => String = request => s"HTTP ${request.method.method}" + ): SttpBackend[F, P] = + new OpenTelemetryMetricsBackend[F, P](delegate, openTelemetry, spanName) +} diff --git a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala b/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala new file mode 100644 index 0000000000..cf21f3fbc5 --- /dev/null +++ b/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala @@ -0,0 +1,83 @@ +package sttp.client3.opentelemetry.metrics + +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.context.propagation.ContextPropagators +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor +import org.scalatest.BeforeAndAfter +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import sttp.client3.monad.IdMonad +import sttp.client3.testing.SttpBackendStub +import sttp.client3.{Identity, Request, Response, SttpBackend, UriContext, basicRequest} +import sttp.model.StatusCode + +import scala.collection.JavaConverters._ +import scala.collection.mutable +import scala.util.Try + +class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with BeforeAndAfter { + + private val recordedRequests = mutable.ListBuffer[Request[_, _]]() + + private val spanExporter = InMemorySpanExporter.create() + + private val mockTracer: SdkTracerProvider = + SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)).build() + + private val mockOpenTelemetry = OpenTelemetrySdk + .builder() + .setTracerProvider(mockTracer) + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .buildAndRegisterGlobal() + + private val backend: SttpBackend[Identity, Any] = + OpenTelemetryMetricsBackend( + SttpBackendStub.apply(IdMonad).whenRequestMatchesPartial { + case r if r.uri.toString.contains("echo") => + recordedRequests += r + Response.ok("") + case r if r.uri.toString.contains("error") => + throw new RuntimeException("something went wrong") + }, + mockOpenTelemetry + ) + + before { + recordedRequests.clear() + spanExporter.reset() + } + + "ZioTelemetryOpenTelemetryBackend" should "record spans for requests" in { + val response = basicRequest.post(uri"http://stub/echo").send(backend) + response.code shouldBe StatusCode.Ok + + val spans = spanExporter.getFinishedSpanItems.asScala + spans should have size 1 + spans.head.getName shouldBe "HTTP POST" + } + + it should "propagate span" in { + val response = basicRequest.post(uri"http://stub/echo").send(backend) + response.code shouldBe StatusCode.Ok + + val spans = spanExporter.getFinishedSpanItems.asScala + spans should have size 1 + + val spanId = spans.head.getSpanId + val traceId = spans.head.getTraceId + recordedRequests(0).header("traceparent") shouldBe Some(s"00-${traceId}-${spanId}-01") + } + + it should "set span status in case of error" in { + Try(basicRequest.post(uri"http://stub/error").send(backend)) + + val spans = spanExporter.getFinishedSpanItems.asScala + spans should have size 1 + + spans.head.getStatus.getStatusCode shouldBe io.opentelemetry.api.trace.StatusCode.ERROR + } + +} diff --git a/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala b/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala index ce45a3b4cf..6ffbe9b5ae 100644 --- a/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala +++ b/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala @@ -34,7 +34,7 @@ class OpenTelemetryTracingBackendTest extends AnyFlatSpec with Matchers with Bef .buildAndRegisterGlobal() private val backend: SttpBackend[Identity, Any] = - OpenTelemetryBackend( + OpenTelemetryTracingBackend( SttpBackendStub.apply(IdMonad).whenRequestMatchesPartial { case r if r.uri.toString.contains("echo") => recordedRequests += r From a0cd710569ce1083e595423ecbe4eaf5a5971fbf Mon Sep 17 00:00:00 2001 From: Pask Date: Sun, 1 May 2022 19:43:17 +0200 Subject: [PATCH 03/13] Removed open tracing backend --- build.sbt | 13 -- .../opentracing/OpenTracingBackend.scala | 103 ----------- .../opentracing/RequestBuilderCarrier.scala | 19 -- .../opentracing/OpenTracingBackendTest.scala | 174 ------------------ 4 files changed, 309 deletions(-) delete mode 100644 metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/OpenTracingBackend.scala delete mode 100644 metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/RequestBuilderCarrier.scala delete mode 100644 metrics/open-tracing-backend/src/test/scala/sttp/client3/opentracing/OpenTracingBackendTest.scala diff --git a/build.sbt b/build.sbt index d7f6ccee04..9df5ebe218 100644 --- a/build.sbt +++ b/build.sbt @@ -891,19 +891,6 @@ lazy val prometheusBackend = (projectMatrix in file("metrics/prometheus-backend" .jvmPlatform(scalaVersions = scala2 ++ scala3) .dependsOn(core) -lazy val openTracingBackend = (projectMatrix in file("metrics/open-tracing-backend")) - .settings(commonJvmSettings) - .settings( - name := "opentracing-backend", - libraryDependencies ++= Seq( - "io.opentracing" % "opentracing-api" % "0.33.0", - "io.opentracing" % "opentracing-mock" % "0.33.0" % Test - ), - scalaTest - ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) - .dependsOn(core) - lazy val openTelemetryTracingBackend = (projectMatrix in file("metrics/open-telemetry-tracing-backend")) .settings(commonJvmSettings) .settings( diff --git a/metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/OpenTracingBackend.scala b/metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/OpenTracingBackend.scala deleted file mode 100644 index 08ad64923e..0000000000 --- a/metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/OpenTracingBackend.scala +++ /dev/null @@ -1,103 +0,0 @@ -package sttp.client3.opentracing - -import io.opentracing.tag.Tags -import io.opentracing.{Span, SpanContext, Tracer} -import io.opentracing.propagation.Format -import io.opentracing.Tracer.SpanBuilder -import sttp.capabilities.Effect -import sttp.monad.MonadError -import sttp.monad.syntax._ -import sttp.client3.{FollowRedirectsBackend, Request, RequestT, Response, SttpBackend} -import sttp.client3.opentracing.OpenTracingBackend._ - -import scala.collection.JavaConverters._ - -class OpenTracingBackend[F[_], P] private (delegate: SttpBackend[F, P], tracer: Tracer) extends SttpBackend[F, P] { - - private implicit val _monad: MonadError[F] = responseMonad - type PE = P with Effect[F] - - override def send[T, R >: PE](request: Request[T, R]): F[Response[T]] = - responseMonad - .eval { - val spanBuilderTransformer: SpanBuilderTransformer = - request - .tag(OpenTracingBackend.SpanBuilderTransformerRequestTag) - .collectFirst { case f: SpanBuilderTransformer => - f - } - .getOrElse(identity) - val span = spanBuilderTransformer( - tracer - .buildSpan( - request - .tag(OpenTracingBackend.OperationIdRequestTag) - .getOrElse("default-operation-id") - .toString - ) - ).withTag(Tags.SPAN_KIND, Tags.SPAN_KIND_CLIENT) - .withTag(Tags.HTTP_METHOD, request.method.method) - .withTag(Tags.HTTP_URL, request.uri.toString) - .withTag(Tags.COMPONENT, "sttp3-client") - .start() - - request - .tag(OpenTracingBackend.SpanTransformerRequestTag) - .collectFirst { case spanTranformer: SpanTransformer => spanTranformer(span) } - .getOrElse(span) - } - .flatMap { span => - val requestBuilderAdapter = new RequestBuilderAdapter(request) - tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new RequestBuilderCarrier(requestBuilderAdapter)) - responseMonad.handleError( - delegate.send(requestBuilderAdapter.request).map { response => - span - .setTag(Tags.HTTP_STATUS, Integer.valueOf(response.code.code)) - .finish() - response - } - ) { case e => - span - .setTag(Tags.ERROR, java.lang.Boolean.TRUE) - .log(Map("event" -> Tags.ERROR.getKey, "error.object" -> e).asJava) - .finish() - responseMonad.error(e) - } - } - - override def close(): F[Unit] = delegate.close() - - override def responseMonad: MonadError[F] = delegate.responseMonad -} - -object OpenTracingBackend { - private val OperationIdRequestTag = "io.opentracing.tag.sttp.operationId" - private val SpanBuilderTransformerRequestTag = "io.opentracing.tag.sttp.span.builder.transformer" - private val SpanTransformerRequestTag = "io.opentracing.tag.sttp.span.transformer" - type SpanBuilderTransformer = SpanBuilder => SpanBuilder - type SpanTransformer = Span => Span - - implicit class RichRequest[U[_], T, R](request: RequestT[U, T, R]) { - def tagWithOperationId(operationId: String): RequestT[U, T, R] = - request.tag(OperationIdRequestTag, operationId) - - def tagWithTransformSpan(transformSpan: SpanTransformer): RequestT[U, T, R] = - request.tag(SpanTransformerRequestTag, transformSpan) - - /** Sets transformation of SpanBuilder used by OpenTracing backend to create Span this request execution. */ - def tagWithTransformSpanBuilder(transformSpan: SpanBuilderTransformer): RequestT[U, T, R] = - request.tag(SpanBuilderTransformerRequestTag, transformSpan) - - /** Sets parent Span for OpenTracing Span of this request execution. */ - def setOpenTracingParentSpan(parent: Span): RequestT[U, T, R] = - tagWithTransformSpanBuilder(_.asChildOf(parent)) - - /** Sets parent SpanContext for OpenTracing Span of this request execution. */ - def setOpenTracingParentSpanContext(parentSpanContext: SpanContext): RequestT[U, T, R] = - tagWithTransformSpanBuilder(_.asChildOf(parentSpanContext)) - } - - def apply[F[_], P](delegate: SttpBackend[F, P], tracer: Tracer): SttpBackend[F, P] = { - new FollowRedirectsBackend[F, P](new OpenTracingBackend(delegate, tracer)) - } -} diff --git a/metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/RequestBuilderCarrier.scala b/metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/RequestBuilderCarrier.scala deleted file mode 100644 index 3ccfde06f9..0000000000 --- a/metrics/open-tracing-backend/src/main/scala/sttp/client3/opentracing/RequestBuilderCarrier.scala +++ /dev/null @@ -1,19 +0,0 @@ -package sttp.client3.opentracing - -import java.util - -import io.opentracing.propagation.TextMap -import sttp.client3.Request - -class RequestBuilderCarrier(requestBuilder: RequestBuilderAdapter[_, _]) extends TextMap { - override def put(key: String, value: String): Unit = requestBuilder.header(key, value) - - override def iterator(): util.Iterator[util.Map.Entry[String, String]] = - throw new UnsupportedOperationException("carrier is write-only") -} - -class RequestBuilderAdapter[T, S](var request: Request[T, S]) { - def header(k: String, v: String): Unit = { - request = request.header(k, v) - } -} diff --git a/metrics/open-tracing-backend/src/test/scala/sttp/client3/opentracing/OpenTracingBackendTest.scala b/metrics/open-tracing-backend/src/test/scala/sttp/client3/opentracing/OpenTracingBackendTest.scala deleted file mode 100644 index e0564d306e..0000000000 --- a/metrics/open-tracing-backend/src/test/scala/sttp/client3/opentracing/OpenTracingBackendTest.scala +++ /dev/null @@ -1,174 +0,0 @@ -package sttp.client3.opentracing - -import io.opentracing.mock.MockTracer -import io.opentracing.tag.Tags -import org.scalatest.BeforeAndAfter -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import sttp.client3.monad.IdMonad -import sttp.client3.testing.SttpBackendStub -import sttp.client3.{Identity, SttpBackend, _} -import sttp.client3.opentracing.OpenTracingBackend._ -import sttp.model.{Method, StatusCode} - -import scala.collection.JavaConverters._ -import scala.collection.mutable -import scala.util.Try - -class OpenTracingBackendTest extends AnyFlatSpec with Matchers with BeforeAndAfter { - - private val recordedRequests = mutable.ListBuffer[Request[_, _]]() - private val tracer = new MockTracer() - private val backend: SttpBackend[Identity, Any] = - OpenTracingBackend[Identity, Nothing]( - SttpBackendStub.apply(IdMonad).whenRequestMatchesPartial { - case r if r.uri.toString.contains("echo") => - recordedRequests += r - Response.ok("") - case r if r.uri.toString.contains("error") => - throw new RuntimeException("something went wrong") - }, - tracer - ) - - before { - recordedRequests.clear() - tracer.reset() - } - - "OpenTracingBackendTest" should "propagate span" in { - val response = basicRequest.post(uri"http://stub/echo").send(backend) - response.code shouldBe StatusCode.Ok - - val spans = tracer.finishedSpans() - spans should have size 1 - val spanId = spans.asScala.head.context().spanId() - val traceId = spans.asScala.head.context().traceId() - recordedRequests(0).headers.collectFirst { case h if h.name == "spanid" => h.value } shouldBe Some(spanId.toString) - recordedRequests(0).headers.collectFirst { case h if h.name == "traceid" => h.value } shouldBe Some( - traceId.toString - ) - } - - it should "make child of current span" in { - val operationName = "my-custom-ops-id" - val span = tracer.buildSpan(operationName).start() - tracer.activateSpan(span) - val response = basicRequest.post(uri"http://stub/echo").send(backend) - response.code shouldBe StatusCode.Ok - span.finish() - - val spans = tracer.finishedSpans() - spans should have size 2 - val spanId = span.context().spanId() - spans.asScala(0).parentId() shouldBe spanId - spans.asScala(1).context().spanId() shouldBe spanId - spans.asScala(1).operationName() shouldBe operationName - } - - it should "make child of given span if defined" in { - val activeSpan = tracer.buildSpan("active-op").start() - tracer.activateSpan(activeSpan) - val parentSpan = tracer.buildSpan("parent-op").start() - val response = basicRequest - .post(uri"http://stub/echo") - .tagWithOperationId("overridden-op") - .setOpenTracingParentSpan(parentSpan) - .send(backend) - response.code shouldBe StatusCode.Ok - parentSpan.finish() - - val spans = tracer.finishedSpans().asScala - - spans should have size 2 - spans(0).parentId shouldBe spans(1).context.spanId - spans(1).operationName shouldBe "parent-op" - spans(0).operationName shouldBe "overridden-op" - } - - it should "make child of given span context if defined" in { - val activeSpan = tracer.buildSpan("active-op").start() - tracer.activateSpan(activeSpan) - val response = basicRequest - .post(uri"http://stub/echo") - .tagWithOperationId("overridden-op") - .setOpenTracingParentSpanContext(activeSpan.context()) - .send(backend) - response.code shouldBe StatusCode.Ok - - val spans = tracer.finishedSpans().asScala - - spans should have size 1 - spans(0).parentId() shouldBe activeSpan.context().spanId() - spans(0).operationName shouldBe "overridden-op" - } - - it should "propagate additional metadata" in { - val span = tracer.buildSpan("my-custom-ops-id").start() - span.setBaggageItem("baggage1", "hello") - tracer.activateSpan(span) - val response = basicRequest.post(uri"http://stub/echo").send(backend) - response.code shouldBe StatusCode.Ok - span.finish() - - recordedRequests.head.headers.collectFirst { case h if h.name == "baggage-baggage1" => h.value } shouldBe Some( - "hello" - ) - } - - it should "add status code when response is not error" in { - val response = basicRequest.post(uri"http://stub/echo").send(backend) - response.code shouldBe StatusCode.Ok - - tracer - .finishedSpans() - .asScala - .head - .tags() - .asScala(Tags.HTTP_STATUS.getKey) - .asInstanceOf[Int] shouldBe StatusCode.Ok.code - } - - it should "add logs if response is error" in { - Try(basicRequest.post(uri"http://stub/error").send(backend)) - - val finishedSpan = tracer - .finishedSpans() - .asScala - .head - finishedSpan - .logEntries() - .asScala - .head - .fields() - .asScala("error.object") - .asInstanceOf[RuntimeException] - .getMessage shouldBe "something went wrong" - finishedSpan.tags().asScala(Tags.ERROR.getKey) shouldBe java.lang.Boolean.TRUE - } - - it should "add standard tags during http call" in { - val url = uri"http://stub/echo" - basicRequest.post(url).send(backend) - val tags = tracer.finishedSpans().asScala.head.tags().asScala - tags(Tags.HTTP_METHOD.getKey) shouldBe Method.POST.method - tags(Tags.HTTP_URL.getKey) shouldBe url.toJavaUri.toString - tags(Tags.SPAN_KIND.getKey) shouldBe Tags.SPAN_KIND_CLIENT - tags(Tags.COMPONENT.getKey) shouldBe "sttp3-client" - } - - it should "be able to adjust span" in { - import OpenTracingBackend._ - - val url = uri"http://stub/echo" - basicRequest - .post(url) - .tagWithTransformSpan(_.setTag("custom-tag", "custom-value").setOperationName("new-name").log("my-event")) - .send(backend) - - val span = tracer.finishedSpans().asScala.head - span.tags().get("custom-tag") shouldBe "custom-value" - span.operationName() shouldBe "new-name" - span.logEntries().get(0).fields().asScala shouldBe Map("event" -> "my-event") - } -} From 6348f01ae2f990624150af88a6571af96ecbc94f Mon Sep 17 00:00:00 2001 From: Pask Date: Mon, 2 May 2022 11:14:23 +0200 Subject: [PATCH 04/13] Basic implmention of metrics backend --- build.sbt | 2 - .../metrics/OpenTelemetryMetricsBackend.scala | 151 +++++++++++++++++- 2 files changed, 146 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 9df5ebe218..069f52726e 100644 --- a/build.sbt +++ b/build.sbt @@ -208,7 +208,6 @@ lazy val allAggregates = projectsWithOptionalNative ++ jsoniter.projectRefs ++ sprayJson.projectRefs ++ playJson.projectRefs ++ - openTracingBackend.projectRefs ++ prometheusBackend.projectRefs ++ openTelemetryTracingBackend.projectRefs ++ finagleBackend.projectRefs ++ @@ -1041,7 +1040,6 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo okhttpBackend, // okhttpMonixBackend, http4sBackend, - openTracingBackend, prometheusBackend, openTelemetryTracingBackend, slf4jBackend diff --git a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala b/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala index 990c81c2e6..0fead98734 100644 --- a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala +++ b/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala @@ -1,32 +1,173 @@ package sttp.client3.opentelemetry.metrics import io.opentelemetry.api.OpenTelemetry +import io.opentelemetry.api.metrics.{DoubleHistogram, LongCounter, LongUpDownCounter, Meter} import sttp.capabilities.Effect import sttp.client3._ import sttp.monad.MonadError +import sttp.monad.syntax.MonadErrorOps + +import java.util.concurrent.ConcurrentHashMap private class OpenTelemetryMetricsBackend[F[_], P]( + requestToInProgressCounterNameMapper: Request[_, _] => Option[CollectorConfig], + requestToSuccessCounterMapper: Response[_] => Option[CollectorConfig], + requestToErrorCounterMapper: Response[_] => Option[CollectorConfig], + requestToFailureCounterMapper: (Request[_, _], Throwable) => Option[CollectorConfig], + requestToSizeHistogramMapper: Request[_, _] => Option[CollectorConfig], + responseToSizeHistogramMapper: Response[_] => Option[CollectorConfig], + memUsageGaugeName: Option[CollectorConfig], delegate: SttpBackend[F, P], - openTelemetry: OpenTelemetry, - spanName: Request[_, _] => String + openTelemetry: OpenTelemetry ) extends SttpBackend[F, P] { + private val meter: Meter = openTelemetry + .meterBuilder("sttp3-client") + .setInstrumentationVersion("1.0.0") + .build() + + memUsageGaugeName + .foreach(config => { + meter + .gaugeBuilder(config.name) + .setDescription("Memory Usage") + .setUnit("byte") + .buildWithCallback(_.record(Runtime.getRuntime.totalMemory())) + }) + + private val counters: ConcurrentHashMap[String, LongCounter] = new ConcurrentHashMap[String, LongCounter] + private val histograms = new ConcurrentHashMap[String, DoubleHistogram]() + private val upAndDownCounter = new ConcurrentHashMap[String, LongUpDownCounter]() + private implicit val _monad: MonadError[F] = responseMonad type PE = P with Effect[F] - def send[T, R >: PE](request: Request[T, R]): F[Response[T]] = ??? + def send[T, R >: PE](request: Request[T, R]): F[Response[T]] = { + responseMonad + .eval(before(request)) + .flatMap(_ => + responseMonad.handleError( + delegate.send(request).map { response => + if (response.isSuccess) { + incrementCounter(requestToSuccessCounterMapper(response)) + } else { + incrementCounter(requestToErrorCounterMapper(response)) + } + decrementUpDownCounter(request) + responseToSizeHistogramMapper(response) + .map(config => getOrCreateMetric(histograms, config, createNewHistogram)) + .foreach(histogram => response.contentLength.map(_.toDouble).foreach(histogram.record)) + response + } + ) { case e => + after(request, e) + responseMonad.error(e) + } + ) + } + + private def before[R >: PE, T](request: Request[T, R]): Unit = { + requestToInProgressCounterNameMapper(request) + .map(config => getOrCreateMetric(upAndDownCounter, config, createNewUpDownCounter)) + .foreach(_.add(1)) + requestToSizeHistogramMapper(request) + .map(config => getOrCreateMetric(histograms, config, createNewHistogram)) + .foreach(histogram => (request.contentLength: Option[Long]).map(_.toDouble).foreach(histogram.record)) + } + + private def after[R >: PE, T](request: Request[T, R], e: Throwable): Unit = { + incrementCounter(requestToFailureCounterMapper(request, e)) + decrementUpDownCounter(request) + } + + private def decrementUpDownCounter[R >: PE, T](request: Request[T, R]): Unit = { + requestToInProgressCounterNameMapper(request) + .map(config => getOrCreateMetric(upAndDownCounter, config, createNewUpDownCounter)) + .foreach(_.add(-1)) + } + + private def incrementCounter(collectorConfig: Option[CollectorConfig]): Unit = { + collectorConfig + .map(config => getOrCreateMetric(counters, config, createNewCounter)) + .foreach(_.add(1)) + } override def close(): F[Unit] = delegate.close() override def responseMonad: MonadError[F] = delegate.responseMonad + private def getOrCreateMetric[T]( + cache: ConcurrentHashMap[String, T], + data: CollectorConfig, + create: CollectorConfig => T + ): T = + cache.computeIfAbsent( + data.name, + new java.util.function.Function[String, T] { + override def apply(t: String): T = create(data) + } + ) + + private def createNewUpDownCounter(collectorConfig: CollectorConfig): LongUpDownCounter = + meter + .upDownCounterBuilder(collectorConfig.name) + .setUnit(collectorConfig.unit) + .setDescription(collectorConfig.description) + .build() + + private def createNewCounter(collectorConfig: CollectorConfig): LongCounter = + meter + .counterBuilder(collectorConfig.name) + .setUnit(collectorConfig.unit) + .setDescription(collectorConfig.description) + .build() + + private def createNewHistogram(collectorConfig: CollectorConfig): DoubleHistogram = + meter + .histogramBuilder(collectorConfig.name) + .setUnit(collectorConfig.unit) + .setDescription(collectorConfig.description) + .build() } +case class CollectorConfig(name: String, description: String = "", unit: String = "") + object OpenTelemetryMetricsBackend { + + val DefaultRequestsInProgressCounterName = "requests_in_progress" + val DefaultSuccessCounterName = "requests_success_count" + val DefaultErrorCounterName = "requests_error_count" + val DefaultFailureCounterName = "requests_failure_count" + val DefaultRequestHistogramName = "request_size_bytes" + val DefaultResponseHistogramName = "response_size_bytes" + val DefaultMemUsageGaugeName = "memory_total" + def apply[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, - spanName: Request[_, _] => String = request => s"HTTP ${request.method.method}" + requestToInProgressGaugeNameMapper: Request[_, _] => Option[CollectorConfig] = (_: Request[_, _]) => + Some(CollectorConfig(DefaultRequestsInProgressCounterName)), + responseToSuccessCounterMapper: Response[_] => Option[CollectorConfig] = (_: Response[_]) => + Some(CollectorConfig(DefaultSuccessCounterName)), + responseToErrorCounterMapper: Response[_] => Option[CollectorConfig] = (_: Response[_]) => + Some(CollectorConfig(DefaultErrorCounterName)), + requestToFailureCounterMapper: (Request[_, _], Throwable) => Option[CollectorConfig] = + (_: Request[_, _], _: Throwable) => Some(CollectorConfig(DefaultFailureCounterName)), + requestToSizeSummaryMapper: Request[_, _] => Option[CollectorConfig] = (_: Request[_, _]) => + Some(CollectorConfig(DefaultRequestHistogramName)), + responseToSizeSummaryMapper: Response[_] => Option[CollectorConfig] = (_: Response[_]) => + Some(CollectorConfig(DefaultResponseHistogramName)), + memUsageGaugeName: Option[CollectorConfig] = Some(CollectorConfig(DefaultMemUsageGaugeName)) ): SttpBackend[F, P] = - new OpenTelemetryMetricsBackend[F, P](delegate, openTelemetry, spanName) + new OpenTelemetryMetricsBackend[F, P]( + requestToInProgressGaugeNameMapper, + responseToSuccessCounterMapper, + responseToErrorCounterMapper, + requestToFailureCounterMapper, + requestToSizeSummaryMapper, + responseToSizeSummaryMapper, + memUsageGaugeName, + delegate, + openTelemetry + ) } From bf382ab7d2fa670056b404375f0c408f78024ac4 Mon Sep 17 00:00:00 2001 From: Pask Date: Mon, 2 May 2022 14:02:36 +0200 Subject: [PATCH 05/13] Added test for metrics backend --- build.sbt | 3 +- .../OpenTelemetryMetricsBackendTest.scala | 257 ++++++++++++++---- .../OpenTelemetryTracingBackendTest.scala | 2 +- 3 files changed, 201 insertions(+), 61 deletions(-) diff --git a/build.sbt b/build.sbt index 069f52726e..4fae452640 100644 --- a/build.sbt +++ b/build.sbt @@ -909,7 +909,8 @@ lazy val openTelemetryMetricsBackend = (projectMatrix in file("metrics/open-tele name := "opentelemetry-metrics-backend", libraryDependencies ++= Seq( "io.opentelemetry" % "opentelemetry-api" % "1.13.0", - "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.13.0" % Test + "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.13.0" % Test, + "io.opentelemetry" % "opentelemetry-sdk-metrics-testing" % "1.13.0-alpha" % Test ), scalaTest ) diff --git a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala b/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala index cf21f3fbc5..21ff82998c 100644 --- a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala +++ b/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala @@ -1,83 +1,222 @@ package sttp.client3.opentelemetry.metrics -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator -import io.opentelemetry.context.propagation.ContextPropagators import io.opentelemetry.sdk.OpenTelemetrySdk -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter -import io.opentelemetry.sdk.trace.SdkTracerProvider -import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor -import org.scalatest.BeforeAndAfter +import io.opentelemetry.sdk.metrics.SdkMeterProvider +import io.opentelemetry.sdk.metrics.data.{DoublePointData, HistogramPointData, MetricData} +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader +import org.scalatest.OptionValues import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import sttp.client3.monad.IdMonad import sttp.client3.testing.SttpBackendStub -import sttp.client3.{Identity, Request, Response, SttpBackend, UriContext, basicRequest} -import sttp.model.StatusCode +import sttp.client3.{Identity, Response, UriContext, basicRequest} +import sttp.model.{Header, StatusCode} import scala.collection.JavaConverters._ -import scala.collection.mutable -import scala.util.Try - -class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with BeforeAndAfter { - - private val recordedRequests = mutable.ListBuffer[Request[_, _]]() - - private val spanExporter = InMemorySpanExporter.create() - - private val mockTracer: SdkTracerProvider = - SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)).build() - - private val mockOpenTelemetry = OpenTelemetrySdk - .builder() - .setTracerProvider(mockTracer) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .buildAndRegisterGlobal() - - private val backend: SttpBackend[Identity, Any] = - OpenTelemetryMetricsBackend( - SttpBackendStub.apply(IdMonad).whenRequestMatchesPartial { - case r if r.uri.toString.contains("echo") => - recordedRequests += r - Response.ok("") - case r if r.uri.toString.contains("error") => - throw new RuntimeException("something went wrong") - }, - mockOpenTelemetry + +class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with OptionValues { + + private def spawnNewOpenTelemetry(reader: InMemoryMetricReader) = { + val mockMeter: SdkMeterProvider = + SdkMeterProvider.builder().registerMetricReader(reader).build() + + OpenTelemetrySdk + .builder() + .setMeterProvider(mockMeter) + .build() + } + + val stubAlwaysOk = SttpBackendStub.synchronous.whenAnyRequest.thenRespondOk() + + "OpenTelemetryMetricsBackend" should "use default names" in { + // given + val requestsNumber = 10 + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, spawnNewOpenTelemetry(reader)) + + // when + (0 until requestsNumber).foreach(_ => backend.send(basicRequest.get(uri"http://127.0.0.1/echo"))) + + // then + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName).value shouldBe requestsNumber + } + + "Number of in-progress requests" should "be zero" in { + // given + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, spawnNewOpenTelemetry(reader)) + + // when + (0 until 10).foreach(_ => backend.send(basicRequest.get(uri"http://127.0.0.1/echo"))) + + // then + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultRequestsInProgressCounterName) should not be empty + } + + "OpenTelemetryMetricsBackend" should "read jvm memory" in { + // given + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, spawnNewOpenTelemetry(reader)) + + // when + backend.send(basicRequest.get(uri"http://127.0.0.1/echo")) + + // then + val maybeData = reader + .collectAllMetrics() + .asScala + .find(_.getName.equals(OpenTelemetryMetricsBackend.DefaultMemUsageGaugeName)) + .map(_.getDoubleGaugeData) + maybeData + .map(_.getPoints.asScala.head) + getGaugeValue(reader, OpenTelemetryMetricsBackend.DefaultMemUsageGaugeName) + } + + it should "allow creating two backends" in { + // given + val reader = InMemoryMetricReader.create() + val sdk = spawnNewOpenTelemetry(reader) + val backend1 = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, sdk) + val backend2 = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, sdk) + + // when + backend1.send(basicRequest.get(uri"http://127.0.0.1/echo")) + backend2.send(basicRequest.get(uri"http://127.0.0.1/echo")) + + // then + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName).value shouldBe 2 + } + + it should "use mapped request to histogram name" in { + // given + val customSuccessCounterName = "my_custom_counter_name" + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any]( + stubAlwaysOk, + spawnNewOpenTelemetry(reader), + responseToSuccessCounterMapper = _ => Some(CollectorConfig(customSuccessCounterName)) ) + val requestsNumber = 5 + + // when + (0 until requestsNumber).foreach(_ => backend.send(basicRequest.get(uri"http://127.0.0.1/foo"))) + + // then + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName) shouldBe empty + getMetricValue(reader, customSuccessCounterName).value shouldBe 5 - before { - recordedRequests.clear() - spanExporter.reset() } - "ZioTelemetryOpenTelemetryBackend" should "record spans for requests" in { - val response = basicRequest.post(uri"http://stub/echo").send(backend) - response.code shouldBe StatusCode.Ok + it should "use mapped request to change collector config" in { + // given + val customSuccessCounterName = "my_custom_counter_name" + val description = "test" + val unit = "number" + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any]( + stubAlwaysOk, + spawnNewOpenTelemetry(reader), + responseToSuccessCounterMapper = _ => Some(CollectorConfig(customSuccessCounterName, description, unit)) + ) + val requestsNumber1 = 5 + + // when + (0 until requestsNumber1).foreach(_ => backend.send(basicRequest.get(uri"http://127.0.0.1/foo"))) - val spans = spanExporter.getFinishedSpanItems.asScala - spans should have size 1 - spans.head.getName shouldBe "HTTP POST" + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName) shouldBe empty + getMetricValue(reader, customSuccessCounterName).value shouldBe 5 + val resource = getMetricResource(reader, customSuccessCounterName) + resource.getDescription shouldBe description + resource.getUnit shouldBe unit } - it should "propagate span" in { - val response = basicRequest.post(uri"http://stub/echo").send(backend) - response.code shouldBe StatusCode.Ok + it should "disable counter" in { + // given + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any]( + stubAlwaysOk, + spawnNewOpenTelemetry(reader), + responseToSuccessCounterMapper = _ => None + ) + val requestsNumber = 6 - val spans = spanExporter.getFinishedSpanItems.asScala - spans should have size 1 + // when + (0 until requestsNumber).foreach(_ => backend.send(basicRequest.get(uri"http://127.0.0.1/foo"))) - val spanId = spans.head.getSpanId - val traceId = spans.head.getTraceId - recordedRequests(0).header("traceparent") shouldBe Some(s"00-${traceId}-${spanId}-01") + // then + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName) shouldBe empty } - it should "set span status in case of error" in { - Try(basicRequest.post(uri"http://stub/error").send(backend)) + it should "use default counter name" in { + // given + val backendStub1 = SttpBackendStub.synchronous.whenAnyRequest.thenRespondOk() + val backendStub2 = SttpBackendStub.synchronous.whenAnyRequest.thenRespondNotFound() + val reader = InMemoryMetricReader.create() + val sdk = spawnNewOpenTelemetry(reader) + val backend1 = OpenTelemetryMetricsBackend[Identity, Any](backendStub1, sdk) + val backend2 = OpenTelemetryMetricsBackend[Identity, Any](backendStub2, sdk) + + // when + (0 until 10).foreach(_ => backend1.send(basicRequest.get(uri"http://127.0.0.1/foo"))) + (0 until 5).foreach(_ => backend2.send(basicRequest.get(uri"http://127.0.0.1/foo"))) + + // then + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName).value shouldBe 10 + getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultErrorCounterName).value shouldBe 5 + } - val spans = spanExporter.getFinishedSpanItems.asScala - spans should have size 1 + it should "use histogram for request and response sizes" in { + // given + val response = Response("Ok", StatusCode.Ok, "Ok", Seq(Header.contentLength(10))) + val backendStub = SttpBackendStub.synchronous.whenAnyRequest.thenRespond(response) + val reader = InMemoryMetricReader.create() + val backend = OpenTelemetryMetricsBackend[Identity, Any](backendStub, spawnNewOpenTelemetry(reader)) + + // when + (0 until 5).foreach(_ => + backend.send( + basicRequest + .get(uri"http://127.0.0.1/foo") + .header(Header.contentLength(5)) + ) + ) + + // then + getHistogramValue(reader, OpenTelemetryMetricsBackend.DefaultRequestHistogramName).value.getSum shouldBe 25 + getHistogramValue(reader, OpenTelemetryMetricsBackend.DefaultResponseHistogramName).value.getSum shouldBe 50 + } + + private[this] def getMetricValue(reader: InMemoryMetricReader, name: String): Option[Long] = { + reader + .collectAllMetrics() + .asScala + .find(_.getName.equals(name)) + .map(_.getLongSumData) + .map(_.getPoints.asScala.head.getValue) + } + + private[this] def getHistogramValue(reader: InMemoryMetricReader, name: String): Option[HistogramPointData] = { + reader + .collectAllMetrics() + .asScala + .find(_.getName.equals(name)) + .map(_.getHistogramData) + .map(_.getPoints.asScala.head) + } + private[this] def getGaugeValue(reader: InMemoryMetricReader, name: String): Option[DoublePointData] = { + reader + .collectAllMetrics() + .asScala + .find(_.getName.equals(name)) + .map(_.getDoubleGaugeData) + .map(_.getPoints.asScala.head) + } - spans.head.getStatus.getStatusCode shouldBe io.opentelemetry.api.trace.StatusCode.ERROR + private[this] def getMetricResource(reader: InMemoryMetricReader, name: String): MetricData = { + reader + .collectAllMetrics() + .asScala + .find(_.getName.equals(name)) + .head } } diff --git a/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala b/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala index 6ffbe9b5ae..fcbd54e62a 100644 --- a/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala +++ b/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala @@ -50,7 +50,7 @@ class OpenTelemetryTracingBackendTest extends AnyFlatSpec with Matchers with Bef spanExporter.reset() } - "ZioTelemetryOpenTelemetryBackend" should "record spans for requests" in { + "OpenTelemetryTracingBackend" should "record spans for requests" in { val response = basicRequest.post(uri"http://stub/echo").send(backend) response.code shouldBe StatusCode.Ok From cf8626f7ef3012335df65a7d86b13d49383ee63b Mon Sep 17 00:00:00 2001 From: Pask Date: Mon, 2 May 2022 14:39:44 +0200 Subject: [PATCH 06/13] Updated documention of opentelemtry backends --- docs/backends/summary.md | 3 +- .../backends/wrappers/opentelemetrymetrics.md | 28 ++++ ...entelemetry.md => opentelemetrytracing.md} | 10 +- docs/backends/wrappers/opentracing.md | 121 ------------------ docs/index.md | 4 +- 5 files changed, 37 insertions(+), 129 deletions(-) create mode 100644 docs/backends/wrappers/opentelemetrymetrics.md rename docs/backends/wrappers/{opentelemetry.md => opentelemetrytracing.md} (79%) delete mode 100644 docs/backends/wrappers/opentracing.md diff --git a/docs/backends/summary.md b/docs/backends/summary.md index f8542159b7..920bd6ee4f 100644 --- a/docs/backends/summary.md +++ b/docs/backends/summary.md @@ -59,7 +59,8 @@ All backends that support asynchronous/non-blocking streams, also support server There are also backends which wrap other backends to provide additional functionality. These include: * `TryBackend`, which safely wraps any exceptions thrown by a synchronous backend in `scala.util.Try` -* `OpenTracingBackend`, for OpenTracing-compatible distributed tracing. See the [dedicated section](wrappers/opentracing.md). +* `OpenTelemetryTracingBackend`, for OpenTelemetry-compatible distributed tracing. See the [dedicated section](wrappers/opentelemetrytracing.md). +* `OpenTelemetryMetricsBackend`, for OpenTelemetry-compatible metrics. See the [dedicated section](wrappers/opentelemetrymetrics.md). * `PrometheusBackend`, for gathering Prometheus-format metrics. See the [dedicated section](wrappers/prometheus.md). * extendable logging backends (with an slf4j implementation) backends. See the [dedicated section](wrappers/logging.md). * `ResolveRelativeUrisBackend` to resolve relative URIs given a base URI, or an arbitrary effectful function diff --git a/docs/backends/wrappers/opentelemetrymetrics.md b/docs/backends/wrappers/opentelemetrymetrics.md new file mode 100644 index 0000000000..ed624bf0ae --- /dev/null +++ b/docs/backends/wrappers/opentelemetrymetrics.md @@ -0,0 +1,28 @@ +# Opentelemetry metrics backend + +To use, add the following dependency to your project: + + +``` +"com.softwaremill.sttp.client3" %% "opentelemetry-metrics-backend" % "@VERSION@" +``` + +This backend depends only on [opentelemetry](https://github.com/open-telemetry/opentelemetry-java). + +The opentelemetry backend wraps any other backend, but it's useless without an instance of opentelemetry. To obtain instance of OpenTelemetryMetricsBackend: + +```scala +OpenTelemetryMetricsBackend( + sttpBackend, + openTelemetry +) +``` + +All counters have provided default names, but the names can be customized by setting correct parameters in constructor: +```scala +OpenTelemetryMetricsBackend( + sttpBackend, + openTelemetry, + responseToSuccessCounterMapper = _ => Some(CollectorConfig("my_custom_counter_name")) +) +``` \ No newline at end of file diff --git a/docs/backends/wrappers/opentelemetry.md b/docs/backends/wrappers/opentelemetrytracing.md similarity index 79% rename from docs/backends/wrappers/opentelemetry.md rename to docs/backends/wrappers/opentelemetrytracing.md index 0ea5dc866b..8cdac78dab 100644 --- a/docs/backends/wrappers/opentelemetry.md +++ b/docs/backends/wrappers/opentelemetrytracing.md @@ -1,18 +1,18 @@ -# Opentelemetry backend +# Opentelemetry tracing backend To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client3" %% "opentelemetry-backend" % "@VERSION@" +"com.softwaremill.sttp.client3" %% "opentelemetry-tracing-backend" % "@VERSION@" ``` This backend depends only on [opentelemetry](https://github.com/open-telemetry/opentelemetry-java). -The opentelemetry backend wraps any other backend, but it's useless without an instance of opentelemetry. To obtain instance of OpenTelemetryBackend: +The opentelemetry backend wraps any other backend, but it's useless without an instance of opentelemetry. To obtain instance of OpenTelemetryTracingBackend: ```scala -OpenTelemetryBackend( +OpenTelemetryTracingBackend( sttpBackend, openTelemetry ) @@ -21,7 +21,7 @@ OpenTelemetryBackend( By default, the span is named after the HTTP method (e.g "HTTP POST") as [recommended by OpenTelemetry](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name) for HTTP clients. It can be customized by setting the third argument in constructor: ```scala -OpenTelemetryBackend( +OpenTelemetryTracingBackend( sttpBackend, openTelemetry, request => s"HTTP ${request.method.method}" diff --git a/docs/backends/wrappers/opentracing.md b/docs/backends/wrappers/opentracing.md deleted file mode 100644 index 98bd73b2aa..0000000000 --- a/docs/backends/wrappers/opentracing.md +++ /dev/null @@ -1,121 +0,0 @@ -# Opentracing backend - -To use, add the following dependency to your project: - -``` -"com.softwaremill.sttp.client3" %% "opentracing-backend" % "@VERSION@" -``` - -This backend depends on [opentracing](https://github.com/opentracing/opentracing-java), a standardized set of api for distributed tracing. - -The opentracing backend wraps any other backend, but it's useless without a concrete distributed tracing implementation. To obtain instance of opentracing backend: - -```scala -OpenTracingBackend(wrappedBackend, tracer) -``` - -Where tracer is an interface which can be implemented by any compatible library. See examples below. - -The backend obtains the current trace context using default spans's propagation mechanisms. - -There is an additional method exposed to override default operation id: - -```scala mdoc:compile-only -import sttp.client3._ -import sttp.client3.opentracing.OpenTracingBackend._ - -basicRequest - .get(???) - .tagWithOperationId("register-user") -``` - -There is an additional method exposed to customize generated span: - -```scala mdoc:compile-only -import sttp.client3._ -import sttp.client3.opentracing.OpenTracingBackend._ - -basicRequest - .get(???) - .tagWithTransformSpan(_.setTag("custom-tag", "custom-value").setOperationName("new-name").log("my-event")) -``` - -## Integration with jaeger - -Using with [jaeger](https://www.jaegertracing.io/) tracing - -Add following dependency: - -``` -libraryDependencies += "io.jaegertracing" % "jaeger-client" % "@JEAGER_CLIENT_VERSION@" -``` - -Create an instance of tracer: - -```scala mdoc:compile-only -import io.opentracing.Tracer -import io.jaegertracing.Configuration -import io.jaegertracing.Configuration.ReporterConfiguration -import io.jaegertracing.Configuration.SamplerConfiguration - -def initTracer(serviceName: String ): Tracer = { - val samplerConfig = SamplerConfiguration.fromEnv().withType("const").withParam(1) - val reporterConfig = ReporterConfiguration.fromEnv().withLogSpans(true) - val config = new Configuration(serviceName).withSampler(samplerConfig) - .withReporter(reporterConfig) - config.getTracer() -} -``` - -For more details about integration with jaeger click [here](https://github.com/jaegertracing/jaeger-client-java) - -## Integration with brave - -Using with [brave](https://github.com/openzipkin/brave) tracing - -Add following dependency: - -``` -libraryDependencies += "io.opentracing.brave" % "brave-opentracing" % "@BRAVE_OPENTRACING_VERSION@" -// and for integrationw with okHttp: -libraryDependencies += "io.zipkin.reporter2" % "zipkin-sender-okhttp3" % "@ZIPKIN_SENDER_OKHTTP_VERSION@" -``` - -Create an instance of tracer: - -```scala mdoc:compile-only -import io.opentracing.Tracer -import zipkin2.reporter.AsyncReporter -import zipkin2.reporter.okhttp3.OkHttpSender -import brave.propagation.{ExtraFieldPropagation, B3Propagation} -import brave.Tracing -import brave.opentracing.BraveTracer -import java.util.Arrays - -def initTracer(zipkinUrl: String, serviceName: String): Tracer = { - // Configure a reporter, which controls how often spans are sent - val sender = OkHttpSender.create(zipkinUrl) - val spanReporter = AsyncReporter.create(sender) - - // If you want to support baggage, indicate the fields you'd like to - // whitelist, in this case "country-code" and "user-id". On the wire, - // they will be prefixed like "baggage-country-code" - val propagationFactory = ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY) - .addPrefixedFields("baggage-", - Arrays.asList("country-code", "user-id")) - .build() - - // Now, create a Brave tracing component with the service name you want to see in - // Zipkin (the dependency is io.zipkin.brave:brave). - val braveTracing = Tracing.newBuilder() - .localServiceName(serviceName) - .propagationFactory(propagationFactory) - .spanReporter(spanReporter) - .build() - - // use this to create an OpenTracing Tracer - BraveTracer.create(braveTracing) -} -``` - -For more details about integration with brave click [here](https://github.com/openzipkin-contrib/brave-opentracing) diff --git a/docs/index.md b/docs/index.md index d85ba2d0be..2b59d8841b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -138,8 +138,8 @@ Development and maintenance of sttp client is sponsored by [SoftwareMill](https: :maxdepth: 2 :caption: Backend wrappers - backends/wrappers/opentracing - backends/wrappers/opentelemetry + backends/wrappers/opentelemetrytracing + backends/wrappers/opentelemetrymetrics backends/wrappers/prometheus backends/wrappers/logging backends/wrappers/custom From 08a1a7387e94007eaee08207871ba435fe241260 Mon Sep 17 00:00:00 2001 From: Pask Date: Mon, 2 May 2022 14:47:45 +0200 Subject: [PATCH 07/13] Added possibility to customize meter in opentelemtryMetricsBackend --- .../metrics/OpenTelemetryMetricsBackend.scala | 41 ++++++++----------- .../OpenTelemetryMetricsBackendTest.scala | 19 --------- 2 files changed, 18 insertions(+), 42 deletions(-) diff --git a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala b/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala index 0fead98734..24fe92efe3 100644 --- a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala +++ b/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala @@ -10,31 +10,26 @@ import sttp.monad.syntax.MonadErrorOps import java.util.concurrent.ConcurrentHashMap private class OpenTelemetryMetricsBackend[F[_], P]( + delegate: SttpBackend[F, P], + openTelemetry: OpenTelemetry, + meterConfig: Option[MeterConfig], requestToInProgressCounterNameMapper: Request[_, _] => Option[CollectorConfig], requestToSuccessCounterMapper: Response[_] => Option[CollectorConfig], requestToErrorCounterMapper: Response[_] => Option[CollectorConfig], requestToFailureCounterMapper: (Request[_, _], Throwable) => Option[CollectorConfig], requestToSizeHistogramMapper: Request[_, _] => Option[CollectorConfig], - responseToSizeHistogramMapper: Response[_] => Option[CollectorConfig], - memUsageGaugeName: Option[CollectorConfig], - delegate: SttpBackend[F, P], - openTelemetry: OpenTelemetry + responseToSizeHistogramMapper: Response[_] => Option[CollectorConfig] ) extends SttpBackend[F, P] { - private val meter: Meter = openTelemetry - .meterBuilder("sttp3-client") - .setInstrumentationVersion("1.0.0") + private val meter: Meter = meterConfig + .map(config => + openTelemetry + .meterBuilder(config.name) + .setInstrumentationVersion(config.version) + ) + .getOrElse(openTelemetry.meterBuilder("sttp3-client")) .build() - memUsageGaugeName - .foreach(config => { - meter - .gaugeBuilder(config.name) - .setDescription("Memory Usage") - .setUnit("byte") - .buildWithCallback(_.record(Runtime.getRuntime.totalMemory())) - }) - private val counters: ConcurrentHashMap[String, LongCounter] = new ConcurrentHashMap[String, LongCounter] private val histograms = new ConcurrentHashMap[String, DoubleHistogram]() private val upAndDownCounter = new ConcurrentHashMap[String, LongUpDownCounter]() @@ -131,6 +126,7 @@ private class OpenTelemetryMetricsBackend[F[_], P]( } case class CollectorConfig(name: String, description: String = "", unit: String = "") +case class MeterConfig(name: String, version: String) object OpenTelemetryMetricsBackend { @@ -140,11 +136,11 @@ object OpenTelemetryMetricsBackend { val DefaultFailureCounterName = "requests_failure_count" val DefaultRequestHistogramName = "request_size_bytes" val DefaultResponseHistogramName = "response_size_bytes" - val DefaultMemUsageGaugeName = "memory_total" def apply[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, + meterConfig: Option[MeterConfig] = None, requestToInProgressGaugeNameMapper: Request[_, _] => Option[CollectorConfig] = (_: Request[_, _]) => Some(CollectorConfig(DefaultRequestsInProgressCounterName)), responseToSuccessCounterMapper: Response[_] => Option[CollectorConfig] = (_: Response[_]) => @@ -156,18 +152,17 @@ object OpenTelemetryMetricsBackend { requestToSizeSummaryMapper: Request[_, _] => Option[CollectorConfig] = (_: Request[_, _]) => Some(CollectorConfig(DefaultRequestHistogramName)), responseToSizeSummaryMapper: Response[_] => Option[CollectorConfig] = (_: Response[_]) => - Some(CollectorConfig(DefaultResponseHistogramName)), - memUsageGaugeName: Option[CollectorConfig] = Some(CollectorConfig(DefaultMemUsageGaugeName)) + Some(CollectorConfig(DefaultResponseHistogramName)) ): SttpBackend[F, P] = new OpenTelemetryMetricsBackend[F, P]( + delegate, + openTelemetry, + meterConfig, requestToInProgressGaugeNameMapper, responseToSuccessCounterMapper, responseToErrorCounterMapper, requestToFailureCounterMapper, requestToSizeSummaryMapper, - responseToSizeSummaryMapper, - memUsageGaugeName, - delegate, - openTelemetry + responseToSizeSummaryMapper ) } diff --git a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala b/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala index 21ff82998c..abdde3e2c8 100644 --- a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala +++ b/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala @@ -52,25 +52,6 @@ class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with Opt getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultRequestsInProgressCounterName) should not be empty } - "OpenTelemetryMetricsBackend" should "read jvm memory" in { - // given - val reader = InMemoryMetricReader.create() - val backend = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, spawnNewOpenTelemetry(reader)) - - // when - backend.send(basicRequest.get(uri"http://127.0.0.1/echo")) - - // then - val maybeData = reader - .collectAllMetrics() - .asScala - .find(_.getName.equals(OpenTelemetryMetricsBackend.DefaultMemUsageGaugeName)) - .map(_.getDoubleGaugeData) - maybeData - .map(_.getPoints.asScala.head) - getGaugeValue(reader, OpenTelemetryMetricsBackend.DefaultMemUsageGaugeName) - } - it should "allow creating two backends" in { // given val reader = InMemoryMetricReader.create() From 2075da13dde42e8ebeac9ee134fa3af523ce6403 Mon Sep 17 00:00:00 2001 From: Pask Date: Mon, 2 May 2022 15:04:40 +0200 Subject: [PATCH 08/13] Added possibility to customize tracer in OpenTelemetryTracingBackend --- .../tracing/OpenTelemetryTracingBackend.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala b/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala index 49996f5704..f372c2c7e7 100644 --- a/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala +++ b/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala @@ -15,10 +15,14 @@ import scala.collection.mutable private class OpenTelemetryTracingBackend[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, - spanName: Request[_, _] => String + spanName: Request[_, _] => String, + tracerConfig: Option[TracerConfig] ) extends SttpBackend[F, P] { - private val tracer = openTelemetry.getTracer("sttp3-client", "1.0.0") + private val tracer = tracerConfig + .map(config => openTelemetry.getTracer(config.name, config.version)) + .getOrElse(openTelemetry.getTracer("sttp3-client", "1.0.0")) + private implicit val _monad: MonadError[F] = responseMonad type PE = P with Effect[F] @@ -72,11 +76,14 @@ private class OpenTelemetryTracingBackend[F[_], P]( } +case class TracerConfig(name: String, version: String) + object OpenTelemetryTracingBackend { def apply[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, - spanName: Request[_, _] => String = request => s"HTTP ${request.method.method}" + spanName: Request[_, _] => String = request => s"HTTP ${request.method.method}", + tracerConfig: Option[TracerConfig] = None ): SttpBackend[F, P] = - new OpenTelemetryTracingBackend[F, P](delegate, openTelemetry, spanName) + new OpenTelemetryTracingBackend[F, P](delegate, openTelemetry, spanName, tracerConfig) } From 4b34306603004ac8fa67534ce1e5190a53d51830 Mon Sep 17 00:00:00 2001 From: Pask Date: Mon, 2 May 2022 15:22:54 +0200 Subject: [PATCH 09/13] Added docs entry about customizing meter and tracer --- docs/backends/wrappers/opentelemetrymetrics.md | 9 +++++++++ docs/backends/wrappers/opentelemetrytracing.md | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/backends/wrappers/opentelemetrymetrics.md b/docs/backends/wrappers/opentelemetrymetrics.md index ed624bf0ae..18e49f8e27 100644 --- a/docs/backends/wrappers/opentelemetrymetrics.md +++ b/docs/backends/wrappers/opentelemetrymetrics.md @@ -25,4 +25,13 @@ OpenTelemetryMetricsBackend( openTelemetry, responseToSuccessCounterMapper = _ => Some(CollectorConfig("my_custom_counter_name")) ) +``` + +There is also the possibility to customize meter name and version by using constructor parameter: +```scala +OpenTelemetryMetricsBackend( + sttpBackend, + openTelemetry, + meterConfig = Some(MeterConfig("my_custom_meter_name", "1.0.0")) +) ``` \ No newline at end of file diff --git a/docs/backends/wrappers/opentelemetrytracing.md b/docs/backends/wrappers/opentelemetrytracing.md index 8cdac78dab..780b86e39e 100644 --- a/docs/backends/wrappers/opentelemetrytracing.md +++ b/docs/backends/wrappers/opentelemetrytracing.md @@ -26,4 +26,13 @@ OpenTelemetryTracingBackend( openTelemetry, request => s"HTTP ${request.method.method}" ) +``` + +There is also the possibility to customize tracer name and version by using constructor parameter: +```scala +OpenTelemetryTracingBackend( + sttpBackend, + openTelemetry, + tracerConfig = Some(TracerConfig("my_custom_tracer_name", "1.0.0")) +) ``` \ No newline at end of file From 7b2aecdce288aeb797b368f140773e3260ce184e Mon Sep 17 00:00:00 2001 From: Pask Date: Wed, 4 May 2022 17:15:57 +0200 Subject: [PATCH 10/13] Move open telemetry backends to single project --- build.sbt | 21 ++++--------------- .../OpenTelemetryMetricsBackend.scala | 2 +- .../OpenTelemetryTracingBackend.scala | 2 +- .../OpenTelemetryMetricsBackendTest.scala | 20 +++++------------- .../OpenTelemetryTracingBackendTest.scala | 2 +- 5 files changed, 12 insertions(+), 35 deletions(-) rename metrics/{open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics => open-telemetry/src/main/scala/sttp/client3/opentelemetry}/OpenTelemetryMetricsBackend.scala (99%) rename metrics/{open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing => open-telemetry/src/main/scala/sttp/client3/opentelemetry}/OpenTelemetryTracingBackend.scala (98%) rename metrics/{open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics => open-telemetry/src/test/scala/sttp/client3/opentelemetry}/OpenTelemetryMetricsBackendTest.scala (92%) rename metrics/{open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing => open-telemetry/src/test/scala/sttp/client3/opentelemetry}/OpenTelemetryTracingBackendTest.scala (98%) diff --git a/build.sbt b/build.sbt index 4fae452640..ddf2a86ce3 100644 --- a/build.sbt +++ b/build.sbt @@ -209,7 +209,7 @@ lazy val allAggregates = projectsWithOptionalNative ++ sprayJson.projectRefs ++ playJson.projectRefs ++ prometheusBackend.projectRefs ++ - openTelemetryTracingBackend.projectRefs ++ + openTelemetry.projectRefs ++ finagleBackend.projectRefs ++ armeriaBackend.projectRefs ++ armeriaScalazBackend.projectRefs ++ @@ -890,23 +890,10 @@ lazy val prometheusBackend = (projectMatrix in file("metrics/prometheus-backend" .jvmPlatform(scalaVersions = scala2 ++ scala3) .dependsOn(core) -lazy val openTelemetryTracingBackend = (projectMatrix in file("metrics/open-telemetry-tracing-backend")) +lazy val openTelemetry = (projectMatrix in file("metrics/open-telemetry")) .settings(commonJvmSettings) .settings( - name := "opentelemetry-tracing-backend", - libraryDependencies ++= Seq( - "io.opentelemetry" % "opentelemetry-api" % "1.13.0", - "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.13.0" % Test - ), - scalaTest - ) - .jvmPlatform(scalaVersions = List(scala2_12, scala2_13) ++ scala3) - .dependsOn(core) - -lazy val openTelemetryMetricsBackend = (projectMatrix in file("metrics/open-telemetry-metrics-backend")) - .settings(commonJvmSettings) - .settings( - name := "opentelemetry-metrics-backend", + name := "opentelemetry", libraryDependencies ++= Seq( "io.opentelemetry" % "opentelemetry-api" % "1.13.0", "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.13.0" % Test, @@ -1042,7 +1029,7 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo // okhttpMonixBackend, http4sBackend, prometheusBackend, - openTelemetryTracingBackend, + openTelemetry, slf4jBackend ) .jvmPlatform(scalaVersions = List(scala2_13)) diff --git a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala similarity index 99% rename from metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala rename to metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala index 24fe92efe3..3d98549edf 100644 --- a/metrics/open-telemetry-metrics-backend/src/main/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackend.scala +++ b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala @@ -1,4 +1,4 @@ -package sttp.client3.opentelemetry.metrics +package sttp.client3.opentelemetry import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.metrics.{DoubleHistogram, LongCounter, LongUpDownCounter, Meter} diff --git a/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryTracingBackend.scala similarity index 98% rename from metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala rename to metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryTracingBackend.scala index f372c2c7e7..650fd3be70 100644 --- a/metrics/open-telemetry-tracing-backend/src/main/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackend.scala +++ b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryTracingBackend.scala @@ -1,4 +1,4 @@ -package sttp.client3.opentelemetry.tracing +package sttp.client3.opentelemetry import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.common.{AttributeKey, Attributes} diff --git a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala b/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala similarity index 92% rename from metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala rename to metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala index abdde3e2c8..31a25ed4ba 100644 --- a/metrics/open-telemetry-metrics-backend/src/test/scala/sttp/client3/opentelemetry/metrics/OpenTelemetryMetricsBackendTest.scala +++ b/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala @@ -1,8 +1,8 @@ -package sttp.client3.opentelemetry.metrics +package sttp.client3.opentelemetry import io.opentelemetry.sdk.OpenTelemetrySdk import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.data.{DoublePointData, HistogramPointData, MetricData} +import io.opentelemetry.sdk.metrics.data.{HistogramPointData, MetricData} import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader import org.scalatest.OptionValues import org.scalatest.flatspec.AnyFlatSpec @@ -40,7 +40,7 @@ class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with Opt getMetricValue(reader, OpenTelemetryMetricsBackend.DefaultSuccessCounterName).value shouldBe requestsNumber } - "Number of in-progress requests" should "be zero" in { + it should "zero the number of in-progress requests" in { // given val reader = InMemoryMetricReader.create() val backend = OpenTelemetryMetricsBackend[Identity, Any](stubAlwaysOk, spawnNewOpenTelemetry(reader)) @@ -175,29 +175,19 @@ class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with Opt .map(_.getPoints.asScala.head.getValue) } - private[this] def getHistogramValue(reader: InMemoryMetricReader, name: String): Option[HistogramPointData] = { + private[this] def getHistogramValue(reader: InMemoryMetricReader, name: String): Option[HistogramPointData] = reader .collectAllMetrics() .asScala .find(_.getName.equals(name)) .map(_.getHistogramData) .map(_.getPoints.asScala.head) - } - private[this] def getGaugeValue(reader: InMemoryMetricReader, name: String): Option[DoublePointData] = { - reader - .collectAllMetrics() - .asScala - .find(_.getName.equals(name)) - .map(_.getDoubleGaugeData) - .map(_.getPoints.asScala.head) - } - private[this] def getMetricResource(reader: InMemoryMetricReader, name: String): MetricData = { + private[this] def getMetricResource(reader: InMemoryMetricReader, name: String): MetricData = reader .collectAllMetrics() .asScala .find(_.getName.equals(name)) .head - } } diff --git a/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala b/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryTracingBackendTest.scala similarity index 98% rename from metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala rename to metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryTracingBackendTest.scala index fcbd54e62a..7b4930f3a7 100644 --- a/metrics/open-telemetry-tracing-backend/src/test/scala/sttp/client3/opentelemetry/tracing/OpenTelemetryTracingBackendTest.scala +++ b/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryTracingBackendTest.scala @@ -1,4 +1,4 @@ -package sttp.client3.opentelemetry.tracing +package sttp.client3.opentelemetry import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator import io.opentelemetry.context.propagation.ContextPropagators From 0b2daa740600f918e6f376c31830d4c1b105f46d Mon Sep 17 00:00:00 2001 From: Pask Date: Wed, 4 May 2022 17:39:41 +0200 Subject: [PATCH 11/13] Refactor docs for open telemetry backends --- docs/backends/summary.md | 4 +- docs/backends/wrappers/opentelemetry.md | 69 +++++++++++++++++++ .../backends/wrappers/opentelemetrymetrics.md | 37 ---------- .../backends/wrappers/opentelemetrytracing.md | 38 ---------- docs/index.md | 3 +- 5 files changed, 72 insertions(+), 79 deletions(-) create mode 100644 docs/backends/wrappers/opentelemetry.md delete mode 100644 docs/backends/wrappers/opentelemetrymetrics.md delete mode 100644 docs/backends/wrappers/opentelemetrytracing.md diff --git a/docs/backends/summary.md b/docs/backends/summary.md index 920bd6ee4f..598c659321 100644 --- a/docs/backends/summary.md +++ b/docs/backends/summary.md @@ -59,8 +59,8 @@ All backends that support asynchronous/non-blocking streams, also support server There are also backends which wrap other backends to provide additional functionality. These include: * `TryBackend`, which safely wraps any exceptions thrown by a synchronous backend in `scala.util.Try` -* `OpenTelemetryTracingBackend`, for OpenTelemetry-compatible distributed tracing. See the [dedicated section](wrappers/opentelemetrytracing.md). -* `OpenTelemetryMetricsBackend`, for OpenTelemetry-compatible metrics. See the [dedicated section](wrappers/opentelemetrymetrics.md). +* `OpenTelemetryTracingBackend`, for OpenTelemetry-compatible distributed tracing. See the [dedicated section](wrappers/opentelemetry.md). +* `OpenTelemetryMetricsBackend`, for OpenTelemetry-compatible metrics. See the [dedicated section](wrappers/opentelemetry.md). * `PrometheusBackend`, for gathering Prometheus-format metrics. See the [dedicated section](wrappers/prometheus.md). * extendable logging backends (with an slf4j implementation) backends. See the [dedicated section](wrappers/logging.md). * `ResolveRelativeUrisBackend` to resolve relative URIs given a base URI, or an arbitrary effectful function diff --git a/docs/backends/wrappers/opentelemetry.md b/docs/backends/wrappers/opentelemetry.md new file mode 100644 index 0000000000..e66e7d879e --- /dev/null +++ b/docs/backends/wrappers/opentelemetry.md @@ -0,0 +1,69 @@ +# Opentelemetry + +Both backends from below depend only on [opentelemetry](https://github.com/open-telemetry/opentelemetry-java). +The opentelemetry are type of wrapper backends, so they wrap any other backend. They require an instance of opentelemetry. + +To use, any of below backends, add the following dependency to your project: + +``` +"com.softwaremill.sttp.client3" %% "opentelemetry" % "@VERSION@" +``` + +### Opentelemetry tracing backend + +To obtain instance of OpenTelemetryTracingBackend: + +```scala +OpenTelemetryTracingBackend( + sttpBackend, + openTelemetry +) +``` + +By default, the span is named after the HTTP method (e.g "HTTP POST") as [recommended by OpenTelemetry](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name) for HTTP clients. +It can be customized by setting the third argument in constructor: +```scala +OpenTelemetryTracingBackend( + sttpBackend, + openTelemetry, + request => s"HTTP ${request.method.method}" +) +``` + +There is also the possibility to customize tracer name and version by using constructor parameter: +```scala +OpenTelemetryTracingBackend( + sttpBackend, + openTelemetry, + tracerConfig = Some(TracerConfig("my_custom_tracer_name", "1.0.0")) +) +``` + +### Opentelemetry metrics backend + +To obtain instance of OpenTelemetryMetricsBackend: + +```scala +OpenTelemetryMetricsBackend( + sttpBackend, + openTelemetry +) +``` + +All counters have provided default names, but the names can be customized by setting correct parameters in constructor: +```scala +OpenTelemetryMetricsBackend( + sttpBackend, + openTelemetry, + responseToSuccessCounterMapper = _ => Some(CollectorConfig("my_custom_counter_name")) +) +``` + +There is also the possibility to customize meter name and version by using constructor parameter: +```scala +OpenTelemetryMetricsBackend( + sttpBackend, + openTelemetry, + meterConfig = Some(MeterConfig("my_custom_meter_name", "1.0.0")) +) +``` \ No newline at end of file diff --git a/docs/backends/wrappers/opentelemetrymetrics.md b/docs/backends/wrappers/opentelemetrymetrics.md deleted file mode 100644 index 18e49f8e27..0000000000 --- a/docs/backends/wrappers/opentelemetrymetrics.md +++ /dev/null @@ -1,37 +0,0 @@ -# Opentelemetry metrics backend - -To use, add the following dependency to your project: - - -``` -"com.softwaremill.sttp.client3" %% "opentelemetry-metrics-backend" % "@VERSION@" -``` - -This backend depends only on [opentelemetry](https://github.com/open-telemetry/opentelemetry-java). - -The opentelemetry backend wraps any other backend, but it's useless without an instance of opentelemetry. To obtain instance of OpenTelemetryMetricsBackend: - -```scala -OpenTelemetryMetricsBackend( - sttpBackend, - openTelemetry -) -``` - -All counters have provided default names, but the names can be customized by setting correct parameters in constructor: -```scala -OpenTelemetryMetricsBackend( - sttpBackend, - openTelemetry, - responseToSuccessCounterMapper = _ => Some(CollectorConfig("my_custom_counter_name")) -) -``` - -There is also the possibility to customize meter name and version by using constructor parameter: -```scala -OpenTelemetryMetricsBackend( - sttpBackend, - openTelemetry, - meterConfig = Some(MeterConfig("my_custom_meter_name", "1.0.0")) -) -``` \ No newline at end of file diff --git a/docs/backends/wrappers/opentelemetrytracing.md b/docs/backends/wrappers/opentelemetrytracing.md deleted file mode 100644 index 780b86e39e..0000000000 --- a/docs/backends/wrappers/opentelemetrytracing.md +++ /dev/null @@ -1,38 +0,0 @@ -# Opentelemetry tracing backend - -To use, add the following dependency to your project: - - -``` -"com.softwaremill.sttp.client3" %% "opentelemetry-tracing-backend" % "@VERSION@" -``` - -This backend depends only on [opentelemetry](https://github.com/open-telemetry/opentelemetry-java). - -The opentelemetry backend wraps any other backend, but it's useless without an instance of opentelemetry. To obtain instance of OpenTelemetryTracingBackend: - -```scala -OpenTelemetryTracingBackend( - sttpBackend, - openTelemetry -) -``` - -By default, the span is named after the HTTP method (e.g "HTTP POST") as [recommended by OpenTelemetry](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name) for HTTP clients. -It can be customized by setting the third argument in constructor: -```scala -OpenTelemetryTracingBackend( - sttpBackend, - openTelemetry, - request => s"HTTP ${request.method.method}" -) -``` - -There is also the possibility to customize tracer name and version by using constructor parameter: -```scala -OpenTelemetryTracingBackend( - sttpBackend, - openTelemetry, - tracerConfig = Some(TracerConfig("my_custom_tracer_name", "1.0.0")) -) -``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 2b59d8841b..42dccc804c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -138,8 +138,7 @@ Development and maintenance of sttp client is sponsored by [SoftwareMill](https: :maxdepth: 2 :caption: Backend wrappers - backends/wrappers/opentelemetrytracing - backends/wrappers/opentelemetrymetrics + backends/wrappers/opentelemetry backends/wrappers/prometheus backends/wrappers/logging backends/wrappers/custom From ee9974d8183328adff1ec19031d00653f199aeea Mon Sep 17 00:00:00 2001 From: Pask Date: Wed, 4 May 2022 21:02:17 +0200 Subject: [PATCH 12/13] Fix compilation of open telemetry for 2.12 --- .../OpenTelemetryMetricsBackend.scala | 30 +++++++++---------- .../OpenTelemetryMetricsBackendTest.scala | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala index 3d98549edf..bcc23fb1c3 100644 --- a/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala +++ b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala @@ -14,7 +14,7 @@ private class OpenTelemetryMetricsBackend[F[_], P]( openTelemetry: OpenTelemetry, meterConfig: Option[MeterConfig], requestToInProgressCounterNameMapper: Request[_, _] => Option[CollectorConfig], - requestToSuccessCounterMapper: Response[_] => Option[CollectorConfig], + responseToSuccessCounterMapper: Response[_] => Option[CollectorConfig], requestToErrorCounterMapper: Response[_] => Option[CollectorConfig], requestToFailureCounterMapper: (Request[_, _], Throwable) => Option[CollectorConfig], requestToSizeHistogramMapper: Request[_, _] => Option[CollectorConfig], @@ -44,7 +44,7 @@ private class OpenTelemetryMetricsBackend[F[_], P]( responseMonad.handleError( delegate.send(request).map { response => if (response.isSuccess) { - incrementCounter(requestToSuccessCounterMapper(response)) + incrementCounter(responseToSuccessCounterMapper(response)) } else { incrementCounter(requestToErrorCounterMapper(response)) } @@ -130,18 +130,18 @@ case class MeterConfig(name: String, version: String) object OpenTelemetryMetricsBackend { - val DefaultRequestsInProgressCounterName = "requests_in_progress" - val DefaultSuccessCounterName = "requests_success_count" - val DefaultErrorCounterName = "requests_error_count" - val DefaultFailureCounterName = "requests_failure_count" - val DefaultRequestHistogramName = "request_size_bytes" - val DefaultResponseHistogramName = "response_size_bytes" + val DefaultRequestsInProgressCounterName = "sttp3_requests_in_progress" + val DefaultSuccessCounterName = "sttp3_requests_success_count" + val DefaultErrorCounterName = "sttp3_requests_error_count" + val DefaultFailureCounterName = "sttp3_requests_failure_count" + val DefaultRequestHistogramName = "sttp3_request_size_bytes" + val DefaultResponseHistogramName = "sttp3_response_size_bytes" def apply[F[_], P]( delegate: SttpBackend[F, P], openTelemetry: OpenTelemetry, meterConfig: Option[MeterConfig] = None, - requestToInProgressGaugeNameMapper: Request[_, _] => Option[CollectorConfig] = (_: Request[_, _]) => + requestToInProgressCounterNameMapper: Request[_, _] => Option[CollectorConfig] = (_: Request[_, _]) => Some(CollectorConfig(DefaultRequestsInProgressCounterName)), responseToSuccessCounterMapper: Response[_] => Option[CollectorConfig] = (_: Response[_]) => Some(CollectorConfig(DefaultSuccessCounterName)), @@ -158,11 +158,11 @@ object OpenTelemetryMetricsBackend { delegate, openTelemetry, meterConfig, - requestToInProgressGaugeNameMapper, - responseToSuccessCounterMapper, - responseToErrorCounterMapper, - requestToFailureCounterMapper, - requestToSizeSummaryMapper, - responseToSizeSummaryMapper + requestToInProgressCounterNameMapper = requestToInProgressCounterNameMapper, + responseToSuccessCounterMapper = responseToSuccessCounterMapper, + requestToErrorCounterMapper = responseToErrorCounterMapper, + requestToFailureCounterMapper = requestToFailureCounterMapper, + requestToSizeHistogramMapper = requestToSizeSummaryMapper, + responseToSizeHistogramMapper = responseToSizeSummaryMapper ) } diff --git a/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala b/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala index 31a25ed4ba..10f4beb381 100644 --- a/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala +++ b/metrics/open-telemetry/src/test/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackendTest.scala @@ -12,6 +12,7 @@ import sttp.client3.{Identity, Response, UriContext, basicRequest} import sttp.model.{Header, StatusCode} import scala.collection.JavaConverters._ +import scala.collection.immutable._ class OpenTelemetryMetricsBackendTest extends AnyFlatSpec with Matchers with OptionValues { From d65e1e3caeb6fbcdd48b02c714c47c810c031d47 Mon Sep 17 00:00:00 2001 From: Pask Date: Thu, 5 May 2022 15:46:27 +0200 Subject: [PATCH 13/13] Added attributes recording --- .../OpenTelemetryMetricsBackend.scala | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala index bcc23fb1c3..03315afff8 100644 --- a/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala +++ b/metrics/open-telemetry/src/main/scala/sttp/client3/opentelemetry/OpenTelemetryMetricsBackend.scala @@ -1,6 +1,7 @@ package sttp.client3.opentelemetry import io.opentelemetry.api.OpenTelemetry +import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.metrics.{DoubleHistogram, LongCounter, LongUpDownCounter, Meter} import sttp.capabilities.Effect import sttp.client3._ @@ -50,8 +51,10 @@ private class OpenTelemetryMetricsBackend[F[_], P]( } decrementUpDownCounter(request) responseToSizeHistogramMapper(response) - .map(config => getOrCreateMetric(histograms, config, createNewHistogram)) - .foreach(histogram => response.contentLength.map(_.toDouble).foreach(histogram.record)) + .foreach(config => + getOrCreateMetric(histograms, config, createNewHistogram) + .record((response.contentLength: Option[Long]).map(_.toDouble).getOrElse(0), config.attributes) + ) response } ) { case e => @@ -63,11 +66,12 @@ private class OpenTelemetryMetricsBackend[F[_], P]( private def before[R >: PE, T](request: Request[T, R]): Unit = { requestToInProgressCounterNameMapper(request) - .map(config => getOrCreateMetric(upAndDownCounter, config, createNewUpDownCounter)) - .foreach(_.add(1)) + .foreach(config => getOrCreateMetric(upAndDownCounter, config, createNewUpDownCounter).add(1, config.attributes)) requestToSizeHistogramMapper(request) - .map(config => getOrCreateMetric(histograms, config, createNewHistogram)) - .foreach(histogram => (request.contentLength: Option[Long]).map(_.toDouble).foreach(histogram.record)) + .foreach(config => + getOrCreateMetric(histograms, config, createNewHistogram) + .record((request.contentLength: Option[Long]).map(_.toDouble).getOrElse(0), config.attributes) + ) } private def after[R >: PE, T](request: Request[T, R], e: Throwable): Unit = { @@ -77,14 +81,12 @@ private class OpenTelemetryMetricsBackend[F[_], P]( private def decrementUpDownCounter[R >: PE, T](request: Request[T, R]): Unit = { requestToInProgressCounterNameMapper(request) - .map(config => getOrCreateMetric(upAndDownCounter, config, createNewUpDownCounter)) - .foreach(_.add(-1)) + .foreach(config => getOrCreateMetric(upAndDownCounter, config, createNewUpDownCounter).add(-1, config.attributes)) } private def incrementCounter(collectorConfig: Option[CollectorConfig]): Unit = { collectorConfig - .map(config => getOrCreateMetric(counters, config, createNewCounter)) - .foreach(_.add(1)) + .foreach(config => getOrCreateMetric(counters, config, createNewCounter).add(1, config.attributes)) } override def close(): F[Unit] = delegate.close() @@ -125,7 +127,12 @@ private class OpenTelemetryMetricsBackend[F[_], P]( .build() } -case class CollectorConfig(name: String, description: String = "", unit: String = "") +case class CollectorConfig( + name: String, + description: String = "", + unit: String = "", + attributes: Attributes = Attributes.empty() +) case class MeterConfig(name: String, version: String) object OpenTelemetryMetricsBackend {