From d2145365784dff7824369686ed5b2f57a89865d5 Mon Sep 17 00:00:00 2001 From: Michael Nedokushev Date: Tue, 15 Nov 2022 12:56:10 +0200 Subject: [PATCH] Module pattern 2.0 (#586) * Change return type of Tracing.spanFromUnsafe * Migrate opentracing module * Migrate opencensus module * Refactoring: unify creation of components * Refactoring: continue unification (opentelemetry) * add override to the methods * tidy up docs of methods * Prettify Dependencies * Remove accessor methods, unify API * Use TextMapAdapter in tests * Migrate opentracing-example * Migrate opentelemetry-example * Fix tests * Fix warning * Refactor Clients from examples * Add zio Trace to all methods of opentelemetry module * Add zio Trace to all methods of opentracing module * Add zio Trace to all methods of opencensus module * Use case class instead of type alias for ErrorMapper * Add aspects as replacement for syntax in opentelemetry * Use aspects in opentelemetry example * Add aspects as replacement for syntax in opencensus * Add aspects as replacement for syntax in opentracing * Use aspects in opentracing example * Use aspect in tests of opentelemetry * Use aspects in tests of opentracing * Improve methods API * Update usecases docs * Update opentelemetry docs * Update opentracing docs * Add inject aspect in opentracing * Update opencensus docs --- docs/overview/opencensus.md | 67 +- docs/overview/opentelemetry.md | 94 +- docs/overview/opentracing.md | 61 +- docs/usecases/opentelemetry_example.md | 8 +- docs/usecases/opentracing_example.md | 29 +- .../zio/telemetry/opencensus/Attributes.scala | 2 + .../telemetry/opencensus/ErrorMapper.scala | 12 + .../scala/zio/telemetry/opencensus/Live.scala | 145 --- .../zio/telemetry/opencensus/Tracing.scala | 315 ++++-- .../zio/telemetry/opencensus/package.scala | 4 - .../zio/telemetry/opencensus/syntax.scala | 41 - .../src/main/resources/application.conf | 6 +- .../opentelemetry/example/BackendApp.scala | 25 + .../opentelemetry/example/BackendServer.scala | 37 - .../opentelemetry/example/JaegerTracer.scala | 17 +- .../opentelemetry/example/ProxyApp.scala | 33 + .../opentelemetry/example/ProxyServer.scala | 50 - .../example/config/AppConfig.scala | 2 +- .../example/config/BackendConfig.scala | 4 +- .../example/config/ProxyConfig.scala | 4 +- .../example/config/TracerConfig.scala | 3 + .../example/config/TracerHost.scala | 3 - ...{BackendApp.scala => BackendHttpApp.scala} | 26 +- .../example/http/BackendHttpServer.scala | 24 + .../opentelemetry/example/http/Client.scala | 56 +- .../opentelemetry/example/http/ProxyApp.scala | 33 - .../example/http/ProxyHttpApp.scala | 44 + .../example/http/ProxyHttpServer.scala | 24 + .../opentelemetry/example/package.scala | 12 + .../opentelemetry/ContextPropagation.scala | 32 - .../telemetry/opentelemetry/ErrorMapper.scala | 12 + .../zio/telemetry/opentelemetry/Tracing.scala | 941 ++++++++++-------- .../opentelemetry/TracingSyntax.scala | 99 -- .../telemetry/opentelemetry/TracingTest.scala | 450 +++++---- .../opentracing/example/BackendApp.scala | 26 + .../opentracing/example/BackendServer.scala | 34 - .../opentracing/example/JaegerTracer.scala | 33 +- .../opentracing/example/ProxyApp.scala | 33 + .../opentracing/example/ProxyServer.scala | 39 - .../example/config/AppConfig.scala | 2 +- .../example/config/BackendUrl.scala | 3 - .../example/config/TracerConfig.scala | 3 + .../example/config/TracerHost.scala | 3 - ...{BackendApp.scala => BackendHttpApp.scala} | 22 +- .../example/http/BackendHttpServer.scala | 24 + .../opentracing/example/http/Client.scala | 40 +- .../opentracing/example/http/ProxyApp.scala | 52 - .../example/http/ProxyHttpApp.scala | 50 + .../example/http/ProxyHttpServer.scala | 24 + .../opentracing/example/package.scala | 12 + .../telemetry/opentracing/OpenTracing.scala | 379 ++++--- .../zio/telemetry/opentracing/package.scala | 51 - .../opentracing/OpenTracingTest.scala | 275 ++--- project/Dependencies.scala | 74 +- 54 files changed, 2074 insertions(+), 1820 deletions(-) create mode 100644 opencensus/src/main/scala/zio/telemetry/opencensus/ErrorMapper.scala delete mode 100644 opencensus/src/main/scala/zio/telemetry/opencensus/Live.scala delete mode 100644 opencensus/src/main/scala/zio/telemetry/opencensus/syntax.scala create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala delete mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendServer.scala create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala delete mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyServer.scala create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala delete mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerHost.scala rename opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/{BackendApp.scala => BackendHttpApp.scala} (68%) create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala delete mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyApp.scala create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala create mode 100644 opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/package.scala delete mode 100644 opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ContextPropagation.scala create mode 100644 opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ErrorMapper.scala delete mode 100644 opentelemetry/src/main/scala/zio/telemetry/opentelemetry/TracingSyntax.scala create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendApp.scala delete mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendServer.scala create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala delete mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyServer.scala delete mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/BackendUrl.scala create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerConfig.scala delete mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerHost.scala rename opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/{BackendApp.scala => BackendHttpApp.scala} (63%) create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala delete mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyApp.scala create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala create mode 100644 opentracing-example/src/main/scala/zio/telemetry/opentracing/example/package.scala delete mode 100644 opentracing/src/main/scala/zio/telemetry/opentracing/package.scala diff --git a/docs/overview/opencensus.md b/docs/overview/opencensus.md index c956e5d1..035831bc 100644 --- a/docs/overview/opencensus.md +++ b/docs/overview/opencensus.md @@ -17,16 +17,39 @@ First, add the following dependency to your build.sbt: To use ZIO Telemetry, you will need a `Tracing` service in your environment. You also need to provide a `tracer` implementation: -After importing `import zio.telemetry.opencensus._`, additional combinators +```scala +import zio.telemetry.opencensus.Tracing +import zio.telemetry.opencensus.implicits._ +import zio._ +import io.opencensus.trace.Status + +val tracerLayer = ZLayer.succeed(io.opencensus.trace.Tracing.getTracer) + +val errorMapper = ErrorMapper[Throwable] { case _ => Status.UNKNOWN } + +val app = + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + (for { + _ <- tracing.putAttributes(Map("foo" -> "bar")) + message <- Console.readline + } yield message) @@ root("/app") + }.provide(Tracing.live, tracerLayer) +``` + +After importing `import tracing.aspects._`, additional `ZIOAspect` combinators on `ZIO`s are available to support starting child spans and adding attributes. ```scala -// start a new root span and set some attributes -val zio = ZIO.unit - .root("root span", attributes = ("foo", "bar)) -// start a child of the current span -val zio = ZIO.unit - .span("child span", attributes = Map.empty) +ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + // start a new root span and set some attributes + val zio1 = ZIO.unit @@ root("root span", attributes = ("foo", "bar)) + // start a child of the current span + val zio2 = ZIO.unit @@ span("child span", attributes = Map.empty) +} ``` To propagate contexts across process boundaries, extraction and injection can be @@ -39,7 +62,10 @@ are not referentially transparent. ```scala -val textFormat = Tracing.getPropagationComponent().getB3Format() +ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + val textFormat = Tracing.getPropagationComponent().getB3Format() val carrier: mutable.Map[String, String] = mutable.Map().empty val getter: TextFormat.Getter[mutable.Map[String, String]] = new TextFormat.Getter[mutable.Map[String, String]] { @@ -54,21 +80,12 @@ val textFormat = Tracing.getPropagationComponent().get override def put(carrier: mutable.Map[String, String], key: String, value: String): Unit = carrier.update(key, value) } - - val injectExtract = - inject( - textFormat, - carrier, - setter - ).root("root span", attributes = Map.empty) - fromRootSpan( - textFormat, - carrier, - getter, - "foo", - attributes = Map.empty - ) { - ZIO.unit - .span("child span", attributes = Map(("foo", "bar"))) - } + + val zio1 = tracing.inject(textFormat, carrier, setter) @@ + root("root span", attributes = Map.empty) + + val zio2 = ZIO.unit @@ + span("child span", attributes = Map(("foo", "bar"))) @@ + fromRootSpan(textFormat, carrier, getter, "foo", attributes = Map.empty) +} ``` \ No newline at end of file diff --git a/docs/overview/opentelemetry.md b/docs/overview/opentelemetry.md index b8878737..aeb162eb 100644 --- a/docs/overview/opentelemetry.md +++ b/docs/overview/opentelemetry.md @@ -15,44 +15,50 @@ First, add the following dependency to your build.sbt: ## Usage -To use ZIO Telemetry, you will need a `Tracing` service in your environment. You also need to provide a `tracer` implementation: +To use ZIO Telemetry, you will need a `Tracing` service in your environment. You also need to provide a `tracer` +(for this example we use `JaegerTracer.live` from `opentelemetry-example` module) implementation: ```scala +import zio.telemetry.opentelemetry.Tracing +import zio.telemetry.opentelemetry.example.JaegerTracer import io.opentelemetry.api.trace.{ SpanKind, StatusCode } import zio._ -import zio.telemetry.opentelemetry.Tracing -import zio.telemetry.opentelemetry.Tracing.root -val errorMapper = { case _ => StatusCode.UNSET } +val errorMapper = ErrorMapper[Throwable]{ case _ => StatusCode.UNSET } -val app = - //start root span that lasts until the effect finishes - root("root span", SpanKind.INTERNAL, errorMapper) { - for { +val app = + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + (for { //sets an attribute to the current span - _ <- Tracing.setAttribute("foo", "bar") + _ <- tracing.setAttribute("foo", "bar") //adds an event to the current span - _ <- Tracing.addEvent("foo") + _ <- tracing.addEvent("foo") message <- Console.readline - _ <- Tracing.addEvent("bar") - } yield message - }.provideLayer(tracer >>> Tracing.live) + _ <- tracing.addEvent("bar") + } yield message) @@ root("root span", SpanKind.INTERNAL, errorMapper) + }.provide(Tracing.live, JaegerTracer.live) ``` -After importing `import zio.telemetry.opentelemetry._`, additional combinators +After importing `import tracing.aspects._`, additional `ZIOAspect` combinators on `ZIO`s are available to support starting child spans, adding events and setting attributes. ```scala -// start a new root span and set some attribute -val zio = ZIO.unit - .setAttribute("foo", "bar") - .root("root span") - -// start a child of the current span, set an attribute and add an event -val zio = ZIO.unit - .setAttribute("http.status_code", 200) - .addEvent("doing some serious work here!") - .span("child span") +ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + // start a new root span and set some attribute + val zio1 = ZIO.unit @@ + setAttribute("foo", "bar") @@ + root("root span") + + // start a child of the current span, set an attribute and add an event + val zio2 = ZIO.unit @@ + setAttribute("http.status_code", 200) @@ + addEvent("doing some serious work here!") @@ + span("child span") +} ``` To propagate contexts across process boundaries, extraction and injection can be @@ -64,28 +70,26 @@ Due to the use of the (mutable) OpenTelemetry carrier APIs, injection and extrac are not referentially transparent. ```scala -val propagator = W3CTraceContextPropagator.getInstance() -val carrier: mutable.Map[String, String] = mutable.Map().empty - -val getter: TextMapGetter[mutable.Map[String, String]] = new TextMapGetter[mutable.Map[String, String]] { - override def keys(carrier: mutable.Map[String, String]): lang.Iterable[String] = - carrier.keys.asJava - - override def get(carrier: mutable.Map[String, String], key: String): String = - carrier.get(key).orNull +ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + val propagator = W3CTraceContextPropagator.getInstance() + val carrier: mutable.Map[String, String] = mutable.Map().empty + + val getter: TextMapGetter[mutable.Map[String, String]] = new TextMapGetter[mutable.Map[String, String]] { + override def keys(carrier: mutable.Map[String, String]): lang.Iterable[String] = + carrier.keys.asJava + + override def get(carrier: mutable.Map[String, String], key: String): String = + carrier.get(key).orNull + } + + val setter: TextMapSetter[mutable.Map[String, String]] = + (carrier, key, value) => carrier.update(key, value) + + tracing.inject(propagator, carrier, setter) @@ span("foo") *> + ZIO.unit @@ spanFrom(propagator, carrier, getter, "baz") @@ span("bar") } - -val setter: TextMapSetter[mutable.Map[String, String]] = - (carrier, key, value) => carrier.update(key, value) - -val injectExtract = - inject( - propagator, - carrier, - setter - ).span("foo") *> ZIO.unit - .spanFrom(propagator, carrier, getter, "baz") - .span("bar") ``` ### [Experimental] Usage with OpenTelemetry automatic instrumentation diff --git a/docs/overview/opentracing.md b/docs/overview/opentracing.md index 129611da..bc5ee201 100644 --- a/docs/overview/opentracing.md +++ b/docs/overview/opentracing.md @@ -17,34 +17,47 @@ First, add the following dependency to your build.sbt: ## Usage To use ZIO Telemetry, you will need an `OpenTracing` service in your -environment: +environment. You also need to provide a `tracer` (for this example we use `JaegerTracer.live` from `opentracing-example` module) implementation: ```scala -import io.opentracing.mock.MockTracer -import io.opentracing.propagation._ +import zio.telemetry.opentracing.OpenTracing +import zio.telemetry.opentracing.example.JaegerTracer import zio._ -import zio.telemetry.opentracing._ +import io.opentracing.tag.Tags -val tracer = new MockTracer +val app = + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ -val layer = OpenTracing.live(tracer) + (for { + _ <- ZIO.unit @@ tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT) + _ <- ZIO.unit @@ tag(Tags.HTTP_METHOD.getKey, "GET") + _ <- ZIO.unit @@ setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value") + message <- Console.readline + _ <- ZIO.unit @@ log("Message has been read") + } yield message) @@ root("/app") + }.provide(OpenTracing.live, JaegerTracer.live("my-app")) ``` -After importing `import zio.telemetry.opentracing._`, additional combinators +After importing `import tracing.aspects._`, additional `ZIOAspect` combinators on `ZIO`s are available to support starting child spans, tagging, logging and managing baggage. ```scala -// start a new root span and set some baggage item -val zio = ZIO.unit - .setBaggage("foo", "bar") - .root("root span") - -// start a child of the current span, set a tag and log a message -val zio = ZIO.unit - .tag("http.status_code", 200) - .log("doing some serious work here!") - .span("child span") +ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + // start a new root span and set some baggage item + val zio1 = ZIO.unit @@ + setBaggage("foo", "bar") @@ + root("root span") + + // start a child of the current span, set a tag and log a message + val zio2 = ZIO.unit @@ + tag("http.status_code", 200) @@ + log("doing some serious work here!") @@ + span("child span") +} ``` To propagate contexts across process boundaries, extraction and injection can be @@ -58,9 +71,13 @@ Due to the use of the (mutable) OpenTracing carrier APIs, injection and extracti are not referentially transparent. ```scala -val buffer = new TextMapAdapter(mutable.Map.empty.asJava) -for { - _ <- zio.inject(Format.Builtin.TEXT_MAP, buffer) - _ <- zio.spanFrom(Format.Builtin.TEXT_MAP, buffer, "child of remote span") -} yield buffer +ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + val buffer = new TextMapAdapter(mutable.Map.empty.asJava) + for { + _ <- ZIO.unit @@ inject(Format.Builtin.TEXT_MAP, buffer) + _ <- ZIO.unit @@ spanFrom(Format.Builtin.TEXT_MAP, buffer, "child of remote span") + } yield buffer +} ``` \ No newline at end of file diff --git a/docs/usecases/opentelemetry_example.md b/docs/usecases/opentelemetry_example.md index 9ae7a6c1..fbeba66e 100644 --- a/docs/usecases/opentelemetry_example.md +++ b/docs/usecases/opentelemetry_example.md @@ -15,14 +15,14 @@ docker run --rm -it \ jaegertracing/all-in-one:1.36 ``` -Then start the proxy server +Then start the proxy application ```bash -sbt "opentelemetryExample/runMain zio.telemetry.opentelemetry.example.ProxyServer" +sbt "opentelemetryExample/runMain zio.telemetry.opentelemetry.example.ProxyApp" ``` -and the backend server +and the backend application ```bash -sbt "opentelemetryExample/runMain zio.telemetry.opentelemetry.example.BackendServer" +sbt "opentelemetryExample/runMain zio.telemetry.opentelemetry.example.BackendApp" ``` Now perform the following request: ```bash diff --git a/docs/usecases/opentracing_example.md b/docs/usecases/opentracing_example.md index b6730f84..1f15c1bf 100644 --- a/docs/usecases/opentracing_example.md +++ b/docs/usecases/opentracing_example.md @@ -23,33 +23,36 @@ To check if it's running properly visit [Jaeger UI](http://localhost:16686/). More info can be found [here](https://www.jaegertracing.io/docs/1.6/getting-started/#all-in-one-docker-image). Our application contains two services: - 1. [Proxy](https://github.com/zio/zio-telemetry/blob/master/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyServer.scala) service - 2. [Backend](https://github.com/zio/zio-telemetry/blob/master/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendServer.scala) service + 1. [Proxy](https://github.com/zio/zio-telemetry/blob/master/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala) service + 2. [Backend](https://github.com/zio/zio-telemetry/blob/master/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendApp.scala) service ### Proxy Service Represents the entry point of the distributed system example. It exposes the `/statuses` endpoint which returns a list of system's services statuses. -The service consists of `ProxyServer` and `ProxyApp`. +The service consists of `ProxyHttpServer` and `ProxyHttpApp`. -#### ProxyServer +#### ProxyApp In order to start the service run: ```bash -sbt "opentracingExample/runMain zio.telemetry.opentracing.example.ProxyServer" +sbt "opentracingExample/runMain zio.telemetry.opentracing.example.ProxyApp" ``` The console should output ```bash -ProxyServer started on port 8080 +running zio.telemetry.opentracing.example.ProxyApp ``` if the server has been started properly. -#### ProxyApp +#### ProxyHttpApp -Provides the implementation of the service, which returns the status of the backend service and the proxy service itself. `Client` is used to retrieve the status of the backend service. +Provides the implementation of the service, which returns the status of the backend service and the proxy service itself. +`Client` is used to retrieve the status of the backend service. -This is also where the tracing of the application is done, by collecting the timings and logging things such as the span type and the HTTP method. The context is injected into a carrier, and passed along to the backend through `Client`, where a child span is created, and logging of the backend service is done. +This is also where the tracing of the application is done, by collecting the timings and logging things such as the span +type and the HTTP method. The context is injected into a carrier, and passed along to the backend through `Client`, +where a child span is created, and logging of the backend service is done. ### Backend Service @@ -57,20 +60,20 @@ Represents the "internal" service of the system. It exposes the `/status` endpoi The service consists of `BackendServer` and `BackendApp`. -#### BackendServer +#### BackendApp In order to start the service run: ```bash -sbt "opentracingExample/runMain zio.telemetry.opentracing.example.BackendServer" +sbt "opentracingExample/runMain zio.telemetry.opentracing.example.BackendApp" ``` The console should output ```bash -BackendServer started on port 9000 +running zio.telemetry.opentracing.example.BackendApp ``` if the server has been started properly. -#### BackendApp +#### BackendHttpApp Provides the implementation of the service, which is to simply return the status of the backend service. diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/Attributes.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/Attributes.scala index dc4de18e..205c4db2 100644 --- a/opencensus/src/main/scala/zio/telemetry/opencensus/Attributes.scala +++ b/opencensus/src/main/scala/zio/telemetry/opencensus/Attributes.scala @@ -3,6 +3,7 @@ package zio.telemetry.opencensus import io.opencensus.trace.AttributeValue object Attributes { + trait implicits { import scala.language.implicitConversions @@ -18,4 +19,5 @@ object Attributes { implicit def doubleToAttribute(d: Double): AttributeValue = AttributeValue.doubleAttributeValue(d) } + } diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/ErrorMapper.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/ErrorMapper.scala new file mode 100644 index 00000000..72dd38f1 --- /dev/null +++ b/opencensus/src/main/scala/zio/telemetry/opencensus/ErrorMapper.scala @@ -0,0 +1,12 @@ +package zio.telemetry.opencensus + +import io.opencensus.trace.Status + +case class ErrorMapper[E](body: PartialFunction[E, Status]) + +object ErrorMapper { + + def default[E]: ErrorMapper[E] = + ErrorMapper[E](Map.empty) + +} diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/Live.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/Live.scala deleted file mode 100644 index e7403469..00000000 --- a/opencensus/src/main/scala/zio/telemetry/opencensus/Live.scala +++ /dev/null @@ -1,145 +0,0 @@ -package zio.telemetry.opencensus - -import zio._ - -import io.opencensus.trace.AttributeValue -import io.opencensus.trace.BlankSpan -import io.opencensus.trace.Span -import io.opencensus.trace.SpanContext -import io.opencensus.trace.Status -import io.opencensus.trace.propagation.TextFormat -import io.opencensus.trace.Tracer - -object Live { - val live: URLayer[Tracer, Tracing] = - ZLayer.scoped(for { - tracer <- ZIO.service[Tracer] - tracing = FiberRef.make[Span](BlankSpan.INSTANCE).map(new Live(tracer, _)) - live <- ZIO.acquireRelease(tracing)(_.end) - } yield live) -} - -class Live(tracer: Tracer, root: FiberRef[Span]) extends Tracing { - val currentSpan_ : FiberRef[Span] = root - - def currentSpan: UIO[Span] = currentSpan_.get - - def span[R, E, A]( - name: String, - kind: Span.Kind = Span.Kind.SERVER, - toErrorStatus: ErrorMapper[E], - attributes: Map[String, AttributeValue] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - ZIO.scoped[R] { - for { - parent <- currentSpan_.get - span <- createSpan(parent, name, kind) - res <- finalizeSpanUsingEffect(putAttributes(attributes) *> effect, toErrorStatus)(span) - } yield res - } - - def root[R, E, A]( - name: String, - kind: Span.Kind, - toErrorStatus: ErrorMapper[E], - attributes: Map[String, AttributeValue] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - ZIO.scoped[R] { - createSpan(BlankSpan.INSTANCE, name, kind).flatMap(span => - finalizeSpanUsingEffect( - putAttributes(attributes) *> effect, - toErrorStatus - )(span) - ) - } - - def fromRemoteSpan[R, E, A]( - remote: SpanContext, - name: String, - kind: Span.Kind, - toErrorStatus: ErrorMapper[E], - attributes: Map[String, AttributeValue] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - ZIO.scoped[R] { - createSpanFromRemote(remote, name, kind).flatMap(span => - finalizeSpanUsingEffect( - putAttributes(attributes) *> effect, - toErrorStatus - )(span) - ) - } - - def putAttributes( - attributes: Map[String, AttributeValue] - ): ZIO[Any, Nothing, Unit] = - for { - current <- currentSpan_.get - _ <- ZIO.succeed(attributes.foreach { case (k, v) => - current.putAttribute(k, v) - }) - } yield () - - private def createSpan( - parent: Span, - name: String, - kind: Span.Kind - ): URIO[Scope, Span] = - ZIO.acquireRelease( - ZIO.succeed( - tracer - .spanBuilderWithExplicitParent(name, parent) - .setSpanKind(kind) - .startSpan() - ) - )(span => ZIO.succeed(span.end())) - - private def createSpanFromRemote( - parent: SpanContext, - name: String, - kind: Span.Kind - ): URIO[Scope, Span] = - ZIO.acquireRelease( - ZIO.succeed( - tracer - .spanBuilderWithRemoteParent(name, parent) - .setSpanKind(kind) - .startSpan() - ) - )(span => ZIO.succeed(span.end())) - - private def finalizeSpanUsingEffect[R, E, A]( - effect: ZIO[R, E, A], - toErrorStatus: ErrorMapper[E] - )(span: Span): ZIO[R, E, A] = - for { - r <- currentSpan_ - .locally(span)(effect) - .tapErrorCause(setErrorStatus(span, _, toErrorStatus)) - } yield r - - def inject[C]( - format: TextFormat, - carrier: C, - setter: TextFormat.Setter[C] - ): UIO[Unit] = - for { - current <- currentSpan - _ <- ZIO.succeed(format.inject(current.getContext, carrier, setter)) - } yield () - - private[opencensus] def end: UIO[Unit] = - for { - span <- currentSpan_.get - _ <- ZIO.succeed(span.end()) - } yield () - - private def setErrorStatus[E]( - span: Span, - cause: Cause[E], - toErrorStatus: ErrorMapper[E] - ): UIO[Unit] = { - val errorStatus = - cause.failureOption.flatMap(toErrorStatus.lift).getOrElse(Status.UNKNOWN) - ZIO.succeed(span.setStatus(errorStatus)) - } -} diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala index 413c04e6..f4b27395 100644 --- a/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala +++ b/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala @@ -1,110 +1,273 @@ package zio.telemetry.opencensus import zio._ -import io.opencensus.trace.Span -import io.opencensus.trace.AttributeValue -import io.opencensus.trace.SpanContext +import io.opencensus.trace._ import io.opencensus.trace.propagation.TextFormat -import zio.telemetry.opencensus.Tracing.defaultMapper -trait Tracing { +trait Tracing { self => + + def getCurrentSpan: UIO[Span] + def span[R, E, A]( name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R, E, A] + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] def root[R, E, A]( name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R, E, A] + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] def fromRemoteSpan[R, E, A]( remote: SpanContext, name: String, kind: Span.Kind = Span.Kind.SERVER, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R, E, A] + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + + def fromRootSpan[C, R, E, A]( + format: TextFormat, + carrier: C, + getter: TextFormat.Getter[C], + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] def inject[C]( format: TextFormat, carrier: C, setter: TextFormat.Setter[C] - ): UIO[Unit] + )(implicit trace: Trace): UIO[Unit] def putAttributes( attrs: Map[String, AttributeValue] - ): ZIO[Any, Nothing, Unit] + )(implicit trace: Trace): UIO[Unit] + + object aspects { + + def span[E1]( + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1], + attributes: Map[String, AttributeValue] = Map.empty + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.span(name, kind, toErrorStatus, attributes)(zio) + } + + def root[E1]( + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1], + attributes: Map[String, AttributeValue] = Map.empty + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.root(name, kind, toErrorStatus, attributes)(zio) + } + + def fromRemoteSpan[E1]( + remote: SpanContext, + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1], + attributes: Map[String, AttributeValue] = Map.empty + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.fromRemoteSpan(remote, name, kind, toErrorStatus, attributes)(zio) + } + + def fromRootSpan[C, E1]( + format: TextFormat, + carrier: C, + getter: TextFormat.Getter[C], + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1], + attributes: Map[String, AttributeValue] = Map.empty + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.fromRootSpan(format, carrier, getter, name, kind, toErrorStatus, attributes)(zio) + } + + def inject[C]( + format: TextFormat, + carrier: C, + setter: TextFormat.Setter[C] + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.inject(format, carrier, setter) *> zio + } + + def withAttributes( + attrs: (String, AttributeValue)* + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.putAttributes(attrs.toMap) *> zio + } + + } - private[opencensus] def end: UIO[Unit] } object Tracing { - def defaultMapper[E]: ErrorMapper[E] = Map.empty - def span[R, E, A]( - name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.span(name, kind, toErrorStatus, attributes)(effect)) + val live: URLayer[Tracer, Tracing] = + ZLayer.scoped(ZIO.service[Tracer].flatMap(scoped)) - def root[R, E, A]( - name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.root(name, kind, toErrorStatus, attributes)(effect)) + def scoped(tracer: Tracer): URIO[Scope, Tracing] = { + val acquire = for { + currentSpan <- FiberRef.make[Span](BlankSpan.INSTANCE) + } yield new Tracing { self => + override def getCurrentSpan: UIO[Span] = + currentSpan.get - def fromRemoteSpan[R, E, A]( - remote: SpanContext, - name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing]( - _.fromRemoteSpan(remote, name, kind, toErrorStatus, attributes)( - effect - ) - ) + override def span[R, E, A]( + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO.scoped[R] { + for { + parent <- getCurrentSpan + span <- createSpan(parent, name, kind) + res <- finalizeSpanUsingEffect(span, toErrorStatus)(putAttributes(attributes) *> effect) + } yield res + } - def putAttributes( - attributes: (String, AttributeValue)* - ): ZIO[Tracing, Nothing, Unit] = - ZIO.serviceWithZIO[Tracing](_.putAttributes(attributes.toMap)) + override def root[R, E, A]( + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO.scoped[R] { + createSpan(BlankSpan.INSTANCE, name, kind).flatMap { span => + finalizeSpanUsingEffect(span, toErrorStatus)( + putAttributes(attributes) *> effect + ) + } + } - def withAttributes[R, E, A]( - attributes: (String, AttributeValue)* - )(eff: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.putAttributes(attributes.toMap)) *> eff + override def fromRemoteSpan[R, E, A]( + remote: SpanContext, + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO.scoped[R] { + createSpanFromRemote(remote, name, kind).flatMap { span => + finalizeSpanUsingEffect(span, toErrorStatus)( + putAttributes(attributes) *> effect + ) + } + } + + override def fromRootSpan[C, R, E, A]( + format: TextFormat, + carrier: C, + getter: TextFormat.Getter[C], + name: String, + kind: Span.Kind = Span.Kind.SERVER, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E], + attributes: Map[String, AttributeValue] = Map.empty + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO + .attempt(format.extract(carrier, getter)) + .foldZIO( + _ => root(name, kind, toErrorStatus)(effect), + remote => fromRemoteSpan(remote, name, kind, toErrorStatus, attributes)(effect) + ) + + override def putAttributes( + attributes: Map[String, AttributeValue] + )(implicit trace: Trace): UIO[Unit] = + for { + current <- getCurrentSpan + _ <- ZIO.succeed(attributes.foreach { case (k, v) => + current.putAttribute(k, v) + }) + } yield () + + override def inject[C]( + format: TextFormat, + carrier: C, + setter: TextFormat.Setter[C] + )(implicit trace: Trace): UIO[Unit] = + for { + current <- getCurrentSpan + _ <- ZIO.succeed(format.inject(current.getContext, carrier, setter)) + } yield () + + private def createSpan( + parent: Span, + name: String, + kind: Span.Kind + )(implicit trace: Trace): URIO[Scope, Span] = + ZIO.acquireRelease( + ZIO.succeed( + tracer + .spanBuilderWithExplicitParent(name, parent) + .setSpanKind(kind) + .startSpan() + ) + )(span => ZIO.succeed(span.end())) + + private def createSpanFromRemote( + parent: SpanContext, + name: String, + kind: Span.Kind + )(implicit trace: Trace): URIO[Scope, Span] = + ZIO.acquireRelease( + ZIO.succeed( + tracer + .spanBuilderWithRemoteParent(name, parent) + .setSpanKind(kind) + .startSpan() + ) + )(span => ZIO.succeed(span.end())) + + private def finalizeSpanUsingEffect[R, E, A]( + span: Span, + toErrorStatus: ErrorMapper[E] + )(effect: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + currentSpan + .locally(span)(effect) + .tapErrorCause(setErrorStatus(span, _, toErrorStatus)) + + private def setErrorStatus[E]( + span: Span, + cause: Cause[E], + toErrorStatus: ErrorMapper[E] + )(implicit trace: Trace): UIO[Unit] = { + val errorStatus = + cause.failureOption + .flatMap(toErrorStatus.body.lift) + .getOrElse(Status.UNKNOWN) + + ZIO.succeed(span.setStatus(errorStatus)) + } + + } + + def release(tracing: Tracing) = + tracing.getCurrentSpan.flatMap(span => ZIO.succeed(span.end())) + + ZIO.acquireRelease(acquire)(release) + } - def fromRootSpan[C, R, E, A]( - format: TextFormat, - carrier: C, - getter: TextFormat.Getter[C], - name: String, - kind: Span.Kind = Span.Kind.SERVER, - toErrorStatus: ErrorMapper[E] = defaultMapper[E], - attributes: Map[String, AttributeValue] = Map() - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO - .attempt(format.extract(carrier, getter)) - .foldZIO( - _ => root(name, kind, toErrorStatus)(effect), - remote => fromRemoteSpan(remote, name, kind, toErrorStatus, attributes)(effect) - ) - - def inject[C, R]( - format: TextFormat, - carrier: C, - setter: TextFormat.Setter[C] - ): URIO[R with Tracing, Unit] = - ZIO.serviceWithZIO[Tracing](_.inject(format, carrier, setter)) } diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/package.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/package.scala index 294a66aa..4d8ef7bf 100644 --- a/opencensus/src/main/scala/zio/telemetry/opencensus/package.scala +++ b/opencensus/src/main/scala/zio/telemetry/opencensus/package.scala @@ -1,9 +1,5 @@ package zio.telemetry -import io.opencensus.trace.Status - package object opencensus { object implicits extends Attributes.implicits - - type ErrorMapper[E] = PartialFunction[E, Status] } diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/syntax.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/syntax.scala deleted file mode 100644 index 6c6c80eb..00000000 --- a/opencensus/src/main/scala/zio/telemetry/opencensus/syntax.scala +++ /dev/null @@ -1,41 +0,0 @@ -package zio.telemetry.opencensus - -import zio._ - -import io.opencensus.trace.AttributeValue -import io.opencensus.trace.SpanContext -import io.opencensus.trace.Span - -object syntax { - implicit final class OpenCensusZioOps[R, E, A](val effect: ZIO[R, E, A]) extends AnyVal { - def span( - name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = Tracing.defaultMapper[E], - attributes: Map[String, AttributeValue] - ): ZIO[R with Tracing, E, A] = - Tracing.span(name, kind, toErrorStatus, attributes)(effect) - - def root( - name: String, - kind: Span.Kind = null, - toErrorStatus: ErrorMapper[E] = Tracing.defaultMapper[E], - attributes: Map[String, AttributeValue] - ): ZIO[R with Tracing, E, A] = - Tracing.root(name, kind, toErrorStatus, attributes)(effect) - - def fromRemoteSpan( - remote: SpanContext, - name: String, - kind: Span.Kind, - toErrorStatus: ErrorMapper[E], - attributes: Map[String, AttributeValue] - ): ZIO[R with Tracing, E, A] = - Tracing.fromRemoteSpan(remote, name, kind, toErrorStatus, attributes)(effect) - - def withAttributes( - attributes: (String, AttributeValue)* - ): ZIO[R with Tracing, E, A] = - Tracing.withAttributes(attributes: _*)(effect) - } -} diff --git a/opentelemetry-example/src/main/resources/application.conf b/opentelemetry-example/src/main/resources/application.conf index 2577e3e7..73f22d1f 100644 --- a/opentelemetry-example/src/main/resources/application.conf +++ b/opentelemetry-example/src/main/resources/application.conf @@ -1,9 +1,11 @@ proxy { - host = "http://localhost:8080" + host = "0.0.0.0" + port = 8080 } backend { - host = "http://localhost:9000" + host = "0.0.0.0" + port = 9000 } tracer { diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala new file mode 100644 index 00000000..41287c1a --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala @@ -0,0 +1,25 @@ +package zio.telemetry.opentelemetry.example + +import zio.config.magnolia._ +import zio.config.typesafe.TypesafeConfig +import zio.telemetry.opentelemetry.Tracing +import zio.telemetry.opentelemetry.example.config.AppConfig +import zio.telemetry.opentelemetry.example.http.{ BackendHttpApp, BackendHttpServer } +import zio._ + +object BackendApp extends ZIOAppDefault { + + private val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) + + override def run: Task[ExitCode] = + ZIO + .serviceWithZIO[BackendHttpServer](_.start.exitCode) + .provide( + configLayer, + BackendHttpServer.live, + BackendHttpApp.live, + Tracing.live, + JaegerTracer.live + ) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendServer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendServer.scala deleted file mode 100644 index 1c94c8a1..00000000 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendServer.scala +++ /dev/null @@ -1,37 +0,0 @@ -package zio.telemetry.opentelemetry.example - -import sttp.model.Uri -import zhttp.service.server.ServerChannelFactory -import zhttp.service.{ EventLoopGroup, Server, ServerChannelFactory } -import zio.Console.printLine -import zio.config.getConfig -import zio.config.magnolia.{ descriptor, Descriptor } -import zio.config.typesafe.TypesafeConfig -import zio.telemetry.opentelemetry.Tracing -import zio.telemetry.opentelemetry.example.config.AppConfig -import zio.telemetry.opentelemetry.example.http.BackendApp -import zio.{ ZIO, ZIOAppDefault } - -object BackendServer extends ZIOAppDefault { - implicit val sttpUriDescriptor: Descriptor[Uri] = - Descriptor[String].transformOrFailLeft(Uri.parse)(_.toString) - - type AppEnv = Tracing with ServerChannelFactory with EventLoopGroup with AppConfig - - val server = - ZIO.scoped[AppEnv] { - for { - conf <- getConfig[AppConfig] - port = conf.backend.host.port.getOrElse(9000) - server = Server.port(port) ++ Server.app(BackendApp.routes) - _ <- server.make - _ <- printLine(s"BackendServer started on port $port") *> ZIO.never - } yield () - } - - val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) - val appLayer = (JaegerTracer.live >>> Tracing.live) ++ ServerChannelFactory.auto ++ EventLoopGroup.auto(0) - - override def run = - ZIO.provideLayer(configLayer >+> appLayer)(server.exitCode) -} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala index b0736584..ad76deae 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala @@ -14,12 +14,19 @@ import zio.telemetry.opentelemetry.example.config.AppConfig object JaegerTracer { def live: RLayer[AppConfig, Tracer] = - ZLayer(for { - c <- ZIO.service[AppConfig] - spanExporter <- ZIO.attempt(JaegerGrpcSpanExporter.builder().setEndpoint(c.tracer.host).build()) + ZLayer { + for { + config <- ZIO.service[AppConfig] + tracer <- makeTracer(config.tracer.host) + } yield tracer + } + + def makeTracer(host: String): Task[Tracer] = + for { + spanExporter <- ZIO.attempt(JaegerGrpcSpanExporter.builder().setEndpoint(host).build()) spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter)) tracerProvider <- - ZIO.succeed( + ZIO.attempt( SdkTracerProvider .builder() .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "opentelemetry-example"))) @@ -28,6 +35,6 @@ object JaegerTracer { ) openTelemetry <- ZIO.succeed(OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build()) tracer <- ZIO.succeed(openTelemetry.getTracer("zio.telemetry.opentelemetry.example.JaegerTracer")) - } yield tracer) + } yield tracer } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala new file mode 100644 index 00000000..138ec469 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala @@ -0,0 +1,33 @@ +package zio.telemetry.opentelemetry.example + +import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend +import zio.config.magnolia._ +import zio.config.typesafe.TypesafeConfig +import zio.telemetry.opentelemetry.Tracing +import zio.telemetry.opentelemetry.example.config.AppConfig +import zio.telemetry.opentelemetry.example.http.{ Client, ProxyHttpApp, ProxyHttpServer } +import zio._ + +object ProxyApp extends ZIOAppDefault { + + private val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) + + private val httpBackendLayer: TaskLayer[Backend] = + ZLayer.scoped { + ZIO.acquireRelease(AsyncHttpClientZioBackend())(_.close().ignore) + } + + override def run: Task[ExitCode] = + ZIO + .serviceWithZIO[ProxyHttpServer](_.start.exitCode) + .provide( + configLayer, + httpBackendLayer, + Client.live, + ProxyHttpServer.live, + ProxyHttpApp.live, + Tracing.live, + JaegerTracer.live + ) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyServer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyServer.scala deleted file mode 100644 index 1341088c..00000000 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyServer.scala +++ /dev/null @@ -1,50 +0,0 @@ -package zio.telemetry.opentelemetry.example - -import sttp.capabilities.WebSockets -import sttp.capabilities.zio.ZioStreams -import sttp.client3.SttpBackend -import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend -import sttp.model.Uri -import zhttp.service.server.ServerChannelFactory -import zhttp.service.{ EventLoopGroup, Server, ServerChannelFactory } -import zio.Console.printLine -import zio.config.getConfig -import zio.config.magnolia._ -import zio.config.typesafe.TypesafeConfig -import zio.telemetry.opentelemetry.Tracing -import zio.telemetry.opentelemetry.example.config.AppConfig -import zio.telemetry.opentelemetry.example.http.{ Client, ProxyApp } -import zio.{ Task, ZIO, ZIOAppDefault, ZLayer } - -object ProxyServer extends ZIOAppDefault { - implicit val sttpUriDescriptor: Descriptor[Uri] = - Descriptor[String].transformOrFailLeft(Uri.parse)(_.toString) - - type AppEnv = AppConfig with Client with Tracing with ServerChannelFactory with EventLoopGroup - - val server = - ZIO.scoped[AppEnv] { - for { - conf <- getConfig[AppConfig] - port = conf.proxy.host.port.getOrElse(8080) - server = Server.port(port) ++ Server.app(ProxyApp.routes) - _ <- server.make - _ <- printLine(s"ProxyServer started on port $port") *> ZIO.never - } yield () - } - - val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) - - val httpBackend: ZLayer[Any, Throwable, SttpBackend[Task, ZioStreams with WebSockets]] = - ZLayer.scoped { - ZIO.acquireRelease(AsyncHttpClientZioBackend())(_.close().ignore) - } - - val sttp: ZLayer[AppConfig, Throwable, Client] = httpBackend >>> Client.live - - val appEnv = - (JaegerTracer.live >>> Tracing.live) ++ sttp ++ ServerChannelFactory.auto ++ EventLoopGroup.auto(0) - - override def run = - ZIO.provideLayer(configLayer >+> appEnv)(server.exitCode) -} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala index 44460f84..b38332b6 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala @@ -1,3 +1,3 @@ package zio.telemetry.opentelemetry.example.config -final case class AppConfig(proxy: ProxyConfig, backend: BackendConfig, tracer: TracerHost) +final case class AppConfig(proxy: ProxyConfig, backend: BackendConfig, tracer: TracerConfig) diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/BackendConfig.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/BackendConfig.scala index 02ef5d65..2b063beb 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/BackendConfig.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/BackendConfig.scala @@ -1,5 +1,3 @@ package zio.telemetry.opentelemetry.example.config -import sttp.model.Uri - -final case class BackendConfig(host: Uri) +final case class BackendConfig(host: String, port: Int) diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/ProxyConfig.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/ProxyConfig.scala index 5862282a..95621c7a 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/ProxyConfig.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/ProxyConfig.scala @@ -1,5 +1,3 @@ package zio.telemetry.opentelemetry.example.config -import sttp.model.Uri - -final case class ProxyConfig(host: Uri) +final case class ProxyConfig(host: String, port: Int) diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala new file mode 100644 index 00000000..ef195c31 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala @@ -0,0 +1,3 @@ +package zio.telemetry.opentelemetry.example.config + +final case class TracerConfig(host: String) diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerHost.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerHost.scala deleted file mode 100644 index 6b3f8964..00000000 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerHost.scala +++ /dev/null @@ -1,3 +0,0 @@ -package zio.telemetry.opentelemetry.example.config - -final case class TracerHost(host: String) extends AnyVal diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala similarity index 68% rename from opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendApp.scala rename to opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala index 830f779b..3a11574f 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendApp.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala @@ -4,16 +4,17 @@ import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator import io.opentelemetry.context.propagation.{ TextMapGetter, TextMapPropagator } import zio.telemetry.opentelemetry.Tracing -import zio.telemetry.opentelemetry.TracingSyntax._ import zio.telemetry.opentelemetry.example.http.{ Status => ServiceStatus } import zhttp.http.{ !!, ->, /, Headers, Http, HttpApp, Method, Response } import zio.json.EncoderOps -import zio.ZIO +import zio._ import java.lang import scala.jdk.CollectionConverters._ -object BackendApp { +case class BackendHttpApp(tracing: Tracing) { + + import tracing.aspects._ val propagator: TextMapPropagator = W3CTraceContextPropagator.getInstance() val getter: TextMapGetter[Headers] = new TextMapGetter[Headers] { @@ -24,15 +25,20 @@ object BackendApp { carrier.headers.headerValue(key).orNull } - val routes: HttpApp[Tracing, Throwable] = + val routes: HttpApp[Any, Throwable] = Http.collectZIO { case request @ Method.GET -> !! / "status" => - val response = for { - _ <- Tracing.addEvent("event from backend before response") + (for { + _ <- tracing.addEvent("event from backend before response") response <- ZIO.succeed(Response.json(ServiceStatus.up("backend").toJson)) - _ <- Tracing.addEvent("event from backend after response") - } yield response - - response.spanFrom(propagator, request.headers, getter, "/status", SpanKind.SERVER) + _ <- tracing.addEvent("event from backend after response") + } yield response) @@ spanFrom(propagator, request.headers, getter, "/status", SpanKind.SERVER) } } + +object BackendHttpApp { + + val live: URLayer[Tracing, BackendHttpApp] = + ZLayer.fromFunction(BackendHttpApp.apply _) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala new file mode 100644 index 00000000..0c103d64 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala @@ -0,0 +1,24 @@ +package zio.telemetry.opentelemetry.example.http + +import zhttp.service.Server +import zio.Console.printLine +import zio._ +import zio.telemetry.opentelemetry.example.config.AppConfig + +case class BackendHttpServer(config: AppConfig, httpApp: BackendHttpApp) { + + def start: ZIO[Any, Throwable, Nothing] = + for { + _ <- Server.start(config.backend.port, httpApp.routes) + _ <- printLine(s"BackendHttpServer started on port ${config.backend.port}") + never <- ZIO.never + } yield never + +} + +object BackendHttpServer { + + val live: URLayer[AppConfig with BackendHttpApp, BackendHttpServer] = + ZLayer.fromFunction(BackendHttpServer.apply _) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala index 16d09b04..bbf56820 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala @@ -1,37 +1,39 @@ package zio.telemetry.opentelemetry.example.http -import sttp.capabilities.WebSockets -import sttp.capabilities.zio.ZioStreams import sttp.client3._ import sttp.client3.ziojson._ +import sttp.model.Uri import zio.telemetry.opentelemetry.example.config.AppConfig -import zio.{ Task, ZIO, ZLayer } +import zio._ +import zio.telemetry.opentelemetry.example.Backend + +case class Client(backend: Backend, config: AppConfig) { + + private val backendUrl = + Uri + .safeApply(config.backend.host, config.backend.port) + .map(_.withPath("status")) + .left + .map(new IllegalArgumentException(_)) + + def status(headers: Map[String, String]): Task[Statuses] = + for { + url <- ZIO.fromEither(backendUrl) + response <- backend + .send( + basicRequest + .get(url.withPath("status")) + .headers(headers) + .response(asJson[Status]) + ) + status = response.body.getOrElse(Status.down("backend")) + } yield Statuses(List(status, Status.up("proxy"))) -trait Client { - def status(headers: Map[String, String]): Task[Statuses] } object Client { - type Backend = SttpBackend[Task, ZioStreams with WebSockets] - - def status(headers: Map[String, String]): ZIO[Client, Throwable, Statuses] = - ZIO.environmentWithZIO[Client](_.get.status(headers)) - - val up = Status.up("proxy") - - val live: ZLayer[AppConfig with Backend, Throwable, Client] = ZLayer(for { - conf <- ZIO.service[AppConfig] - backend <- ZIO.service[Backend] - service = new Client { - def status(headers: Map[String, String]): Task[Statuses] = - backend - .send( - basicRequest.get(conf.backend.host.withPath("status")).headers(headers).response(asJson[Status]) - ) - .map { response => - val status = response.body.getOrElse(Status.down("backend")) - Statuses(List(status, up)) - } - } - } yield service) + + val live: RLayer[AppConfig with Backend, Client] = + ZLayer.fromFunction(Client.apply _) + } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyApp.scala deleted file mode 100644 index 24c2f5af..00000000 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyApp.scala +++ /dev/null @@ -1,33 +0,0 @@ -package zio.telemetry.opentelemetry.example.http - -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator -import io.opentelemetry.api.trace.{ SpanKind, StatusCode } -import io.opentelemetry.context.propagation.{ TextMapPropagator, TextMapSetter } -import zio.ZIO -import zio.telemetry.opentelemetry.Tracing.root -import zio.telemetry.opentelemetry.Tracing -import zhttp.http.{ !!, ->, /, Http, HttpApp, Method, Response } -import zio.json.EncoderOps - -import scala.collection.mutable - -object ProxyApp { - - val propagator: TextMapPropagator = W3CTraceContextPropagator.getInstance() - val setter: TextMapSetter[mutable.Map[String, String]] = (carrier, key, value) => carrier.update(key, value) - - val errorMapper: PartialFunction[Throwable, StatusCode] = { case _ => StatusCode.UNSET } - - val routes: HttpApp[Client with Tracing, Throwable] = Http.collectZIO { case Method.GET -> !! / "statuses" => - root("/statuses", SpanKind.SERVER, errorMapper) { - for { - carrier <- ZIO.succeed(mutable.Map[String, String]().empty) - _ <- Tracing.setAttribute("http.method", "get") - _ <- Tracing.addEvent("proxy-event") - _ <- Tracing.inject(propagator, carrier, setter) - res <- Client.status(carrier.toMap).map(s => Response.json(s.toJson)) - } yield res - } - } - -} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala new file mode 100644 index 00000000..b19ffd68 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala @@ -0,0 +1,44 @@ +package zio.telemetry.opentelemetry.example.http + +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.api.trace.{ SpanKind, StatusCode } +import io.opentelemetry.context.propagation.{ TextMapPropagator, TextMapSetter } +import zio._ +import zio.telemetry.opentelemetry.{ ErrorMapper, Tracing } +import zhttp.http.{ !!, ->, /, Http, HttpApp, Method, Response } +import zio.json.EncoderOps + +import scala.collection.mutable + +case class ProxyHttpApp(client: Client, tracing: Tracing) { + + import tracing.aspects._ + + private val propagator: TextMapPropagator = + W3CTraceContextPropagator.getInstance() + + private val setter: TextMapSetter[mutable.Map[String, String]] = + (carrier, key, value) => carrier.update(key, value) + + private val errorMapper: ErrorMapper[Throwable] = + ErrorMapper[Throwable] { case _ => StatusCode.UNSET } + + val routes: HttpApp[Any, Throwable] = + Http.collectZIO { case Method.GET -> !! / "statuses" => + (for { + _ <- tracing.setAttribute("http.method", "get") + _ <- tracing.addEvent("proxy-event") + carrier = mutable.Map.empty[String, String] + _ <- tracing.inject(propagator, carrier, setter) + statuses <- client.status(carrier.toMap) + } yield Response.json(statuses.toJson)) @@ root("/statuses", SpanKind.SERVER, errorMapper) + } + +} + +object ProxyHttpApp { + + val live: URLayer[Client with Tracing, ProxyHttpApp] = + ZLayer.fromFunction(ProxyHttpApp.apply _) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala new file mode 100644 index 00000000..897d8abb --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala @@ -0,0 +1,24 @@ +package zio.telemetry.opentelemetry.example.http + +import zhttp.service.Server +import zio.Console.printLine +import zio._ +import zio.telemetry.opentelemetry.example.config.AppConfig + +case class ProxyHttpServer(config: AppConfig, httpApp: ProxyHttpApp) { + + def start: ZIO[Any, Throwable, Nothing] = + for { + _ <- Server.start(config.proxy.port, httpApp.routes) + _ <- printLine(s"ProxyHttpServer started on port ${config.backend.port}") + never <- ZIO.never + } yield never + +} + +object ProxyHttpServer { + + val live: URLayer[AppConfig with ProxyHttpApp, ProxyHttpServer] = + ZLayer.fromFunction(ProxyHttpServer.apply _) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/package.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/package.scala new file mode 100644 index 00000000..125a252d --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/package.scala @@ -0,0 +1,12 @@ +package zio.telemetry.opentelemetry + +import sttp.capabilities.WebSockets +import sttp.capabilities.zio.ZioStreams +import sttp.client3.SttpBackend +import zio.Task + +package object example { + + type Backend = SttpBackend[Task, ZioStreams with WebSockets] + +} diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ContextPropagation.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ContextPropagation.scala deleted file mode 100644 index 3b1bebe2..00000000 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ContextPropagation.scala +++ /dev/null @@ -1,32 +0,0 @@ -package zio.telemetry.opentelemetry - -import io.opentelemetry.context.Context -import io.opentelemetry.context.propagation.{ TextMapGetter, TextMapPropagator, TextMapSetter } -import zio.{ UIO, ZIO } - -private[opentelemetry] object ContextPropagation { - - /** - * Extract and returns the context from carrier `C`. - */ - def extractContext[C]( - propagator: TextMapPropagator, - carrier: C, - getter: TextMapGetter[C] - ): UIO[Context] = - ZIO.uninterruptible { - ZIO.succeed(propagator.extract(Context.root(), carrier, getter)) - } - - /** - * Injects the context into carrier `C`. - */ - def injectContext[C]( - context: Context, - propagator: TextMapPropagator, - carrier: C, - setter: TextMapSetter[C] - ): UIO[Unit] = - ZIO.succeed(propagator.inject(context, carrier, setter)) - -} diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ErrorMapper.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ErrorMapper.scala new file mode 100644 index 00000000..eadc873a --- /dev/null +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/ErrorMapper.scala @@ -0,0 +1,12 @@ +package zio.telemetry.opentelemetry + +import io.opentelemetry.api.trace.StatusCode + +case class ErrorMapper[E](body: PartialFunction[E, StatusCode]) + +object ErrorMapper { + + def default[E]: ErrorMapper[E] = + ErrorMapper[E](Map.empty) + +} diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/Tracing.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/Tracing.scala index da4c49b6..aabf1ac3 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/Tracing.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/Tracing.scala @@ -6,17 +6,27 @@ import io.opentelemetry.api.trace._ import io.opentelemetry.context.Context import io.opentelemetry.context.propagation.{ TextMapGetter, TextMapPropagator, TextMapSetter } import zio._ -import zio.telemetry.opentelemetry.ContextPropagation.{ extractContext, injectContext } import java.util.concurrent.TimeUnit import scala.concurrent.ExecutionContext import scala.jdk.CollectionConverters._ -final class Tracing private (tracer: Tracer, currentContext: ContextStorage) { +trait Tracing { self => - def getCurrentContext: UIO[Context] = currentContext.get + /** + * Gets current Context + */ + def getCurrentContext(implicit trace: Trace): UIO[Context] - def getCurrentSpan: UIO[Span] = getCurrentContext.map(Span.fromContext) + /** + * Gets current Span + */ + def getCurrentSpan(implicit trace: Trace): UIO[Span] + + /** + * Gets the current SpanContext + */ + def getCurrentSpanContext(implicit trace: Trace): UIO[SpanContext] /** * Extracts the span from carrier `C` and set its child span with name 'spanName' as the current span. Ends the span @@ -27,18 +37,9 @@ final class Tracing private (tracer: Tracer, currentContext: ContextStorage) { carrier: C, getter: TextMapGetter[C], spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - extractContext(propagator, carrier, getter).flatMap { context => - ZIO.acquireReleaseWith { - createChildOf(context, spanName, spanKind) - } { case (r, _) => - r - } { case (_, ctx) => - finalizeSpanUsingEffect(effect, ctx, toErrorStatus) - } - } + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] /** * Extracts the span from carrier `C` and unsafely set its child span with name 'spanName' as the current span. You @@ -49,96 +50,50 @@ final class Tracing private (tracer: Tracer, currentContext: ContextStorage) { carrier: C, getter: TextMapGetter[C], spanName: String, - spanKind: SpanKind - ): URIO[Tracing, (Span, URIO[Tracing, Any])] = - for { - context <- extractContext(propagator, carrier, getter) - updated <- createChildOfUnsafe(context, spanName, spanKind) - old <- currentContext.getAndSet(updated) - span <- getCurrentSpan - finalize = end *> currentContext.set(old) - } yield (span, finalize) + spanKind: SpanKind = SpanKind.INTERNAL + )(implicit trace: Trace): UIO[(Span, UIO[Any])] /** - * Sets the current span to be the new root span with name 'spanName' Ends the span when the effect finishes. + * Sets the current span to be the new root span with name 'spanName'. Ends the span when the effect finishes. */ def root[R, E, A]( spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - ZIO.acquireReleaseWith { - createRoot(spanName, spanKind) - } { case (r, _) => - r - } { case (_, ctx) => - finalizeSpanUsingEffect(effect, ctx, toErrorStatus) - } + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] /** - * Sets the current span to be the child of the current span with name 'spanName' Ends the span when the effect + * Sets the current span to be the child of the current span with name 'spanName'. Ends the span when the effect * finishes. */ def span[R, E, A]( spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - getCurrentContext.flatMap { old => - ZIO.acquireReleaseWith { - createChildOf(old, spanName, spanKind) - } { case (r, _) => - r - } { case (_, ctx) => - finalizeSpanUsingEffect(effect, ctx, toErrorStatus) - } - } + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] /** - * Unsafely sets the current span to be the child of the current span with name 'spanName' You need to manually call + * Unsafely sets the current span to be the child of the current span with name 'spanName'. You need to manually call * the finalizer to end the span. Useful for interop. */ def spanUnsafe( spanName: String, - spanKind: SpanKind - ): UIO[(Span, UIO[Any])] = - for { - old <- getCurrentContext - updated <- createChildOfUnsafe(old, spanName, spanKind) - _ <- currentContext.set(updated) - span <- getCurrentSpan - finalize = end *> currentContext.set(old) - } yield (span, finalize) + spanKind: SpanKind = SpanKind.INTERNAL + )(implicit trace: Trace): UIO[(Span, UIO[Any])] /** * Introduces a thread-local scope during the execution allowing for non-zio context propagation. * * Closes the scope when the effect finishes. */ - def scopedEffect[A](effect: => A): Task[A] = - for { - currentContext <- getCurrentContext - eff <- ZIO.attempt { - val scope = currentContext.makeCurrent() - try effect - finally scope.close() - } - } yield eff + def scopedEffect[A](effect: => A)(implicit trace: Trace): Task[A] /** * Introduces a thread-local scope during the execution allowing for non-zio context propagation. * * Closes the scope when the effect finishes. */ - def scopedEffectTotal[A](effect: => A): UIO[A] = - for { - currentContext <- getCurrentContext - eff <- ZIO.succeed { - val scope = currentContext.makeCurrent() - try effect - finally scope.close() - } - } yield eff + def scopedEffectTotal[A](effect: => A)(implicit trace: Trace): UIO[A] /** * Introduces a thread-local scope from the currently active zio span allowing for non-zio context propagation. This @@ -150,15 +105,7 @@ final class Tracing private (tracer: Tracer, currentContext: ContextStorage) { * * CLoses the scope when the effect finishes */ - def scopedEffectFromFuture[A](make: ExecutionContext => scala.concurrent.Future[A]): Task[A] = - for { - currentContext <- getCurrentContext - eff <- ZIO.fromFuture { implicit ec => - val scope = currentContext.makeCurrent() - try make(ec) - finally scope.close() - } - } yield eff + def scopedEffectFromFuture[A](make: ExecutionContext => scala.concurrent.Future[A])(implicit trace: Trace): Task[A] /** * Injects the current span into carrier `C` @@ -167,37 +114,29 @@ final class Tracing private (tracer: Tracer, currentContext: ContextStorage) { propagator: TextMapPropagator, carrier: C, setter: TextMapSetter[C] - ): UIO[Unit] = - for { - current <- getCurrentContext - _ <- injectContext(current, propagator, carrier, setter) - } yield () + )(implicit trace: Trace): UIO[Unit] /** - * Create a child of 'span' with name 'spanName' as the current span. Ends the span when the effect finishes. + * Mark this effect as the child of an externally provided span. Ends the span when the effect finishes. + * zio-opentelemetry will mark the span as being the child of the external one. + * + * This is designed for use-cases where you are incrementally introducing zio & zio-telemetry in a project that + * already makes use of instrumentation, and you need to interoperate with futures-based code. + * + * The caller is solely responsible for managing the external span, including calling Span.end */ + def inSpan[R, E, A]( span: Span, spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R, E, A] = - ZIO.acquireReleaseWith { - createChildOf(Context.root().`with`(span), spanName, spanKind) - } { case (r, _) => - r - } { case (_, ctx) => - finalizeSpanUsingEffect(effect, ctx, toErrorStatus) - } + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] /** * Adds an event to the current span */ - def addEvent(name: String): UIO[Span] = - for { - nanoSeconds <- currentNanos - span <- getCurrentSpan - } yield span.addEvent(name, nanoSeconds, TimeUnit.NANOSECONDS) + def addEvent(name: String)(implicit trace: Trace): UIO[Span] /** * Adds an event with attributes to the current span. @@ -205,368 +144,214 @@ final class Tracing private (tracer: Tracer, currentContext: ContextStorage) { def addEventWithAttributes( name: String, attributes: Attributes - ): UIO[Span] = - for { - nanoSeconds <- currentNanos - span <- getCurrentSpan - } yield span.addEvent(name, attributes, nanoSeconds, TimeUnit.NANOSECONDS) + )(implicit trace: Trace): UIO[Span] /** * Sets an attribute of the current span. */ - def setAttribute(name: String, value: Boolean): UIO[Span] = - getCurrentSpan.map(_.setAttribute(name, value)) + def setAttribute(name: String, value: Boolean)(implicit trace: Trace): UIO[Span] /** * Sets an attribute of the current span. */ - def setAttribute(name: String, value: Double): UIO[Span] = - getCurrentSpan.map(_.setAttribute(name, value)) + def setAttribute(name: String, value: Double)(implicit trace: Trace): UIO[Span] /** * Sets an attribute of the current span. */ - def setAttribute(name: String, value: Long): UIO[Span] = - getCurrentSpan.map(_.setAttribute(name, value)) + def setAttribute(name: String, value: Long)(implicit trace: Trace): UIO[Span] /** * Sets an attribute of the current span. */ - def setAttribute(name: String, value: String): UIO[Span] = - getCurrentSpan.map(_.setAttribute(name, value)) - - def setAttribute[T](key: AttributeKey[T], value: T): UIO[Span] = - getCurrentSpan.map(_.setAttribute(key, value)) - - def setAttribute(name: String, values: Seq[String]): UIO[Span] = { - val v = values.asJava - getCurrentSpan.map(_.setAttribute(AttributeKey.stringArrayKey(name), v)) - } - - def setAttribute(name: String, values: Seq[Boolean])(implicit i1: DummyImplicit): UIO[Span] = { - val v = values.map(Boolean.box).asJava - getCurrentSpan.map(_.setAttribute(AttributeKey.booleanArrayKey(name), v)) - } - - def setAttribute(name: String, values: Seq[Long])(implicit - i1: DummyImplicit, - i2: DummyImplicit - ): UIO[Span] = { - val v = values.map(Long.box).asJava - getCurrentSpan.map(_.setAttribute(AttributeKey.longArrayKey(name), v)) - } - - def setAttribute(name: String, values: Seq[Double])(implicit - i1: DummyImplicit, - i2: DummyImplicit, - i3: DummyImplicit - ): UIO[Span] = { - val v = values.map(Double.box).asJava - getCurrentSpan.map(_.setAttribute(AttributeKey.doubleArrayKey(name), v)) - } - - /** - * Sets a baggage entry in the current context - */ - def setBaggage(name: String, value: String): UIO[Context] = - currentContext.updateAndGet(context => - Baggage.fromContext(context).toBuilder.put(name, value).build().storeInContext(context) - ) - - /** - * Gets the baggage from current context - */ - def getCurrentBaggage: UIO[Baggage] = - getCurrentContext.map(Baggage.fromContext) + def setAttribute(name: String, value: String)(implicit trace: Trace): UIO[Span] /** - * Gets the current SpanContext + * Sets an attribute of the current span. */ - def getCurrentSpanContext: UIO[SpanContext] = - getCurrentSpan.map(_.getSpanContext()) - - private def setErrorStatus[E]( - span: Span, - cause: Cause[E], - toErrorStatus: PartialFunction[E, StatusCode] - ): UIO[Span] = { - val errorStatus: StatusCode = cause.failureOption.flatMap(toErrorStatus.lift).getOrElse(StatusCode.UNSET) - ZIO.succeed(span.setStatus(errorStatus, cause.prettyPrint)) - } + def setAttribute[T](key: AttributeKey[T], value: T)(implicit trace: Trace): UIO[Span] /** - * Sets the `currentContext` to `context` only while `effect` runs, and error status of `span` according to any - * potential failure of effect. - */ - private def finalizeSpanUsingEffect[R, E, A]( - effect: ZIO[R, E, A], - context: Context, - toErrorStatus: PartialFunction[E, StatusCode] - ): ZIO[R, E, A] = - currentContext - .locally(context)(effect) - .tapErrorCause(setErrorStatus(Span.fromContext(context), _, toErrorStatus)) - - private def currentNanos: UIO[Long] = Clock.currentTime(TimeUnit.NANOSECONDS) - - private def createRoot(spanName: String, spanKind: SpanKind): UIO[(UIO[Unit], Context)] = - for { - nanoSeconds <- currentNanos - span <- ZIO.succeed( - tracer - .spanBuilder(spanName) - .setNoParent() - .setSpanKind(spanKind) - .setStartTimestamp(nanoSeconds, TimeUnit.NANOSECONDS) - .startSpan() - ) - } yield (endSpan(span), span.storeInContext(Context.root())) - - private def createChildOf( - parent: Context, - spanName: String, - spanKind: SpanKind - ): UIO[(UIO[Unit], Context)] = - for { - nanoSeconds <- currentNanos - span <- ZIO.succeed( - tracer - .spanBuilder(spanName) - .setParent(parent) - .setSpanKind(spanKind) - .setStartTimestamp(nanoSeconds, TimeUnit.NANOSECONDS) - .startSpan() - ) - } yield (endSpan(span), span.storeInContext(parent)) - - private def createChildOfUnsafe(parent: Context, spanName: String, spanKind: SpanKind): UIO[Context] = - for { - nanoSeconds <- currentNanos - span <- - ZIO.succeed( - tracer - .spanBuilder(spanName) - .setParent(parent) - .setSpanKind(spanKind) - .setStartTimestamp(nanoSeconds, TimeUnit.NANOSECONDS) - .startSpan() - ) - } yield span.storeInContext(parent) - - private def end: UIO[Any] = - for { - nanos <- currentNanos - context <- currentContext.get - span = Span.fromContext(context) - } yield span.end(nanos, TimeUnit.NANOSECONDS) - - private def endSpan(span: Span): UIO[Unit] = currentNanos.map(span.end(_, TimeUnit.NANOSECONDS)) - -} - -object Tracing { - - def getCurrentContext: URIO[Tracing, Context] = ZIO.serviceWithZIO(_.getCurrentContext) - - def getCurrentSpan: URIO[Tracing, Span] = ZIO.serviceWithZIO(_.getCurrentSpan) - - /** - * Extracts the span from carrier `C` and set its child span with name 'spanName' as the current span. Ends the span - * when the effect finishes. + * Sets an attribute of the current span. */ - def spanFrom[C, R, E, A]( - propagator: TextMapPropagator, - carrier: C, - getter: TextMapGetter[C], - spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.spanFrom(propagator, carrier, getter, spanName, spanKind, toErrorStatus)(effect)) + def setAttribute(name: String, values: Seq[String])(implicit trace: Trace): UIO[Span] /** - * Extracts the span from carrier `C` and unsafely set its child span with name 'spanName' as the current span. You - * need to make sure to call the finalize effect to end the span. Primarily useful for interop. + * Sets an attribute of the current span. */ - def spanFromUnsafe[C]( - propagator: TextMapPropagator, - carrier: C, - getter: TextMapGetter[C], - spanName: String, - spanKind: SpanKind - ): URIO[Tracing, (Span, URIO[Tracing, Any])] = - ZIO.serviceWithZIO[Tracing](_.spanFromUnsafe(propagator, carrier, getter, spanName, spanKind)) + def setAttribute(name: String, values: Seq[Boolean])(implicit i1: DummyImplicit, trace: Trace): UIO[Span] /** - * Sets the current span to be the new root span with name 'spanName' Ends the span when the effect finishes. + * Sets an attribute of the current span. */ - def root[R, E, A]( - spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.root(spanName, spanKind, toErrorStatus)(effect)) + def setAttribute(name: String, values: Seq[Long])(implicit + i1: DummyImplicit, + i2: DummyImplicit, + trace: Trace + ): UIO[Span] /** - * Sets the current span to be the child of the current span with name 'spanName' Ends the span when the effect - * finishes. + * Sets an attribute of the current span. */ - def span[R, E, A]( - spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.span(spanName, spanKind, toErrorStatus)(effect)) + def setAttribute(name: String, values: Seq[Double])(implicit + i1: DummyImplicit, + i2: DummyImplicit, + i3: DummyImplicit, + trace: Trace + ): UIO[Span] /** - * Unsafely sets the current span to be the child of the current span with name 'spanName' You need to manually call - * the finalizer to end the span. Useful for interop. + * Sets a baggage entry in the current context */ - def spanUnsafe( - spanName: String, - spanKind: SpanKind - ): URIO[Tracing, (Span, ZIO[Tracing, Nothing, Any])] = - ZIO.serviceWithZIO[Tracing](_.spanUnsafe(spanName, spanKind)) + def setBaggage(name: String, value: String)(implicit trace: Trace): UIO[Context] /** - * Introduces a thread-local scope during the execution allowing for non-zio context propagation. - * - * Closes the scope when the effect finishes. + * Gets the baggage from current context */ - def scopedEffect[A](effect: => A): ZIO[Tracing, Throwable, A] = - ZIO.serviceWithZIO[Tracing](_.scopedEffect(effect)) + def getCurrentBaggage(implicit trace: Trace): UIO[Baggage] + + object aspects { + + def spanFrom[C, E1]( + propagator: TextMapPropagator, + carrier: C, + getter: TextMapGetter[C], + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1] + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.spanFrom(propagator, carrier, getter, spanName, spanKind, toErrorStatus)(zio) + } - /** - * Introduces a thread-local scope during the execution allowing for non-zio context propagation. - * - * Closes the scope when the effect finishes. - */ - def scopedEffectTotal[A](effect: => A): ZIO[Tracing, Nothing, A] = - ZIO.serviceWithZIO[Tracing](_.scopedEffectTotal(effect)) + def root[E1]( + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1] + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.root(spanName, spanKind, toErrorStatus)(zio) + } - /** - * Introduces a thread-local scope from the currently active zio span allowing for non-zio context propagation. This - * scope will only be active during Future creation, so another mechanism must be used to ensure that the scope is - * passed into the Future callbacks. - * - * The java auto instrumentation package provides such a mechanism out of the box, so one is not provided as a part of - * this method. - * - * CLoses the scope when the effect finishes - */ - def scopedEffectFromFuture[A](make: ExecutionContext => scala.concurrent.Future[A]): ZIO[Tracing, Throwable, A] = - ZIO.serviceWithZIO[Tracing](_.scopedEffectFromFuture(make)) + def span[E1]( + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1] + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.span(spanName, spanKind, toErrorStatus)(zio) + } - /** - * Injects the current span into carrier `C` - */ - def inject[C]( - propagator: TextMapPropagator, - carrier: C, - setter: TextMapSetter[C] - ): URIO[Tracing, Unit] = - ZIO.serviceWithZIO[Tracing](_.inject(propagator, carrier, setter)) + def inSpan[E1]( + span: Span, + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E1] = ErrorMapper.default[E1] + ): ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] = + new ZIOAspect[Nothing, Any, E1, E1, Nothing, Any] { + override def apply[R, E >: E1 <: E1, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.inSpan(span, spanName, spanKind, toErrorStatus)(zio) + } - /** - * Create a child of 'span' with name 'spanName' as the current span. Ends the span when the effect finishes. - */ - def inSpan[R, E, A]( - span: Span, - spanName: String, - spanKind: SpanKind, - toErrorStatus: PartialFunction[E, StatusCode] - )(effect: ZIO[R, E, A]): ZIO[R with Tracing, E, A] = - ZIO.serviceWithZIO[Tracing](_.inSpan(span, spanName, spanKind, toErrorStatus)(effect)) + def addEvent(name: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.addEvent(name) + } - /** - * Adds an event to the current span - */ - def addEvent(name: String): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.addEvent(name)) + def addEventWithAttributes( + name: String, + attributes: Attributes + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.addEventWithAttributes(name, attributes) + } - /** - * Adds an event with attributes to the current span. - */ - def addEventWithAttributes( - name: String, - attributes: Attributes - ): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.addEventWithAttributes(name, attributes)) + def setAttribute(name: String, value: Boolean): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, value) + } - /** - * Sets an attribute of the current span. - */ - def setAttribute(name: String, value: Boolean): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, value)) + def setAttribute(name: String, value: Double): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, value) + } - /** - * Sets an attribute of the current span. - */ - def setAttribute(name: String, value: Double): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, value)) + def setAttribute(name: String, value: Long): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, value) + } - /** - * Sets an attribute of the current span. - */ - def setAttribute(name: String, value: Long): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, value)) + def setAttribute(name: String, value: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, value) + } - /** - * Sets an attribute of the current span. - */ - def setAttribute(name: String, value: String): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, value)) + def setAttribute[T](key: AttributeKey[T], value: T): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(key, value) + } - def setAttribute[T](key: AttributeKey[T], value: T): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(key, value)) + def setAttribute(name: String, values: Seq[String]): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, values) + } - def setAttribute(name: String, values: Seq[String]): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, values)) + def setAttribute(name: String, values: Seq[Boolean])(implicit + i1: DummyImplicit + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, values) + } - def setAttribute(name: String, values: Seq[Boolean])(implicit i1: DummyImplicit): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, values)(i1)) + def setAttribute(name: String, values: Seq[Long])(implicit + i1: DummyImplicit, + i2: DummyImplicit + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, values)(i1, i2, trace) + } - def setAttribute(name: String, values: Seq[Long])(implicit - i1: DummyImplicit, - i2: DummyImplicit - ): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, values)(i1, i2)) + def setAttribute(name: String, values: Seq[Double])(implicit + i1: DummyImplicit, + i2: DummyImplicit, + i3: DummyImplicit + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setAttribute(name, values)(i1, i2, i3, trace) + } - def setAttribute(name: String, values: Seq[Double])(implicit - i1: DummyImplicit, - i2: DummyImplicit, - i3: DummyImplicit - ): URIO[Tracing, Span] = - ZIO.serviceWithZIO[Tracing](_.setAttribute(name, values)(i1, i2, i3)) + def setBaggage(name: String, value: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + zio <* self.setBaggage(name, value) + } - /** - * Sets a baggage entry in the current context - */ - def setBaggage(name: String, value: String): URIO[Tracing, Context] = - ZIO.serviceWithZIO[Tracing](_.setBaggage(name, value)) + } - /** - * Gets the baggage from current context - */ - def getCurrentBaggage: URIO[Tracing, Baggage] = - ZIO.serviceWithZIO[Tracing](_.getCurrentBaggage) +} - /** - * Gets the current SpanContext - */ - def getCurrentSpanContext: URIO[Tracing, SpanContext] = - ZIO.serviceWithZIO[Tracing](_.getCurrentSpanContext) +object Tracing { - def scoped(tracer: Tracer): URIO[Scope, Tracing] = { - val tracing: URIO[Scope, Tracing] = + def live: URLayer[Tracer, Tracing] = + ZLayer.scoped(ZIO.service[Tracer].flatMap { tracer => FiberRef .make[Context](Context.root()) - .map(ref => new Tracing(tracer, ContextStorage.fiberRef(ref))) - - ZIO.acquireRelease(tracing)(_.end) - } - - def live: URLayer[Tracer, Tracing] = ZLayer.scoped(ZIO.service[Tracer].flatMap(scoped)) + .flatMap(ref => scoped(tracer, ContextStorage.fiberRef(ref))) + }) /** * Tracing context will be bidirectionally propagated between ZIO and non-ZIO code. @@ -580,11 +365,337 @@ object Tracing { Runtime.addSupervisor(new PropagatingSupervisor) ++ ZLayer.scoped(ZIO.service[Tracer].flatMap(scopedPropagating)) - private def scopedPropagating(tracer: Tracer): URIO[Scope, Tracing] = { - val tracing: URIO[Scope, Tracing] = - ZIO.succeed(new Tracing(tracer, ContextStorage.threadLocal)) + private def scopedPropagating(tracer: Tracer): URIO[Scope, Tracing] = + scoped(tracer, ContextStorage.threadLocal) + + def scoped(tracer: Tracer, currentContext: ContextStorage): URIO[Scope, Tracing] = { + val acquire: URIO[Scope, Tracing] = + ZIO.succeed { + new Tracing { self => + override def getCurrentContext(implicit trace: Trace): UIO[Context] = + currentContext.get + + override def getCurrentSpan(implicit trace: Trace): UIO[Span] = + getCurrentContext.map(Span.fromContext) + + override def getCurrentSpanContext(implicit trace: Trace): UIO[SpanContext] = + getCurrentSpan.map(_.getSpanContext()) + + override def spanFrom[C, R, E, A]( + propagator: TextMapPropagator, + carrier: C, + getter: TextMapGetter[C], + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + extractContext(propagator, carrier, getter).flatMap { context => + ZIO.acquireReleaseWith { + createChildOf(context, spanName, spanKind) + } { case (r, _) => + r + } { case (_, ctx) => + finalizeSpanUsingEffect(effect, ctx, toErrorStatus) + } + } + + override def spanFromUnsafe[C]( + propagator: TextMapPropagator, + carrier: C, + getter: TextMapGetter[C], + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL + )(implicit trace: Trace): UIO[(Span, UIO[Any])] = + for { + context <- extractContext(propagator, carrier, getter) + updated <- createChildOfUnsafe(context, spanName, spanKind) + old <- currentContext.getAndSet(updated) + span <- getCurrentSpan + finalize = end *> currentContext.set(old) + } yield (span, finalize) + + override def root[R, E, A]( + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO.acquireReleaseWith { + createRoot(spanName, spanKind) + } { case (r, _) => + r + } { case (_, ctx) => + finalizeSpanUsingEffect(effect, ctx, toErrorStatus) + } + + override def span[R, E, A]( + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + getCurrentContext.flatMap { old => + ZIO.acquireReleaseWith { + createChildOf(old, spanName, spanKind) + } { case (r, _) => + r + } { case (_, ctx) => + finalizeSpanUsingEffect(effect, ctx, toErrorStatus) + } + } + + override def spanUnsafe( + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL + )(implicit trace: Trace): UIO[(Span, UIO[Any])] = + for { + old <- getCurrentContext + updated <- createChildOfUnsafe(old, spanName, spanKind) + _ <- currentContext.set(updated) + span <- getCurrentSpan + finalize = end *> currentContext.set(old) + } yield (span, finalize) + + override def scopedEffect[A](effect: => A)(implicit trace: Trace): Task[A] = + for { + currentContext <- getCurrentContext + eff <- ZIO.attempt { + val scope = currentContext.makeCurrent() + try effect + finally scope.close() + } + } yield eff + + override def scopedEffectTotal[A](effect: => A)(implicit trace: Trace): UIO[A] = + for { + currentContext <- getCurrentContext + eff <- ZIO.succeed { + val scope = currentContext.makeCurrent() + try effect + finally scope.close() + } + } yield eff + + override def scopedEffectFromFuture[A]( + make: ExecutionContext => scala.concurrent.Future[A] + )(implicit trace: Trace): Task[A] = + for { + currentContext <- getCurrentContext + eff <- ZIO.fromFuture { implicit ec => + val scope = currentContext.makeCurrent() + try make(ec) + finally scope.close() + } + } yield eff + + override def inject[C]( + propagator: TextMapPropagator, + carrier: C, + setter: TextMapSetter[C] + )(implicit trace: Trace): UIO[Unit] = + for { + current <- getCurrentContext + _ <- injectContext(current, propagator, carrier, setter) + } yield () + + override def inSpan[R, E, A]( + span: Span, + spanName: String, + spanKind: SpanKind = SpanKind.INTERNAL, + toErrorStatus: ErrorMapper[E] = ErrorMapper.default[E] + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO.acquireReleaseWith { + createChildOf(Context.root().`with`(span), spanName, spanKind) + } { case (r, _) => + r + } { case (_, ctx) => + finalizeSpanUsingEffect(effect, ctx, toErrorStatus) + } + + override def addEvent(name: String)(implicit trace: Trace): UIO[Span] = + for { + nanoSeconds <- currentNanos + span <- getCurrentSpan + } yield span.addEvent(name, nanoSeconds, TimeUnit.NANOSECONDS) + + override def addEventWithAttributes( + name: String, + attributes: Attributes + )(implicit trace: Trace): UIO[Span] = + for { + nanoSeconds <- currentNanos + span <- getCurrentSpan + } yield span.addEvent(name, attributes, nanoSeconds, TimeUnit.NANOSECONDS) + + override def setAttribute(name: String, value: Boolean)(implicit trace: Trace): UIO[Span] = + getCurrentSpan.map(_.setAttribute(name, value)) + + override def setAttribute(name: String, value: Double)(implicit trace: Trace): UIO[Span] = + getCurrentSpan.map(_.setAttribute(name, value)) + + override def setAttribute(name: String, value: Long)(implicit trace: Trace): UIO[Span] = + getCurrentSpan.map(_.setAttribute(name, value)) + + override def setAttribute(name: String, value: String)(implicit trace: Trace): UIO[Span] = + getCurrentSpan.map(_.setAttribute(name, value)) + + override def setAttribute[T](key: AttributeKey[T], value: T)(implicit trace: Trace): UIO[Span] = + getCurrentSpan.map(_.setAttribute(key, value)) + + override def setAttribute(name: String, values: Seq[String])(implicit trace: Trace): UIO[Span] = { + val v = values.asJava + getCurrentSpan.map(_.setAttribute(AttributeKey.stringArrayKey(name), v)) + } + + override def setAttribute(name: String, values: Seq[Boolean])(implicit + i1: DummyImplicit, + trace: Trace + ): UIO[Span] = { + val v = values.map(Boolean.box).asJava + getCurrentSpan.map(_.setAttribute(AttributeKey.booleanArrayKey(name), v)) + } + + override def setAttribute(name: String, values: Seq[Long])(implicit + i1: DummyImplicit, + i2: DummyImplicit, + trace: Trace + ): UIO[Span] = { + val v = values.map(Long.box).asJava + getCurrentSpan.map(_.setAttribute(AttributeKey.longArrayKey(name), v)) + } + + override def setAttribute(name: String, values: Seq[Double])(implicit + i1: DummyImplicit, + i2: DummyImplicit, + i3: DummyImplicit, + trace: Trace + ): UIO[Span] = { + val v = values.map(Double.box).asJava + getCurrentSpan.map(_.setAttribute(AttributeKey.doubleArrayKey(name), v)) + } + + override def setBaggage(name: String, value: String)(implicit trace: Trace): UIO[Context] = + currentContext.updateAndGet { context => + Baggage + .fromContext(context) + .toBuilder + .put(name, value) + .build() + .storeInContext(context) + } + + override def getCurrentBaggage(implicit trace: Trace): UIO[Baggage] = + getCurrentContext.map(Baggage.fromContext) + + private def setErrorStatus[E]( + span: Span, + cause: Cause[E], + toErrorStatus: ErrorMapper[E] + )(implicit trace: Trace): UIO[Span] = { + val errorStatus = + cause.failureOption + .flatMap(toErrorStatus.body.lift) + .getOrElse(StatusCode.UNSET) + + ZIO.succeed(span.setStatus(errorStatus, cause.prettyPrint)) + } + + /** + * Sets the `currentContext` to `context` only while `effect` runs, and error status of `span` according to + * any potential failure of effect. + */ + private def finalizeSpanUsingEffect[R, E, A]( + effect: ZIO[R, E, A], + context: Context, + toErrorStatus: ErrorMapper[E] + )(implicit trace: Trace): ZIO[R, E, A] = + currentContext + .locally(context)(effect) + .tapErrorCause(setErrorStatus(Span.fromContext(context), _, toErrorStatus)) + + private def currentNanos(implicit trace: Trace): UIO[Long] = + Clock.currentTime(TimeUnit.NANOSECONDS) + + private def createRoot(spanName: String, spanKind: SpanKind)(implicit + trace: Trace + ): UIO[(UIO[Unit], Context)] = + for { + nanoSeconds <- currentNanos + span <- ZIO.succeed( + tracer + .spanBuilder(spanName) + .setNoParent() + .setSpanKind(spanKind) + .setStartTimestamp(nanoSeconds, TimeUnit.NANOSECONDS) + .startSpan() + ) + } yield (endSpan(span), span.storeInContext(Context.root())) + + private def createChildOf(parent: Context, spanName: String, spanKind: SpanKind)(implicit + trace: Trace + ): UIO[(UIO[Unit], Context)] = + for { + nanoSeconds <- currentNanos + span <- ZIO.succeed( + tracer + .spanBuilder(spanName) + .setParent(parent) + .setSpanKind(spanKind) + .setStartTimestamp(nanoSeconds, TimeUnit.NANOSECONDS) + .startSpan() + ) + } yield (endSpan(span), span.storeInContext(parent)) + + private def createChildOfUnsafe(parent: Context, spanName: String, spanKind: SpanKind)(implicit + trace: Trace + ): UIO[Context] = + for { + nanoSeconds <- currentNanos + span <- + ZIO.succeed( + tracer + .spanBuilder(spanName) + .setParent(parent) + .setSpanKind(spanKind) + .setStartTimestamp(nanoSeconds, TimeUnit.NANOSECONDS) + .startSpan() + ) + } yield span.storeInContext(parent) + + private def endSpan(span: Span)(implicit trace: Trace): UIO[Unit] = + currentNanos.flatMap(nanos => ZIO.succeed(span.end(nanos, TimeUnit.NANOSECONDS))) + + private def end(implicit trace: Trace): UIO[Any] = + getCurrentSpan.flatMap(endSpan) + + /** + * Extract and returns the context from carrier `C`. + */ + private def extractContext[C]( + propagator: TextMapPropagator, + carrier: C, + getter: TextMapGetter[C] + )(implicit trace: Trace): UIO[Context] = + ZIO.uninterruptible { + ZIO.succeed(propagator.extract(Context.root(), carrier, getter)) + } + + /** + * Injects the context into carrier `C`. + */ + private def injectContext[C]( + context: Context, + propagator: TextMapPropagator, + carrier: C, + setter: TextMapSetter[C] + )(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(propagator.inject(context, carrier, setter)) + + } + } + + def release(tracing: Tracing) = + tracing.getCurrentSpan.flatMap(span => ZIO.succeed(span.end())) - ZIO.acquireRelease(tracing)(_.end) + ZIO.acquireRelease(acquire)(release) } } diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/TracingSyntax.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/TracingSyntax.scala deleted file mode 100644 index 9fe198df..00000000 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/TracingSyntax.scala +++ /dev/null @@ -1,99 +0,0 @@ -package zio.telemetry.opentelemetry - -import io.opentelemetry.api.common.{ AttributeKey, Attributes } -import io.opentelemetry.context.propagation.{ TextMapGetter, TextMapPropagator } -import io.opentelemetry.api.trace.{ Span, SpanKind, StatusCode } -import zio.ZIO - -object TracingSyntax { - - implicit final class OpenTelemetryZioOps[-R, +E, +A](val effect: ZIO[R, E, A]) extends AnyVal { - - def spanFrom[C]( - propagator: TextMapPropagator, - carrier: C, - getter: TextMapGetter[C], - spanName: String, - spanKind: SpanKind = SpanKind.INTERNAL, - toErrorStatus: PartialFunction[E, StatusCode] = Map.empty - ): ZIO[R with Tracing, E, A] = - Tracing.spanFrom(propagator, carrier, getter, spanName, spanKind, toErrorStatus)(effect) - - def root( - spanName: String, - spanKind: SpanKind = SpanKind.INTERNAL, - toErrorStatus: PartialFunction[E, StatusCode] = Map.empty - ): ZIO[R with Tracing, E, A] = Tracing.root(spanName, spanKind, toErrorStatus)(effect) - - def span( - spanName: String, - spanKind: SpanKind = SpanKind.INTERNAL, - toErrorStatus: PartialFunction[E, StatusCode] = Map.empty - ): ZIO[R with Tracing, E, A] = Tracing.span(spanName, spanKind, toErrorStatus)(effect) - - /** - * Mark this effect as the child of an externally provided span. zio-opentelemetry will mark the span as being the - * child of the external one. - * - * This is designed for use-cases where you are incrementally introducing zio & zio-telemetry in a project that - * already makes use of instrumentation, and you need to interoperate with futures-based code. - * - * The caller is solely responsible for managing the external span, including calling Span.end - */ - def inSpan( - span: Span, - spanName: String, - spanKind: SpanKind = SpanKind.INTERNAL, - toErrorStatus: PartialFunction[E, StatusCode] = Map.empty - ): ZIO[R with Tracing, E, A] = - Tracing.inSpan(span, spanName, spanKind, toErrorStatus)(effect) - - def addEvent(name: String): ZIO[Tracing with R, E, A] = - effect <* Tracing.addEvent(name) - - def addEventWithAttributes( - name: String, - attributes: Attributes - ): ZIO[Tracing with R, E, A] = - effect <* Tracing.addEventWithAttributes(name, attributes) - - def setAttribute(name: String, value: Boolean): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, value) - - def setAttribute(name: String, value: Double): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, value) - - def setAttribute(name: String, value: Long): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, value) - - def setAttribute(name: String, value: String): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, value) - - def setAttribute[T](key: AttributeKey[T], value: T): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(key, value) - - def setAttribute(name: String, values: Seq[String]): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, values) - - def setAttribute(name: String, values: Seq[Boolean])(implicit - i1: DummyImplicit - ): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, values) - - def setAttribute(name: String, values: Seq[Long])(implicit - i1: DummyImplicit, - i2: DummyImplicit - ): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, values)(i1, i2) - - def setAttribute(name: String, values: Seq[Double])(implicit - i1: DummyImplicit, - i2: DummyImplicit, - i3: DummyImplicit - ): ZIO[Tracing with R, E, A] = - effect <* Tracing.setAttribute(name, values)(i1, i2, i3) - - def setBaggage(name: String, value: String): ZIO[Tracing with R, E, A] = - effect <* Tracing.setBaggage(name, value) - } -} diff --git a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/TracingTest.scala b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/TracingTest.scala index a7682c3f..2ee55dee 100644 --- a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/TracingTest.scala +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/TracingTest.scala @@ -6,8 +6,6 @@ import io.opentelemetry.api.trace.{ Span, SpanId, Tracer } import io.opentelemetry.sdk.trace.SdkTracerProvider import io.opentelemetry.sdk.trace.data.SpanData -import zio.telemetry.opentelemetry.Tracing.inject -import zio.telemetry.opentelemetry.TracingSyntax._ import zio.test.Assertion._ import zio.test.assert import zio.test.TestClock @@ -17,11 +15,9 @@ import scala.collection.mutable import scala.concurrent.Future import scala.jdk.CollectionConverters._ import io.opentelemetry.context.Context -import io.opentelemetry.context.propagation.{ TextMapGetter, TextMapSetter } import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor -import java.lang import zio.test.ZIOSpecDefault object TracingTest extends ZIOSpecDefault { @@ -38,7 +34,7 @@ object TracingTest extends ZIOSpecDefault { ZEnvironment(inMemoryTracing).add(tracer) }) - val tracingMockLayer: ULayer[InMemorySpanExporter with Tracing with Tracer] = + val tracingMockLayer: ULayer[Tracing with InMemorySpanExporter with Tracer] = inMemoryTracerLayer >>> (Tracing.live ++ inMemoryTracerLayer) def getFinishedSpans = @@ -56,214 +52,249 @@ object TracingTest extends ZIOSpecDefault { }.provideLayer(inMemoryTracerLayer), suite("spans")( test("childSpan") { - for { - _ <- ZIO.unit.span("Child").span("Root") - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - child = spans.find(_.getName == "Child") - } yield assert(root)(isSome(anything)) && - assert(child)( - isSome( - hasField[SpanData, String]( - "parentSpanId", - _.getParentSpanId, - equalTo(root.get.getSpanId) + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- ZIO.unit @@ span("Child") @@ span("Root") + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + child = spans.find(_.getName == "Child") + } yield assert(root)(isSome(anything)) && + assert(child)( + isSome( + hasField[SpanData, String]( + "parentSpanId", + _.getParentSpanId, + equalTo(root.get.getSpanId) + ) ) ) - ) + } }, test("scopedEffect") { - for { - _ <- Tracing.scopedEffect { - val span = Span.current() - span.addEvent("In legacy code") - if (Context.current() == Context.root()) throw new RuntimeException("Current context is root!") - span.addEvent("Finishing legacy code") - }.span("Scoped") - .span("Root") - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - scoped = spans.find(_.getName == "Scoped") - tags = scoped.get.getEvents.asScala.toList.map(_.getName) - } yield assert(root)(isSome(anything)) && - assert(scoped)( - isSome( - hasField[SpanData, String]( - "parentSpanId", - _.getParentSpanId, - equalTo(root.get.getSpanId) + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- tracing.scopedEffect { + val span = Span.current() + span.addEvent("In legacy code") + if (Context.current() == Context.root()) throw new RuntimeException("Current context is root!") + span.addEvent("Finishing legacy code") + } @@ span[Throwable]("Scoped") @@ span[Throwable]("Root") + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + scoped = spans.find(_.getName == "Scoped") + tags = scoped.get.getEvents.asScala.toList.map(_.getName) + } yield assert(root)(isSome(anything)) && + assert(scoped)( + isSome( + hasField[SpanData, String]( + "parentSpanId", + _.getParentSpanId, + equalTo(root.get.getSpanId) + ) ) - ) - ) && assert(tags)( - equalTo(List("In legacy code", "Finishing legacy code")) - ) + ) && + assert(tags)(equalTo(List("In legacy code", "Finishing legacy code"))) + } }, test("scopedEffectTotal") { - for { - _ <- Tracing.scopedEffectTotal { - val span = Span.current() - span.addEvent("In legacy code") - if (Context.current() == Context.root()) throw new RuntimeException("Current context is root!") - Thread.sleep(10) - if (Context.current() == Context.root()) throw new RuntimeException("Current context is root!") - span.addEvent("Finishing legacy code") - }.span("Scoped") - .span("Root") - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - scoped = spans.find(_.getName == "Scoped") - tags = scoped.get.getEvents.asScala.toList.map(_.getName) - } yield assert(root)(isSome(anything)) && - assert(scoped)( - isSome( - hasField[SpanData, String]( - "parentSpanId", - _.getParentSpanId, - equalTo(root.get.getSpanId) + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- tracing.scopedEffectTotal { + val span = Span.current() + span.addEvent("In legacy code") + if (Context.current() == Context.root()) throw new RuntimeException("Current context is root!") + Thread.sleep(10) + if (Context.current() == Context.root()) throw new RuntimeException("Current context is root!") + span.addEvent("Finishing legacy code") + } @@ span("Scoped") @@ span("Root") + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + scoped = spans.find(_.getName == "Scoped") + tags = scoped.get.getEvents.asScala.toList.map(_.getName) + } yield assert(root)(isSome(anything)) && + assert(scoped)( + isSome( + hasField[SpanData, String]( + "parentSpanId", + _.getParentSpanId, + equalTo(root.get.getSpanId) + ) ) - ) - ) && assert(tags)( - equalTo(List("In legacy code", "Finishing legacy code")) - ) + ) && + assert(tags)(equalTo(List("In legacy code", "Finishing legacy code"))) + } }, test("scopedEffectFromFuture") { - for { - result <- Tracing.scopedEffectFromFuture { _ => - Future.successful { - val span = Span.current() - span.addEvent("In legacy code") - if (Context.current() == Context.root()) - throw new RuntimeException("Current context is root!") - span.addEvent("Finishing legacy code") - 1 - } - }.span("Scoped").span("Root") - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - scoped = spans.find(_.getName == "Scoped") - tags = scoped.get.getEvents.asScala.toList.map(_.getName) - } yield assert(result)(equalTo(1)) && - assert(root)(isSome(anything)) && - assert(scoped)( - isSome( - hasField[SpanData, String]( - "parentSpanId", - _.getParentSpanId, - equalTo(root.get.getSpanId) + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + result <- tracing.scopedEffectFromFuture { _ => + Future.successful { + val span = Span.current() + span.addEvent("In legacy code") + if (Context.current() == Context.root()) + throw new RuntimeException("Current context is root!") + span.addEvent("Finishing legacy code") + 1 + } + } @@ span[Throwable]("Scoped") @@ span[Throwable]("Root") + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + scoped = spans.find(_.getName == "Scoped") + tags = scoped.get.getEvents.asScala.toList.map(_.getName) + } yield assert(result)(equalTo(1)) && + assert(root)(isSome(anything)) && + assert(scoped)( + isSome( + hasField[SpanData, String]( + "parentSpanId", + _.getParentSpanId, + equalTo(root.get.getSpanId) + ) ) - ) - ) && assert(tags)( - equalTo(List("In legacy code", "Finishing legacy code")) - ) + ) && + assert(tags)(equalTo(List("In legacy code", "Finishing legacy code"))) + } }, test("rootSpan") { - for { - _ <- ZIO.unit.root("ROOT2").root("ROOT") - spans <- getFinishedSpans - root = spans.find(_.getName == "ROOT") - child = spans.find(_.getName == "ROOT2") - } yield assert(root)(isSome(anything)) && - assert(child)( + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- ZIO.unit @@ root("ROOT2") @@ root("ROOT") + spans <- getFinishedSpans + root = spans.find(_.getName == "ROOT") + child = spans.find(_.getName == "ROOT2") + } yield assert(root)(isSome(anything)) && + assert(child)( + isSome( + hasField[SpanData, String]( + "parent", + _.getParentSpanId, + equalTo(SpanId.getInvalid) + ) + ) + ) + } + }, + test("inSpan") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + res <- inMemoryTracer + (_, tracer) = res + externallyProvidedRootSpan = tracer.spanBuilder("external").startSpan() + scope = externallyProvidedRootSpan.makeCurrent() + _ <- ZIO.unit @@ inSpan(externallyProvidedRootSpan, "zio-otel-child") + _ = externallyProvidedRootSpan.end() + _ = scope.close() + spans <- getFinishedSpans + child = spans.find(_.getName == "zio-otel-child") + } yield assert(child)( isSome( hasField[SpanData, String]( "parent", _.getParentSpanId, - equalTo(SpanId.getInvalid) + equalTo(externallyProvidedRootSpan.getSpanContext.getSpanId) ) ) ) - }, - test("inSpan") { - for { - res <- inMemoryTracer - (_, tracer) = res - externallyProvidedRootSpan = tracer.spanBuilder("external").startSpan() - scope = externallyProvidedRootSpan.makeCurrent() - _ <- ZIO.unit.inSpan(externallyProvidedRootSpan, "zio-otel-child") - _ = externallyProvidedRootSpan.end() - _ = scope.close() - spans <- getFinishedSpans - child = spans.find(_.getName == "zio-otel-child") - } yield assert(child)( - isSome( - hasField[SpanData, String]( - "parent", - _.getParentSpanId, - equalTo(externallyProvidedRootSpan.getSpanContext.getSpanId) - ) - ) - ) + } }, test("inject - extract roundtrip") { - val propagator = W3CTraceContextPropagator.getInstance() - val carrier: mutable.Map[String, String] = mutable.Map().empty + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ - val getter: TextMapGetter[mutable.Map[String, String]] = new TextMapGetter[mutable.Map[String, String]] { - override def keys(carrier: mutable.Map[String, String]): lang.Iterable[String] = - carrier.keys.asJava + val propagator = W3CTraceContextPropagator.getInstance() + val carrier: mutable.Map[String, String] = mutable.Map().empty - override def get(carrier: mutable.Map[String, String], key: String): String = - carrier.get(key).orNull + for { + _ <- (for { + _ <- tracing.inject(propagator, carrier, setter = TextMapAdapter) @@ span("foo") + _ <- ZIO.unit @@ spanFrom(propagator, carrier, getter = TextMapAdapter, "baz") @@ span("bar") + } yield ()) @@ span("ROOT") + spans <- getFinishedSpans + root = spans.find(_.getName == "ROOT") + foo = spans.find(_.getName == "foo") + bar = spans.find(_.getName == "bar") + baz = spans.find(_.getName == "baz") + } yield assert(root)(isSome(anything)) && + assert(foo)(isSome(anything)) && + assert(bar)(isSome(anything)) && + assert(baz)(isSome(anything)) && + assert(foo.get.getParentSpanId)(equalTo(root.get.getSpanId)) && + assert(bar.get.getParentSpanId)(equalTo(root.get.getSpanId)) && + assert(baz.get.getParentSpanId)(equalTo(foo.get.getSpanId)) } - - val setter: TextMapSetter[mutable.Map[String, String]] = - (carrier, key, value) => carrier.update(key, value) - - val injectExtract = - inject( - propagator, - carrier, - setter - ).span("foo") *> ZIO.unit - .spanFrom(propagator, carrier, getter, "baz") - .span("bar") - - for { - _ <- injectExtract.span("ROOT") - spans <- getFinishedSpans - root = spans.find(_.getName == "ROOT") - foo = spans.find(_.getName == "foo") - bar = spans.find(_.getName == "bar") - baz = spans.find(_.getName == "baz") - } yield assert(root)(isSome(anything)) && - assert(foo)(isSome(anything)) && - assert(bar)(isSome(anything)) && - assert(baz)(isSome(anything)) && - assert(foo.get.getParentSpanId)(equalTo(root.get.getSpanId)) && - assert(bar.get.getParentSpanId)(equalTo(root.get.getSpanId)) && - assert(baz.get.getParentSpanId)(equalTo(foo.get.getSpanId)) }, test("tagging") { - for { - _ <- ZIO.unit - .setAttribute("boolean", true) - .setAttribute("int", 1) - .setAttribute("string", "foo") - .setAttribute("booleans", Seq(true, false)) - .setAttribute("longs", Seq(1L, 2L)) - .setAttribute("strings", Seq("foo", "bar")) - .span("foo") - spans <- getFinishedSpans - tags = spans.head.getAttributes - } yield assert(tags.get(AttributeKey.booleanKey("boolean")))(equalTo(Boolean.box(true))) && - assert(tags.get(AttributeKey.longKey("int")))(equalTo(Long.box(1))) && - assert(tags.get(AttributeKey.stringKey("string")))(equalTo("foo")) && - assert(tags.get(AttributeKey.booleanArrayKey("booleans")))( - equalTo(Seq(Boolean.box(true), Boolean.box(false)).asJava) - ) && - assert(tags.get(AttributeKey.longArrayKey("longs")))( - equalTo(Seq(Long.box(1L), Long.box(2L)).asJava) - ) && - assert(tags.get(AttributeKey.stringArrayKey("strings")))(equalTo(Seq("foo", "bar").asJava)) + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- (for { + _ <- tracing.setAttribute("boolean", true) + _ <- tracing.setAttribute("int", 1) + _ <- tracing.setAttribute("string", "foo") + _ <- tracing.setAttribute("booleans", Seq(true, false)) + _ <- tracing.setAttribute("longs", Seq(1L, 2L)) + _ <- tracing.setAttribute("strings", Seq("foo", "bar")) + } yield ()) @@ span("foo") + spans <- getFinishedSpans + tags = spans.head.getAttributes + } yield assert(tags.get(AttributeKey.booleanKey("boolean")))(equalTo(Boolean.box(true))) && + assert(tags.get(AttributeKey.longKey("int")))(equalTo(Long.box(1))) && + assert(tags.get(AttributeKey.stringKey("string")))(equalTo("foo")) && + assert(tags.get(AttributeKey.booleanArrayKey("booleans")))( + equalTo(Seq(Boolean.box(true), Boolean.box(false)).asJava) + ) && + assert(tags.get(AttributeKey.longArrayKey("longs")))( + equalTo(Seq(Long.box(1L), Long.box(2L)).asJava) + ) && + assert(tags.get(AttributeKey.stringArrayKey("strings")))(equalTo(Seq("foo", "bar").asJava)) + } }, test("logging") { - val duration = 1000.micros + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ - val log = - ZIO.unit.addEvent("message") *> - TestClock - .adjust(duration) - .addEventWithAttributes( + val duration = 1000.micros + + val log = for { + _ <- tracing.addEvent("message") + _ <- TestClock.adjust(duration) + _ <- tracing.addEventWithAttributes( + "message2", + Attributes.of( + AttributeKey.stringKey("msg"), + "message", + AttributeKey.longKey("size"), + Long.box(1) + ) + ) + } yield () + + for { + _ <- log @@ span("foo") + _ <- ZIO.unit @@ span("Child") @@ span("Root") + spans <- getFinishedSpans + tags = spans.collect { + case span if span.getName == "foo" => + span.getEvents.asScala.toList.map(le => (le.getEpochNanos, le.getName, le.getAttributes)) + }.flatten + } yield { + val expected = List( + (0L, "message", Attributes.empty()), + ( + 1000000L, "message2", Attributes.of( AttributeKey.stringKey("msg"), @@ -272,47 +303,30 @@ object TracingTest extends ZIOSpecDefault { Long.box(1) ) ) - - for { - _ <- log.span("foo") - _ <- ZIO.unit.span("Child").span("Root") - spans <- getFinishedSpans - tags = spans.collect { - case span if span.getName == "foo" => - span.getEvents.asScala.toList.map(le => (le.getEpochNanos, le.getName, le.getAttributes)) - }.flatten - } yield { - val expected = List( - (0L, "message", Attributes.empty()), - ( - 1000000L, - "message2", - Attributes.of( - AttributeKey.stringKey("msg"), - "message", - AttributeKey.longKey("size"), - Long.box(1) - ) ) - ) - assert(tags)(equalTo(expected)) + assert(tags)(equalTo(expected)) + } } }, test("baggaging") { - for { - _ <- ZIO.unit.setBaggage("some", "thing") - baggage <- Tracing.getCurrentBaggage - entryValue = Option(baggage.getEntryValue("some")) - } yield assert(entryValue)(equalTo(Some("thing"))) + ZIO.serviceWithZIO[Tracing] { tracing => + for { + _ <- tracing.setBaggage("some", "thing") + baggage <- tracing.getCurrentBaggage + entryValue = Option(baggage.getEntryValue("some")) + } yield assert(entryValue)(equalTo(Some("thing"))) + } }, test("resources") { - for { - ref <- Ref.make(false) - scope <- Scope.make - resource = ZIO.addFinalizer(ref.set(true)) - _ <- scope.extend(resource.span("Resource")) - released <- ref.get - } yield assert(released)(isFalse) + ZIO.serviceWithZIO[Tracing] { tracing => + for { + ref <- Ref.make(false) + scope <- Scope.make + resource = ZIO.addFinalizer(ref.set(true)) + _ <- scope.extend(tracing.span("Resource")(resource)) + released <- ref.get + } yield assert(released)(isFalse) + } } ).provideLayer(tracingMockLayer) ) diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendApp.scala new file mode 100644 index 00000000..2ee51b38 --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendApp.scala @@ -0,0 +1,26 @@ +package zio.telemetry.opentracing.example + +import zio.config.magnolia._ +import zio.config.typesafe.TypesafeConfig +import zio.telemetry.opentracing.example.config.AppConfig +import zio.telemetry.opentracing.example.http.{ BackendHttpApp, BackendHttpServer } +import zio._ +import zio.telemetry.opentracing.OpenTracing + +object BackendApp extends ZIOAppDefault { + + private val configLayer = + TypesafeConfig.fromResourcePath(descriptor[AppConfig]) + + override def run: Task[ExitCode] = + ZIO + .serviceWithZIO[BackendHttpServer](_.start.exitCode) + .provide( + configLayer, + BackendHttpServer.live, + BackendHttpApp.live, + OpenTracing.live(), + JaegerTracer.live("zio-backend") + ) + +} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendServer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendServer.scala deleted file mode 100644 index 94f95ce0..00000000 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/BackendServer.scala +++ /dev/null @@ -1,34 +0,0 @@ -package zio.telemetry.opentracing.example - -import zhttp.service.server.ServerChannelFactory -import zhttp.service.{ EventLoopGroup, Server, ServerChannelFactory } -import zio.Console.printLine -import zio.config.getConfig -import zio.config.magnolia._ -import zio.config.typesafe.TypesafeConfig -import zio.telemetry.opentracing.example.JaegerTracer.makeService -import zio.telemetry.opentracing.example.config.AppConfig -import zio.telemetry.opentracing.example.http.BackendApp -import zio.{ ZIO, ZIOAppDefault } - -object BackendServer extends ZIOAppDefault { - - type AppEnv = AppConfig with EventLoopGroup with ServerChannelFactory - - val server: ZIO[AppEnv, Throwable, Unit] = - ZIO.scoped[AppEnv] { - for { - conf <- getConfig[AppConfig] - tracingService = makeService(conf.tracer.host, "zio-backend") - server = Server.port(conf.backend.port) ++ Server.app(BackendApp.status(tracingService)) - _ <- server.make - _ <- printLine(s"BackendServer started at ${conf.backend.port}") *> ZIO.never - } yield () - } - - val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) - val appLayer = ServerChannelFactory.auto ++ EventLoopGroup.auto(0) - - override def run = - ZIO.provideLayer(configLayer >+> appLayer)(server.exitCode) -} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/JaegerTracer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/JaegerTracer.scala index 999f6271..3c436e76 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/JaegerTracer.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/JaegerTracer.scala @@ -1,25 +1,36 @@ package zio.telemetry.opentracing.example import io.jaegertracing.Configuration +import io.jaegertracing.internal import io.jaegertracing.internal.samplers.ConstSampler import io.jaegertracing.zipkin.ZipkinV2Reporter +import io.opentracing.Tracer import org.apache.http.client.utils.URIBuilder -import zio.ZLayer -import zio.telemetry.opentracing.OpenTracing +import zio._ +import zio.telemetry.opentracing.example.config.AppConfig import zipkin2.reporter.AsyncReporter import zipkin2.reporter.okhttp3.OkHttpSender object JaegerTracer { - def makeService(host: String, serviceName: String): ZLayer[Any, Throwable, OpenTracing] = { - val url = new URIBuilder().setScheme("http").setHost(host).setPath("/api/v2/spans").build.toString - val senderBuilder = OkHttpSender.newBuilder.compressionEnabled(true).endpoint(url) + def live(serviceName: String): RLayer[AppConfig, Tracer] = + ZLayer { + for { + config <- ZIO.service[AppConfig] + tracer <- makeTracer(config.tracer.host, serviceName) + } yield tracer + } - val tracer = new Configuration(serviceName).getTracerBuilder - .withSampler(new ConstSampler(true)) - .withReporter(new ZipkinV2Reporter(AsyncReporter.create(senderBuilder.build))) - .build + def makeTracer(host: String, serviceName: String): Task[internal.JaegerTracer] = + for { + url <- ZIO.attempt(new URIBuilder().setScheme("http").setHost(host).setPath("/api/v2/spans").build.toString) + senderBuilder <- ZIO.attempt(OkHttpSender.newBuilder.compressionEnabled(true).endpoint(url)) + tracer <- ZIO.attempt( + new Configuration(serviceName).getTracerBuilder + .withSampler(new ConstSampler(true)) + .withReporter(new ZipkinV2Reporter(AsyncReporter.create(senderBuilder.build))) + .build + ) + } yield tracer - OpenTracing.live(tracer) - } } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala new file mode 100644 index 00000000..b9acac80 --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala @@ -0,0 +1,33 @@ +package zio.telemetry.opentracing.example + +import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend +import zio.config.magnolia._ +import zio.config.typesafe.TypesafeConfig +import zio.telemetry.opentracing.example.config.AppConfig +import zio.telemetry.opentracing.example.http.{ Client, ProxyHttpApp, ProxyHttpServer } +import zio._ +import zio.telemetry.opentracing.OpenTracing + +object ProxyApp extends ZIOAppDefault { + + private val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) + + private val httpBackendLayer: TaskLayer[Backend] = + ZLayer.scoped { + ZIO.acquireRelease(AsyncHttpClientZioBackend())(_.close().ignore) + } + + override def run: Task[ExitCode] = + ZIO + .serviceWithZIO[ProxyHttpServer](_.start.exitCode) + .provide( + configLayer, + httpBackendLayer, + Client.live, + ProxyHttpServer.live, + ProxyHttpApp.live, + OpenTracing.live(), + JaegerTracer.live("zio-proxy") + ) + +} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyServer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyServer.scala deleted file mode 100644 index 25f7424d..00000000 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyServer.scala +++ /dev/null @@ -1,39 +0,0 @@ -package zio.telemetry.opentracing.example - -import sttp.model.Uri -import zhttp.service.server.ServerChannelFactory -import zhttp.service.{ EventLoopGroup, Server, ServerChannelFactory } -import zio.Console.printLine -import zio.config.getConfig -import zio.config.magnolia._ -import zio.config.typesafe.TypesafeConfig -import zio.telemetry.opentracing.example.JaegerTracer.makeService -import zio.telemetry.opentracing.example.config.AppConfig -import zio.telemetry.opentracing.example.http.ProxyApp -import zio.{ ZIO, ZIOAppDefault } - -object ProxyServer extends ZIOAppDefault { - - private val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig]) - - type AppEnv = AppConfig with EventLoopGroup with ServerChannelFactory - - private val appEnv = - configLayer ++ ServerChannelFactory.auto ++ EventLoopGroup.auto(0) - - val server: ZIO[AppEnv, Throwable, Unit] = - ZIO.scoped[AppEnv] { - for { - conf <- getConfig[AppConfig] - backendUrl <- - ZIO.fromEither(Uri.safeApply(conf.backend.host, conf.backend.port)).mapError(new IllegalArgumentException(_)) - service = makeService(conf.tracer.host, "zio-proxy") - server = Server.port(conf.proxy.port) ++ Server.app(ProxyApp.statuses(backendUrl, service)) - _ <- server.make - _ <- printLine(s"ProxyServer started on ${conf.proxy.port}") *> ZIO.never - } yield () - } - - override def run = - ZIO.provideLayer(appEnv)(server.exitCode) -} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/AppConfig.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/AppConfig.scala index 36d18bec..9ec0b892 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/AppConfig.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/AppConfig.scala @@ -1,3 +1,3 @@ package zio.telemetry.opentracing.example.config -final case class AppConfig(proxy: ProxyConfig, backend: BackendConfig, tracer: TracerHost) +final case class AppConfig(proxy: ProxyConfig, backend: BackendConfig, tracer: TracerConfig) diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/BackendUrl.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/BackendUrl.scala deleted file mode 100644 index 868a5b4e..00000000 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/BackendUrl.scala +++ /dev/null @@ -1,3 +0,0 @@ -package zio.telemetry.opentracing.example.config - -final case class BackendUrl(url: String) extends AnyVal diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerConfig.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerConfig.scala new file mode 100644 index 00000000..f96d01af --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerConfig.scala @@ -0,0 +1,3 @@ +package zio.telemetry.opentracing.example.config + +final case class TracerConfig(host: String) diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerHost.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerHost.scala deleted file mode 100644 index 5b1d7e95..00000000 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/config/TracerHost.scala +++ /dev/null @@ -1,3 +0,0 @@ -package zio.telemetry.opentracing.example.config - -final case class TracerHost(host: String) extends AnyVal diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala similarity index 63% rename from opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendApp.scala rename to opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala index ad669095..a90d8be1 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendApp.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala @@ -6,17 +6,27 @@ import zhttp.http.{ !!, ->, /, Http, HttpApp, Method, Response } import zio.json.EncoderOps import zio.telemetry.opentracing._ import zio.telemetry.opentracing.example.http.{ Status => ServiceStatus } -import zio.{ ZIO, ZLayer } +import zio._ import scala.jdk.CollectionConverters._ -object BackendApp { - def status(service: ZLayer[Any, Throwable, OpenTracing]): HttpApp[Any, Throwable] = +case class BackendHttpApp(tracing: OpenTracing) { + + import tracing.aspects._ + + def routes: HttpApp[Any, Throwable] = Http.collectZIO { case request @ Method.GET -> !! / "status" => val headers = request.headers.toList.toMap - ZIO.unit - .spanFrom(HttpHeadersFormat, new TextMapAdapter(headers.asJava), "/status") + + (ZIO.unit @@ spanFrom(HttpHeadersFormat, new TextMapAdapter(headers.asJava), "/status")) .as(Response.json(ServiceStatus.up("backend").toJson)) - .provideSomeLayer(service) } + +} + +object BackendHttpApp { + + val live: URLayer[OpenTracing, BackendHttpApp] = + ZLayer.fromFunction(BackendHttpApp.apply _) + } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala new file mode 100644 index 00000000..55cd5910 --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala @@ -0,0 +1,24 @@ +package zio.telemetry.opentracing.example.http + +import zhttp.service.Server +import zio.Console.printLine +import zio._ +import zio.telemetry.opentracing.example.config.AppConfig + +case class BackendHttpServer(config: AppConfig, httpApp: BackendHttpApp) { + + def start: ZIO[Any, Throwable, Nothing] = + for { + _ <- Server.start(config.backend.port, httpApp.routes) + _ <- printLine(s"BackendHttpServer started on port ${config.backend.port}") + never <- ZIO.never + } yield never + +} + +object BackendHttpServer { + + val live: URLayer[AppConfig with BackendHttpApp, BackendHttpServer] = + ZLayer.fromFunction(BackendHttpServer.apply _) + +} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala index fc05be42..e5b1c215 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala @@ -1,19 +1,41 @@ package zio.telemetry.opentracing.example.http import sttp.client3._ -import sttp.client3.asynchttpclient.zio._ import sttp.client3.ziojson._ import sttp.model.Uri -import zio.Task +import zio._ +import zio.telemetry.opentracing.example.Backend +import zio.telemetry.opentracing.example.config.AppConfig -object Client { - private val backend = AsyncHttpClientZioBackend() +case class Client(backend: Backend, config: AppConfig) { + + private val backendUrl = + Uri + .safeApply(config.backend.host, config.backend.port) + .map(_.withPath("status")) + .left + .map(new IllegalArgumentException(_)) def status( - uri: Uri, headers: Map[String, String] - ): Task[Response[Either[ResponseException[String, String], Status]]] = - backend.flatMap { backend => - basicRequest.get(uri).headers(headers).response(asJson[Status]).send(backend) - } + ): Task[Statuses] = + for { + url <- ZIO.fromEither(backendUrl) + response <- backend + .send( + basicRequest + .get(url.withPath("status")) + .headers(headers) + .response(asJson[Status]) + ) + status = response.body.getOrElse(Status.down("backend")) + } yield Statuses(List(status, Status.up("proxy"))) + +} + +object Client { + + val live: RLayer[AppConfig with Backend, Client] = + ZLayer.fromFunction(Client.apply _) + } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyApp.scala deleted file mode 100644 index a34e18d8..00000000 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyApp.scala +++ /dev/null @@ -1,52 +0,0 @@ -package zio.telemetry.opentracing.example.http - -import io.opentracing.propagation.Format.Builtin.{ HTTP_HEADERS => HttpHeadersFormat } -import io.opentracing.propagation.TextMapAdapter -import io.opentracing.tag.Tags -import sttp.model.Method.GET -import sttp.model.Uri -import zhttp.http.{ !!, ->, /, Http, HttpApp, Method, Response } -import zio.json.EncoderOps -import zio.telemetry.opentracing.OpenTracing -import zio.{ UIO, ZIO, ZLayer } - -import scala.collection.mutable -import scala.jdk.CollectionConverters._ - -object ProxyApp { - def statuses(backendUri: Uri, service: ZLayer[Any, Throwable, OpenTracing]): HttpApp[Any, Throwable] = - Http.collectZIO { case Method.GET -> !! / "statuses" => - val zio = - for { - _ <- OpenTracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT) - _ <- OpenTracing.tag(Tags.HTTP_METHOD.getKey, GET.method) - _ <- OpenTracing.setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value") - buffer <- ZIO.succeed(new TextMapAdapter(mutable.Map.empty[String, String].asJava)) - _ <- OpenTracing.inject(HttpHeadersFormat, buffer) - headers <- extractHeaders(buffer) - up = Status.up("proxy") - res <- Client - .status(backendUri.withPath("status"), headers) - .map { res => - val status = res.body.getOrElse(Status.down("backend")) - val statuses = Statuses(List(status, up)) - Response.json(statuses.toJson) - } - } yield res - - zio - .root("/statuses") - .provideSomeLayer(service) - } - - private def extractHeaders(adapter: TextMapAdapter): UIO[Map[String, String]] = { - val m = mutable.Map.empty[String, String] - ZIO - .succeed(adapter.forEach { entry => - m.put(entry.getKey, entry.getValue) - () - }) - .as(m.toMap) - } - -} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala new file mode 100644 index 00000000..3b80e397 --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala @@ -0,0 +1,50 @@ +package zio.telemetry.opentracing.example.http + +import io.opentracing.propagation.Format.Builtin.{ HTTP_HEADERS => HttpHeadersFormat } +import io.opentracing.propagation.TextMapAdapter +import io.opentracing.tag.Tags +import sttp.model.Method.GET +import zhttp.http.{ !!, ->, /, Http, HttpApp, Method, Response } +import zio.json.EncoderOps +import zio.telemetry.opentracing.OpenTracing +import zio._ + +import scala.collection.mutable +import scala.jdk.CollectionConverters._ + +case class ProxyHttpApp(client: Client, tracing: OpenTracing) { + + import tracing.aspects._ + + def routes: HttpApp[Any, Throwable] = + Http.collectZIO { case Method.GET -> !! / "statuses" => + (for { + _ <- ZIO.unit @@ tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT) + _ <- ZIO.unit @@ tag(Tags.HTTP_METHOD.getKey, GET.method) + _ <- ZIO.unit @@ setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value") + carrier = new TextMapAdapter(mutable.Map.empty[String, String].asJava) + _ <- tracing.inject(HttpHeadersFormat, carrier) + headers <- extractHeaders(carrier) + statuses <- client.status(headers) + } yield Response.json(statuses.toJson)) @@ root("/statuses") + } + + private def extractHeaders(adapter: TextMapAdapter): UIO[Map[String, String]] = { + val m = mutable.Map.empty[String, String] + + ZIO.succeed { + adapter.forEach { entry => + m.put(entry.getKey, entry.getValue) + () + } + }.as(m.toMap) + } + +} + +object ProxyHttpApp { + + val live: URLayer[Client with OpenTracing, ProxyHttpApp] = + ZLayer.fromFunction(ProxyHttpApp.apply _) + +} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala new file mode 100644 index 00000000..cc1d318f --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala @@ -0,0 +1,24 @@ +package zio.telemetry.opentracing.example.http + +import zhttp.service.Server +import zio.Console.printLine +import zio.telemetry.opentracing.example.config.AppConfig +import zio._ + +case class ProxyHttpServer(config: AppConfig, httpApp: ProxyHttpApp) { + + def start: ZIO[Any, Throwable, Nothing] = + for { + _ <- Server.start(config.proxy.port, httpApp.routes) + _ <- printLine(s"ProxyHttpServer started on port ${config.proxy.port}") + never <- ZIO.never + } yield never + +} + +object ProxyHttpServer { + + val live: URLayer[AppConfig with ProxyHttpApp, ProxyHttpServer] = + ZLayer.fromFunction(ProxyHttpServer.apply _) + +} diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/package.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/package.scala new file mode 100644 index 00000000..42678105 --- /dev/null +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/package.scala @@ -0,0 +1,12 @@ +package zio.telemetry.opentracing + +import sttp.capabilities.WebSockets +import sttp.capabilities.zio.ZioStreams +import sttp.client3.SttpBackend +import zio.Task + +package object example { + + type Backend = SttpBackend[Task, ZioStreams with WebSockets] + +} diff --git a/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala b/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala index 27929ac0..ba8d20d5 100644 --- a/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala +++ b/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala @@ -9,172 +9,263 @@ import zio._ import scala.jdk.CollectionConverters._ -trait OpenTracing { - private[opentracing] val tracer: Tracer - - def currentSpan: FiberRef[Span] - def error(span: Span, cause: Cause[_], tagError: Boolean, logError: Boolean): UIO[Unit] - def finish(span: Span): UIO[Unit] - def log[R, E, A](zio: ZIO[R, E, A], fields: Map[String, _]): ZIO[R, E, A] - def log[R, E, A](zio: ZIO[R, E, A], msg: String): ZIO[R, E, A] - def root[R, E, A](zio: ZIO[R, E, A], operation: String, tagError: Boolean, logError: Boolean): ZIO[R, E, A] - def setBaggageItem[R, E, A](zio: ZIO[R, E, A], key: String, value: String): ZIO[R, E, A] - def span[R, E, A](zio: ZIO[R, E, A], operation: String, tagError: Boolean, logError: Boolean): ZIO[R, E, A] - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: String): ZIO[R, E, A] - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: Int): ZIO[R, E, A] - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: Boolean): ZIO[R, E, A] -} +trait OpenTracing { self => -object OpenTracing { - lazy val noop: ULayer[OpenTracing] = live(NoopTracerFactory.create()) - - def live(tracer: Tracer, rootOperation: String = "ROOT"): ULayer[OpenTracing] = - ZLayer.scoped(scoped(tracer, rootOperation)) - - def scoped(tracer0: Tracer, rootOperation: String): URIO[Scope, OpenTracing] = - ZIO.acquireRelease( - for { - span <- ZIO.succeed(tracer0.buildSpan(rootOperation).start()) - ref <- FiberRef.make(span) - micros = Clock.currentTime(TimeUnit.MICROSECONDS) - } yield new OpenTracing { self => - val tracer: Tracer = tracer0 - - val currentSpan: FiberRef[Span] = ref - - def error(span: Span, cause: Cause[_], tagError: Boolean, logError: Boolean): UIO[Unit] = - for { - _ <- ZIO.succeed(span.setTag("error", true)).when(tagError) - _ <- ZIO.succeed(span.log(Map("error.object" -> cause, "stack" -> cause.prettyPrint).asJava)).when(logError) - } yield () - - def finish(span: Span): UIO[Unit] = micros.map(span.finish) - - def log[R, E, A](zio: ZIO[R, E, A], fields: Map[String, _]): ZIO[R, E, A] = - zio <* currentSpan.get.zipWith(micros)((span, now) => span.log(now, fields.asJava)) - - def log[R, E, A](zio: ZIO[R, E, A], msg: String): ZIO[R, E, A] = - zio <* currentSpan.get.zipWith(micros)((span, now) => span.log(now, msg)) - - def root[R, E, A](zio: ZIO[R, E, A], operation: String, tagError: Boolean, logError: Boolean): ZIO[R, E, A] = - for { - root <- ZIO.succeed(tracer.buildSpan(operation).start()) - current <- currentSpan.get - _ <- currentSpan.set(root) - res <- zio - .catchAllCause(c => error(root, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) - .ensuring(finish(root) *> currentSpan.set(current)) - } yield res - - def setBaggageItem[R, E, A](zio: ZIO[R, E, A], key: String, value: String): ZIO[R, E, A] = - zio <* currentSpan.get.map(_.setBaggageItem(key, value)) - - def span[R, E, A](zio: ZIO[R, E, A], operation: String, tagError: Boolean, logError: Boolean): ZIO[R, E, A] = - for { - current <- currentSpan.get - child <- ZIO.succeed(tracer.buildSpan(operation).asChildOf(current).start()) - _ <- currentSpan.set(child) - res <- zio - .catchAllCause(c => error(child, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) - .ensuring(finish(child) *> currentSpan.set(current)) - } yield res - - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: String): ZIO[R, E, A] = - zio <* currentSpan.get.map(_.setTag(key, value)) - - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: Int): ZIO[R, E, A] = - zio <* currentSpan.get.map(_.setTag(key, value)) - - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: Boolean): ZIO[R, E, A] = - zio <* currentSpan.get.map(_.setTag(key, value)) - } - )(_.currentSpan.get.flatMap(span => ZIO.succeed(span.finish()))) + def getCurrentSpan(implicit trace: Trace): UIO[Span] + + def getCurrentSpanContext(implicit trace: Trace): UIO[SpanContext] + + def error( + span: Span, + cause: Cause[_], + tagError: Boolean = true, + logError: Boolean = true + )(implicit trace: Trace): UIO[Unit] + + def finish(span: Span)(implicit trace: Trace): UIO[Unit] + + def log[R, E, A](fields: Map[String, _])(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] - def spanFrom[R, R1 <: R with OpenTracing, E, Span, C <: AnyRef]( + def log[R, E, A](msg: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + + def root[R, E, A]( + operation: String, + tagError: Boolean = true, + logError: Boolean = true + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + + def span[R, E, A]( + operation: String, + tagError: Boolean = true, + logError: Boolean = true + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + + def spanFrom[R, E, Span, C]( format: Format[C], carrier: C, - zio: ZIO[R, E, Span], operation: String, tagError: Boolean = true, logError: Boolean = true - ): ZIO[R1, E, Span] = - ZIO.serviceWithZIO[OpenTracing] { service => - ZIO - .attempt(service.tracer.extract(format, carrier)) - .foldZIO( - _ => zio, - spanCtx => - for { - current <- service.currentSpan.get - span <- ZIO.succeed(service.tracer.buildSpan(operation).asChildOf(spanCtx).start()) - _ <- service.currentSpan.set(span) - res <- zio - .catchAllCause(c => service.error(span, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) - .ensuring(service.finish(span) *> service.currentSpan.set(current)) - } yield res - ) - } + )(effect: => ZIO[R, E, Span])(implicit trace: Trace): ZIO[R, E, Span] - def context: URIO[OpenTracing, SpanContext] = - ZIO.serviceWithZIO(_.currentSpan.get.map(_.context)) + def tag[R, E, A](key: String, value: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] - def getBaggageItem(key: String): URIO[OpenTracing, Option[String]] = - for { - service <- ZIO.service[OpenTracing] - span <- service.currentSpan.get - res <- ZIO.succeed(span.getBaggageItem(key)).map(Option(_)) - } yield res + def tag[R, E, A](key: String, value: Int)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] - def inject[C <: AnyRef](format: Format[C], carrier: C): URIO[OpenTracing, Unit] = - for { - service <- ZIO.service[OpenTracing] - span <- service.currentSpan.get - _ <- ZIO.succeed(service.tracer.inject(span.context(), format, carrier)) - } yield () + def tag[R, E, A](key: String, value: Boolean)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] - def log(msg: String): URIO[OpenTracing, Unit] = log(ZIO.unit, msg) + def inject[C](format: Format[C], carrier: C)(implicit trace: Trace): UIO[Unit] - def log(fields: Map[String, _]): URIO[OpenTracing, Unit] = log(ZIO.unit, fields) + def setBaggageItem[R, E, A](key: String, value: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] - def log[R, E, A](zio: ZIO[R, E, A], fields: Map[String, _]): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.log(zio, fields)) + def getBaggageItem(key: String)(implicit trace: Trace): UIO[Option[String]] - def log[R, E, A](zio: ZIO[R, E, A], msg: String): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.log(zio, msg)) + object aspects { - def root[R, E, A]( - zio: ZIO[R, E, A], - operation: String, - tagError: Boolean = false, - logError: Boolean = false - ): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.root(zio, operation, tagError, logError)) + def root( + operation: String, + tagError: Boolean = true, + logError: Boolean = true + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.root(operation, tagError, logError)(zio) + } - def setBaggageItem[R, E, A](zio: ZIO[R, E, A], key: String, value: String): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.setBaggageItem(zio, key, value)) + def span( + operation: String, + tagError: Boolean = true, + logError: Boolean = true + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.span(operation, tagError, logError)(zio) + } - def setBaggageItem(key: String, value: String): URIO[OpenTracing, Unit] = setBaggageItem(ZIO.unit, key, value) + def spanFrom[C]( + format: Format[C], + carrier: C, + operation: String, + tagError: Boolean = true, + logError: Boolean = true + ): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.spanFrom(format, carrier, operation, tagError, logError)(zio) + } - def span[R, E, A]( - zio: ZIO[R, E, A], - operation: String, - tagError: Boolean = false, - logError: Boolean = false - ): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.span(zio, operation, tagError, logError)) + def setBaggageItem(key: String, value: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.setBaggageItem(key, value)(zio) + } - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: String): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.tag(zio, key, value)) + def tag(key: String, value: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.tag(key, value)(zio) + } + + def tag(key: String, value: Int): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.tag(key, value)(zio) + } - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: Int): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.tag(zio, key, value)) + def tag(key: String, value: Boolean): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.tag(key, value)(zio) + } + + def log(fields: Map[String, _]): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.log(fields)(zio) + } - def tag[R, E, A](zio: ZIO[R, E, A], key: String, value: Boolean): ZIO[R with OpenTracing, E, A] = - ZIO.serviceWithZIO[OpenTracing](_.tag(zio, key, value)) + def log(msg: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.log(msg)(zio) + } + + def inject[C](format: Format[C], carrier: C): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] = + new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] { + override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + self.inject(format, carrier) *> zio + } + + } + +} + +object OpenTracing { + + lazy val noop: ULayer[OpenTracing] = + ZLayer.succeed(NoopTracerFactory.create()) >>> live() + + def live(rootOperation: String = "ROOT"): URLayer[Tracer, OpenTracing] = + ZLayer.scoped(ZIO.service[Tracer].flatMap(scoped(_, rootOperation))) + + def scoped(tracer: Tracer, rootOperation: String): URIO[Scope, OpenTracing] = { + val acquire: URIO[Scope, OpenTracing] = for { + span <- ZIO.succeed(tracer.buildSpan(rootOperation).start()) + currentSpan <- FiberRef.make(span) + currentMicros = Clock.currentTime(TimeUnit.MICROSECONDS) + } yield new OpenTracing { self => + override def getCurrentSpan(implicit trace: Trace): UIO[Span] = + currentSpan.get + + override def getCurrentSpanContext(implicit trace: Trace): UIO[SpanContext] = + getCurrentSpan.map(_.context) + + override def error( + span: Span, + cause: Cause[_], + tagError: Boolean = true, + logError: Boolean = true + )(implicit trace: Trace): UIO[Unit] = + for { + _ <- ZIO.succeed(span.setTag("error", true)).when(tagError) + _ <- ZIO.succeed(span.log(Map("error.object" -> cause, "stack" -> cause.prettyPrint).asJava)).when(logError) + } yield () + + override def finish(span: Span)(implicit trace: Trace): UIO[Unit] = + currentMicros.flatMap(micros => ZIO.succeed(span.finish(micros))) + + override def log[R, E, A](fields: Map[String, _])(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + effect <* getCurrentSpan.zipWith(currentMicros)((span, now) => span.log(now, fields.asJava)) + + override def log[R, E, A](msg: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + effect <* getCurrentSpan.zipWith(currentMicros)((span, now) => span.log(now, msg)) + + override def root[R, E, A]( + operation: String, + tagError: Boolean = true, + logError: Boolean = true + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + for { + root <- ZIO.succeed(tracer.buildSpan(operation).start()) + current <- getCurrentSpan + _ <- currentSpan.set(root) + res <- effect + .catchAllCause(c => error(root, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) + .ensuring(finish(root) *> currentSpan.set(current)) + } yield res + + override def span[R, E, A]( + operation: String, + tagError: Boolean = true, + logError: Boolean = true + )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + for { + current <- getCurrentSpan + child <- ZIO.succeed(tracer.buildSpan(operation).asChildOf(current).start()) + _ <- currentSpan.set(child) + res <- effect + .catchAllCause(c => error(child, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) + .ensuring(finish(child) *> currentSpan.set(current)) + } yield res + + override def spanFrom[R, E, Span, C]( + format: Format[C], + carrier: C, + operation: String, + tagError: Boolean = true, + logError: Boolean = true + )(effect: => ZIO[R, E, Span])(implicit trace: Trace): ZIO[R, E, Span] = + ZIO + .attempt(tracer.extract(format, carrier)) + .foldZIO( + _ => effect, + spanCtx => + for { + current <- getCurrentSpan + span <- ZIO.succeed(tracer.buildSpan(operation).asChildOf(spanCtx).start()) + _ <- currentSpan.set(span) + res <- effect + .catchAllCause(c => error(span, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) + .ensuring(finish(span) *> currentSpan.set(current)) + } yield res + ) + + override def tag[R, E, A](key: String, value: String)(effect: => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R, E, A] = + effect <* getCurrentSpan.map(_.setTag(key, value)) + + override def tag[R, E, A](key: String, value: Int)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + effect <* getCurrentSpan.map(_.setTag(key, value)) + + override def tag[R, E, A](key: String, value: Boolean)(effect: => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R, E, A] = + effect <* getCurrentSpan.map(_.setTag(key, value)) + + override def inject[C](format: Format[C], carrier: C)(implicit trace: Trace): UIO[Unit] = + for { + span <- getCurrentSpan + _ <- ZIO.succeed(tracer.inject(span.context(), format, carrier)) + } yield () + + override def setBaggageItem[R, E, A](key: String, value: String)(effect: => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R, E, A] = + effect <* getCurrentSpan.map(_.setBaggageItem(key, value)) + + override def getBaggageItem(key: String)(implicit trace: Trace): UIO[Option[String]] = + for { + span <- getCurrentSpan + res <- ZIO.succeed(span.getBaggageItem(key)).map(Option(_)) + } yield res + + } - def tag(key: String, value: String): URIO[OpenTracing, Unit] = tag(ZIO.unit, key, value) + def release(tracing: OpenTracing) = + tracing.getCurrentSpan.flatMap(span => ZIO.succeed(span.finish())) - def tag(key: String, value: Int): URIO[OpenTracing, Unit] = tag(ZIO.unit, key, value) + ZIO.acquireRelease(acquire)(release) + } - def tag(key: String, value: Boolean): URIO[OpenTracing, Unit] = tag(ZIO.unit, key, value) } diff --git a/opentracing/src/main/scala/zio/telemetry/opentracing/package.scala b/opentracing/src/main/scala/zio/telemetry/opentracing/package.scala deleted file mode 100644 index 010347a5..00000000 --- a/opentracing/src/main/scala/zio/telemetry/opentracing/package.scala +++ /dev/null @@ -1,51 +0,0 @@ -package zio.telemetry - -import io.opentracing.propagation.Format -import zio.ZIO - -package object opentracing { - implicit final class OpenTracingZioOps[-R, +E, +A](val zio: ZIO[R, E, A]) extends AnyVal { - - def root( - operation: String, - tagError: Boolean = true, - logError: Boolean = true - ): ZIO[R with OpenTracing, E, A] = - OpenTracing.root(zio, operation, tagError, logError) - - def span( - operation: String, - tagError: Boolean = true, - logError: Boolean = true - ): ZIO[R with OpenTracing, E, A] = - OpenTracing.span(zio, operation, tagError, logError) - - def spanFrom[R1 <: R with OpenTracing, C <: Object]( - format: Format[C], - carrier: C, - operation: String, - tagError: Boolean = true, - logError: Boolean = true - ): ZIO[R1, E, A] = - OpenTracing.spanFrom(format, carrier, zio, operation, tagError, logError) - - def setBaggageItem(key: String, value: String): ZIO[R with OpenTracing, E, A] = - OpenTracing.setBaggageItem(zio, key, value) - - def tag(key: String, value: String): ZIO[R with OpenTracing, E, A] = - OpenTracing.tag(zio, key, value) - - def tag(key: String, value: Int): ZIO[R with OpenTracing, E, A] = - OpenTracing.tag(zio, key, value) - - def tag(key: String, value: Boolean): ZIO[R with OpenTracing, E, A] = - OpenTracing.tag(zio, key, value) - - def log(msg: String): ZIO[R with OpenTracing, E, A] = - OpenTracing.log(zio, msg) - - def log(fields: Map[String, _]): ZIO[R with OpenTracing, E, A] = - OpenTracing.log(zio, fields) - - } -} diff --git a/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala b/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala index 8ff03d59..895943ad 100644 --- a/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala +++ b/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala @@ -15,12 +15,12 @@ import java.nio.ByteBuffer object OpenTracingTest extends ZIOSpecDefault { val mockTracer: Layer[Nothing, MockTracer] = - ZLayer.fromZIO(ZIO.succeed(new MockTracer)) + ZLayer(ZIO.succeed(new MockTracer)) val testService: URLayer[MockTracer, OpenTracing] = ZLayer.scoped(ZIO.service[MockTracer].flatMap(OpenTracing.scoped(_, "ROOT"))) - val customLayer = mockTracer ++ (mockTracer >>> testService) + val customLayer: ULayer[MockTracer with OpenTracing] = mockTracer ++ (mockTracer >>> testService) def spec = suite("zio opentracing")( @@ -28,11 +28,7 @@ object OpenTracingTest extends ZIOSpecDefault { val tracer = new MockTracer ZIO - .scoped( - OpenTracing - .live(tracer, "ROOT") - .build - ) + .scoped(OpenTracing.scoped(tracer, "ROOT")) .as( assert(tracer.finishedSpans.asScala)(hasSize(equalTo(1))) && assert(tracer.finishedSpans().get(0))( hasField[MockSpan, String]( @@ -50,151 +46,180 @@ object OpenTracingTest extends ZIOSpecDefault { }, suite("spans")( test("childSpan") { - for { - tracer <- ZIO.service[MockTracer] - _ <- ZIO.unit.span("Child").span("ROOT") - } yield { - val spans = tracer.finishedSpans.asScala - val root = spans.find(_.operationName() == "ROOT") - val child = spans.find(_.operationName() == "Child") - assert(root)(isSome(anything)) && - assert(child)( - isSome( - hasField[MockSpan, Long]( - "parent", - _.parentId, - equalTo(root.get.context().spanId()) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + for { + tracer <- ZIO.service[MockTracer] + _ <- ZIO.unit @@ span("Child") @@ span("ROOT") + } yield { + val spans = tracer.finishedSpans.asScala + val root = spans.find(_.operationName() == "ROOT") + val child = spans.find(_.operationName() == "Child") + assert(root)(isSome(anything)) && + assert(child)( + isSome( + hasField[MockSpan, Long]( + "parent", + _.parentId, + equalTo(root.get.context().spanId()) + ) ) ) - ) + } } }, test("rootSpan") { - for { - tracer <- ZIO.service[MockTracer] - _ <- ZIO.unit.root("ROOT2").root("ROOT") - } yield { - val spans = tracer.finishedSpans.asScala - val root = spans.find(_.operationName() == "ROOT") - val child = spans.find(_.operationName() == "ROOT2") - assert(root)(isSome(anything)) && - assert(child)( - isSome( - hasField[MockSpan, Long]( - "parent", - _.parentId, - equalTo(0L) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + for { + tracer <- ZIO.service[MockTracer] + _ <- ZIO.unit @@ root("ROOT2") @@ root("ROOT") + } yield { + val spans = tracer.finishedSpans.asScala + val root = spans.find(_.operationName() == "ROOT") + val child = spans.find(_.operationName() == "ROOT2") + assert(root)(isSome(anything)) && + assert(child)( + isSome( + hasField[MockSpan, Long]( + "parent", + _.parentId, + equalTo(0L) + ) ) ) - ) + } } }, test("spanFrom behaves like root if extract returns null") { - val tm = new TextMapAdapter(mutable.Map.empty.asJava) - for { - tracer <- ZIO.service[MockTracer] - _ <- ZIO.unit.spanFrom(Format.Builtin.TEXT_MAP, tm, "spanFrom") - } yield { - val spans = tracer.finishedSpans.asScala - val spanFrom = spans.find(_.operationName() == "spanFrom") - assert(spanFrom)( - isSome( - hasField[MockSpan, Long]( - "parent", - _.parentId, - equalTo(0L) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + val tm = new TextMapAdapter(mutable.Map.empty[String, String].asJava) + + for { + tracer <- ZIO.service[MockTracer] + _ <- ZIO.unit @@ spanFrom(Format.Builtin.TEXT_MAP, tm, "spanFrom") + } yield { + val spans = tracer.finishedSpans.asScala + val spanFrom = spans.find(_.operationName() == "spanFrom") + assert(spanFrom)( + isSome( + hasField[MockSpan, Long]( + "parent", + _.parentId, + equalTo(0L) + ) ) ) - ) + } } }, test("spanFrom is a no-op if extract throws") { - val byteBuffer = ByteBuffer.wrap("corrupted binary".toCharArray.map(x => x.toByte)) - val tm = BinaryAdapters.extractionCarrier(byteBuffer) - for { - tracer <- ZIO.service[MockTracer] - _ <- ZIO.unit.spanFrom(Format.Builtin.BINARY_EXTRACT, tm, "spanFrom") - } yield { - val spans = tracer.finishedSpans.asScala - val spanFrom = spans.find(_.operationName() == "spanFrom") - assert(spanFrom)(isNone) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + val byteBuffer = ByteBuffer.wrap("corrupted binary".toCharArray.map(x => x.toByte)) + val tm = BinaryAdapters.extractionCarrier(byteBuffer) + + for { + tracer <- ZIO.service[MockTracer] + _ <- ZIO.unit @@ spanFrom(Format.Builtin.BINARY_EXTRACT, tm, "spanFrom") + } yield { + val spans = tracer.finishedSpans.asScala + val spanFrom = spans.find(_.operationName() == "spanFrom") + assert(spanFrom)(isNone) + } } }, test("inject - extract roundtrip") { - val tm = new TextMapAdapter(mutable.Map.empty.asJava) - val injectExtract = OpenTracing.inject(Format.Builtin.TEXT_MAP, tm).span("foo") *> - OpenTracing - .spanFrom(Format.Builtin.TEXT_MAP, tm, ZIO.unit, "baz") - .span("bar") - for { - tracer <- ZIO.service[MockTracer] - _ <- injectExtract.span("ROOT") - } yield { - val spans = tracer.finishedSpans().asScala - val root = spans.find(_.operationName() == "ROOT") - val foo = spans.find(_.operationName() == "foo") - val bar = spans.find(_.operationName() == "bar") - val baz = spans.find(_.operationName() == "baz") - assert(root)(isSome(anything)) && - assert(foo)(isSome(anything)) && - assert(bar)(isSome(anything)) && - assert(baz)(isSome(anything)) && - assert(foo.get.parentId())(equalTo(root.get.context().spanId())) && - assert(bar.get.parentId())(equalTo(root.get.context().spanId())) && - assert(baz.get.parentId())(equalTo(foo.get.context().spanId())) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + val tm = new TextMapAdapter(mutable.Map.empty[String, String].asJava) + + for { + tracer <- ZIO.service[MockTracer] + injectExtract = tracing.inject(Format.Builtin.TEXT_MAP, tm) @@ span("foo") *> + ZIO.unit @@ spanFrom(Format.Builtin.TEXT_MAP, tm, "baz") @@ span("bar") + _ <- injectExtract @@ span("ROOT") + } yield { + val spans = tracer.finishedSpans().asScala + val root = spans.find(_.operationName() == "ROOT") + val foo = spans.find(_.operationName() == "foo") + val bar = spans.find(_.operationName() == "bar") + val baz = spans.find(_.operationName() == "baz") + assert(root)(isSome(anything)) && + assert(foo)(isSome(anything)) && + assert(bar)(isSome(anything)) && + assert(baz)(isSome(anything)) && + assert(foo.get.parentId())(equalTo(root.get.context().spanId())) && + assert(bar.get.parentId())(equalTo(root.get.context().spanId())) && + assert(baz.get.parentId())(equalTo(foo.get.context().spanId())) + } } }, test("tagging") { - for { - tracer <- ZIO.service[MockTracer] - _ <- ZIO.unit - .tag("boolean", true) - .tag("int", 1) - .tag("string", "foo") - .span("foo") - } yield { - val tags = tracer.finishedSpans().asScala.head.tags.asScala.toMap - val expected = Map[String, Any]("boolean" -> true, "int" -> 1, "string" -> "foo") - assert(tags)(equalTo(expected)) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + for { + tracer <- ZIO.service[MockTracer] + _ <- ZIO.unit @@ tag("boolean", true) @@ tag("int", 1) @@ tag("string", "foo") @@ span("foo") + } yield { + val tags = tracer.finishedSpans().asScala.head.tags.asScala.toMap + val expected = Map[String, Any]("boolean" -> true, "int" -> 1, "string" -> "foo") + + assert(tags)(equalTo(expected)) + } } }, test("logging") { - val duration = 1000.micros - - val log = - ZIO.unit.log("message") *> - TestClock.adjust(duration).log(Map("msg" -> "message", "size" -> 1)) - - for { - tracer <- ZIO.service[MockTracer] - _ <- log.span("foo") - } yield { - val tags = - tracer - .finishedSpans() - .asScala - .collect { - case span if span.operationName == "foo" => - span.logEntries().asScala.map(le => le.timestampMicros -> le.fields.asScala.toMap) - } - .flatten - .toList - - val expected = List( - 0L -> Map("event" -> "message"), - 1000L -> Map[String, Any]("msg" -> "message", "size" -> 1) - ) - assert(tags)(equalTo(expected)) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + val duration = 1000.micros + + for { + tracer <- ZIO.service[MockTracer] + logging = ZIO.unit @@ log("message") *> + TestClock.adjust(duration) @@ log(Map("msg" -> "message", "size" -> 1)) + _ <- tracing.span("foo")(logging) + } yield { + val tags = + tracer + .finishedSpans() + .asScala + .collect { + case span if span.operationName == "foo" => + span.logEntries().asScala.map(le => le.timestampMicros -> le.fields.asScala.toMap) + } + .flatten + .toList + + val expected = List( + 0L -> Map("event" -> "message"), + 1000L -> Map[String, Any]("msg" -> "message", "size" -> 1) + ) + + assert(tags)(equalTo(expected)) + } } }, test("baggage") { - for { - _ <- OpenTracing.setBaggageItem("foo", "bar") - _ <- OpenTracing.setBaggageItem("bar", "baz") - fooBag <- OpenTracing.getBaggageItem("foo") - barBag <- OpenTracing.getBaggageItem("bar") - } yield assert(fooBag)(isSome(equalTo("bar"))) && - assert(barBag)(isSome(equalTo("baz"))) + ZIO.serviceWithZIO[OpenTracing] { tracing => + import tracing.aspects._ + + for { + _ <- ZIO.unit @@ setBaggageItem("foo", "bar") + _ <- ZIO.unit @@ setBaggageItem("bar", "baz") + fooBag <- tracing.getBaggageItem("foo") + barBag <- tracing.getBaggageItem("bar") + } yield assert(fooBag)(isSome(equalTo("bar"))) && + assert(barBag)(isSome(equalTo("baz"))) + } } ).provideLayer(customLayer) ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a9727adb..4eff1f48 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,6 +1,7 @@ import sbt._ object Dependencies { + object Versions { val opentracing = "0.33.0" val opentelemetry = "1.18.0" @@ -9,6 +10,20 @@ object Dependencies { val zio = "2.0.2" } + object Orgs { + val zio = "dev.zio" + val opentracing = "io.opentracing" + val opentelemetry = "io.opentelemetry" + val opencensus = "io.opencensus" + val jaegertracing = "io.jaegertracing" + val scalaLangModules = "org.scala-lang.modules" + val typelevel = "org.typelevel" + val d11 = "io.d11" + val softwaremillSttpClient3 = "com.softwaremill.sttp.client3" + val slf4j = "org.slf4j" + val grpc = "io.grpc" + } + private object ExampleVersions { val cats = "2.7.0" val grpcNetty = "1.47.0" @@ -22,45 +37,45 @@ object Dependencies { } lazy val zio = Seq( - "dev.zio" %% "zio" % Versions.zio, - "dev.zio" %% "zio-test" % Versions.zio % Test, - "dev.zio" %% "zio-test-sbt" % Versions.zio % Test + Orgs.zio %% "zio" % Versions.zio, + Orgs.zio %% "zio-test" % Versions.zio % Test, + Orgs.zio %% "zio-test-sbt" % Versions.zio % Test ) lazy val opentracing = zio ++ Seq( - "io.opentracing" % "opentracing-api" % Versions.opentracing, - "io.opentracing" % "opentracing-noop" % Versions.opentracing, - "org.scala-lang.modules" %% "scala-collection-compat" % Versions.scalaCollectionCompat, - "io.opentracing" % "opentracing-mock" % Versions.opentracing % Test + Orgs.opentracing % "opentracing-api" % Versions.opentracing, + Orgs.opentracing % "opentracing-noop" % Versions.opentracing, + Orgs.scalaLangModules %% "scala-collection-compat" % Versions.scalaCollectionCompat, + Orgs.opentracing % "opentracing-mock" % Versions.opentracing % Test ) lazy val opentelemetry = zio ++ Seq( - "io.opentelemetry" % "opentelemetry-api" % Versions.opentelemetry, - "io.opentelemetry" % "opentelemetry-context" % Versions.opentelemetry, - "org.scala-lang.modules" %% "scala-collection-compat" % Versions.scalaCollectionCompat, - "io.opentelemetry" % "opentelemetry-sdk-testing" % Versions.opentelemetry % Test + Orgs.opentelemetry % "opentelemetry-api" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-context" % Versions.opentelemetry, + Orgs.scalaLangModules %% "scala-collection-compat" % Versions.scalaCollectionCompat, + Orgs.opentelemetry % "opentelemetry-sdk-testing" % Versions.opentelemetry % Test ) lazy val opencensus = zio ++ Seq( - "io.opencensus" % "opencensus-api" % Versions.opencensus, - "io.opencensus" % "opencensus-impl" % Versions.opencensus, - "io.opencensus" % "opencensus-contrib-http-util" % Versions.opencensus + Orgs.opencensus % "opencensus-api" % Versions.opencensus, + Orgs.opencensus % "opencensus-impl" % Versions.opencensus, + Orgs.opencensus % "opencensus-contrib-http-util" % Versions.opencensus ) lazy val example = Seq( - "org.typelevel" %% "cats-core" % ExampleVersions.cats, - "io.jaegertracing" % "jaeger-core" % ExampleVersions.jaeger, - "io.jaegertracing" % "jaeger-client" % ExampleVersions.jaeger, - "io.jaegertracing" % "jaeger-zipkin" % ExampleVersions.jaeger, - "com.softwaremill.sttp.client3" %% "async-http-client-backend-zio" % ExampleVersions.sttp3, - "com.softwaremill.sttp.client3" %% "zio-json" % ExampleVersions.sttp3, - "io.d11" %% "zhttp" % ExampleVersions.zioHttp, - "dev.zio" %% "zio-json" % ExampleVersions.zioJson, - "dev.zio" %% "zio-config" % ExampleVersions.zioConfig, - "dev.zio" %% "zio-config-magnolia" % ExampleVersions.zioConfig, - "dev.zio" %% "zio-config-typesafe" % ExampleVersions.zioConfig, + Orgs.typelevel %% "cats-core" % ExampleVersions.cats, + Orgs.jaegertracing % "jaeger-core" % ExampleVersions.jaeger, + Orgs.jaegertracing % "jaeger-client" % ExampleVersions.jaeger, + Orgs.jaegertracing % "jaeger-zipkin" % ExampleVersions.jaeger, + Orgs.softwaremillSttpClient3 %% "async-http-client-backend-zio" % ExampleVersions.sttp3, + Orgs.softwaremillSttpClient3 %% "zio-json" % ExampleVersions.sttp3, + Orgs.d11 %% "zhttp" % ExampleVersions.zioHttp, + Orgs.zio %% "zio-json" % ExampleVersions.zioJson, + Orgs.zio %% "zio-config" % ExampleVersions.zioConfig, + Orgs.zio %% "zio-config-magnolia" % ExampleVersions.zioConfig, + Orgs.zio %% "zio-config-typesafe" % ExampleVersions.zioConfig, // runtime to avoid warning in examples - "org.slf4j" % "slf4j-simple" % ExampleVersions.slf4j % Runtime + Orgs.slf4j % "slf4j-simple" % ExampleVersions.slf4j % Runtime ) lazy val opentracingExample = example ++ Seq( @@ -69,8 +84,9 @@ object Dependencies { ) lazy val opentelemetryExample = example ++ Seq( - "io.opentelemetry" % "opentelemetry-exporter-jaeger" % Versions.opentelemetry, - "io.opentelemetry" % "opentelemetry-sdk" % Versions.opentelemetry, - "io.grpc" % "grpc-netty-shaded" % ExampleVersions.grpcNetty + Orgs.opentelemetry % "opentelemetry-exporter-jaeger" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-sdk" % Versions.opentelemetry, + Orgs.grpc % "grpc-netty-shaded" % ExampleVersions.grpcNetty ) + }