diff --git a/build.sbt b/build.sbt index c14518b7..096191b9 100644 --- a/build.sbt +++ b/build.sbt @@ -2,12 +2,12 @@ enablePlugins(ZioSbtEcosystemPlugin, ZioSbtCiPlugin) inThisBuild( List( - name := "ZIO Telemetry", - organization := "dev.zio", - zioVersion := "2.0.15", - homepage := Some(url("https://zio.dev/zio-telemetry/")), - licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), - developers := List( + name := "ZIO Telemetry", + organization := "dev.zio", + zioVersion := "2.0.15", + homepage := Some(url("https://zio.dev/zio-telemetry/")), + licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), + developers := List( Developer( "mijicd", "Dejan Mijic", @@ -21,12 +21,11 @@ inThisBuild( url("https://github.com/runtologist") ) ), - crossScalaVersions := Seq(scala212.value, scala213.value, "3.3.0"), - ciEnabledBranches := Seq("series/2.x"), - pgpPassphrase := sys.env.get("PGP_PASSWORD").map(_.toArray), - pgpPublicRing := file("/tmp/public.asc"), - pgpSecretRing := file("/tmp/secret.asc"), - scmInfo := Some( + ciEnabledBranches := Seq("series/2.x"), + pgpPassphrase := sys.env.get("PGP_PASSWORD").map(_.toArray), + pgpPublicRing := file("/tmp/public.asc"), + pgpSecretRing := file("/tmp/secret.asc"), + scmInfo := Some( ScmInfo( url("https://github.com/zio/zio-telemetry/"), "scm:git:git@github.com:zio/zio-telemetry.git" @@ -45,9 +44,27 @@ addCommandAlias( "opentracingExample/compile;opentelemetryExample/compile;opentelemetryInstrumentationExample/compile" ) -// Fix 'Flag set repeatedly' error allegedly introduced by the usage of sdtSettings -lazy val tempFixScalacOptions = - Seq("-deprecation", "-encoding", "utf8", "-feature", "-unchecked", "-language:implicitConversions") +def stdModuleSettings(name: Option[String], packageName: Option[String]) = + stdSettings(name, packageName) ++ + Seq( + crossScalaVersions := Seq(scala212.value, scala213.value, scala3.value), + // Fix 'Flag set repeatedly' error allegedly introduced by the usage of sdtSettings: https://github.com/zio/zio-sbt/issues/221 + scalacOptions --= Seq( + "-deprecation", + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:implicitConversions" + ) + ) + +def stdExampleSettings(name: Option[String], packageName: Option[String]) = + stdSettings(name, packageName) ++ + Seq( + crossScalaVersions := Seq(scala212.value, scala213.value), + publish / skip := true + ) lazy val root = project @@ -60,11 +77,10 @@ lazy val opentracing = .in(file("opentracing")) .settings(enableZIO()) .settings( - stdSettings( + stdModuleSettings( name = Some("zio-opentracing"), packageName = Some("zio.telemetry.opentracing") - ), - scalacOptions --= tempFixScalacOptions + ) ) .settings(libraryDependencies ++= Dependencies.opentracing) @@ -73,11 +89,10 @@ lazy val opentelemetry = .in(file("opentelemetry")) .settings(enableZIO()) .settings( - stdSettings( + stdModuleSettings( name = Some("zio-opentelemetry"), packageName = Some("zio.telemetry.opentelemetry") - ), - scalacOptions --= tempFixScalacOptions + ) ) .settings(libraryDependencies ++= Dependencies.opentelemetry) @@ -85,11 +100,10 @@ lazy val opencensus = project .in(file("opencensus")) .settings(enableZIO()) .settings( - stdSettings( + stdModuleSettings( name = Some("zio-opencensus"), packageName = Some("zio.telemetry.opencensus") - ), - scalacOptions --= tempFixScalacOptions + ) ) .settings(libraryDependencies ++= Dependencies.opencensus) @@ -98,13 +112,11 @@ lazy val opentracingExample = .in(file("opentracing-example")) .settings(enableZIO()) .settings( - crossScalaVersions := Seq(scala212.value, scala213.value), - stdSettings( + stdExampleSettings( name = Some("opentracing-example"), packageName = Some("zio.telemetry.opentracing.example") ) ) - .settings(publish / skip := true) .settings(libraryDependencies ++= Dependencies.opentracingExample) .dependsOn(opentracing) @@ -113,13 +125,11 @@ lazy val opentelemetryExample = .in(file("opentelemetry-example")) .settings(enableZIO()) .settings( - crossScalaVersions := Seq(scala212.value, scala213.value), - stdSettings( + stdExampleSettings( name = Some("opentelemetry-example"), packageName = Some("zio.telemetry.opentelemetry.example") ) ) - .settings(publish / skip := true) .settings(libraryDependencies ++= Dependencies.opentelemetryExample) .dependsOn(opentelemetry) @@ -128,13 +138,11 @@ lazy val opentelemetryInstrumentationExample = .in(file("opentelemetry-instrumentation-example")) .settings(enableZIO()) .settings( - crossScalaVersions := Seq(scala212.value, scala213.value), - stdSettings( + stdExampleSettings( name = Some("opentelemetry-instrumentation-example"), packageName = Some("zio.telemetry.opentelemetry.instrumentation.example") ) ) - .settings(publish / skip := true) .settings(libraryDependencies ++= Dependencies.opentelemetryInstrumentationExample) .dependsOn(opentelemetry) @@ -142,13 +150,13 @@ lazy val docs = project .in(file("zio-telemetry-docs")) .settings( + crossScalaVersions := Seq(scala212.value, scala213.value, scala3.value), moduleName := "zio-telemetry-docs", - scalacOptions -= "-Yno-imports", - scalacOptions -= "-Xfatal-warnings", projectName := "ZIO Telemetry", mainModuleName := (opentracing / moduleName).value, projectStage := ProjectStage.ProductionReady, - ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(opentracing, opentelemetry, opencensus) + ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(opentracing, opentelemetry, opencensus), + scalacOptions --= Seq("-Yno-imports", "-Xfatal-warnings") ) .dependsOn(opentracing, opentelemetry, opencensus) .enablePlugins(WebsitePlugin) diff --git a/docs/opentelemetry.md b/docs/opentelemetry.md index a72f8a05..b14cf19b 100644 --- a/docs/opentelemetry.md +++ b/docs/opentelemetry.md @@ -27,7 +27,7 @@ import zio.telemetry.opentelemetry.example.JaegerTracer import io.opentelemetry.api.trace.{ SpanKind, StatusCode } import zio._ -val statusMapper = StatusMapper.failure[Unit](StatusCode.UNSET) +val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET) val app = ZIO.serviceWithZIO[Tracing] { tracing => @@ -133,7 +133,7 @@ import zio.telemetry.opentelemetry.example.JaegerTracer import io.opentelemetry.api.trace.{SpanKind, StatusCode} import zio._ -val statusMapper = StatusMapper.failure[Unit](StatusCode.UNSET) +val statusMapper = StatusMapper.failureNoException(_ => StatusCode.UNSET) val app = ZIO.serviceWithZIO[Tracing] { tracing => diff --git a/docs/opentracing.md b/docs/opentracing.md index 998cf7ff..8029cfea 100644 --- a/docs/opentracing.md +++ b/docs/opentracing.md @@ -30,11 +30,11 @@ val app = import tracing.aspects._ (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") + _ <- tracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT) + _ <- tracing.tag(Tags.HTTP_METHOD.getKey, "GET") + _ <- tracing.setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value") message <- Console.readline - _ <- ZIO.unit @@ log("Message has been read") + _ <- tracing.log("Message has been read") } yield message) @@ root("/app") }.provide(OpenTracing.live, JaegerTracer.live("my-app")) ``` @@ -48,15 +48,14 @@ 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") + val zio1 = tracing.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") + val zio2 = + (for { + _ <- tracing.tag("http.status_code", 200) + _ <- tracing.log("doing some serious work here!") + } yield ()) @@ span("child span") } ``` @@ -76,7 +75,7 @@ ZIO.serviceWithZIO[OpenTracing] { tracing => val buffer = new TextMapAdapter(mutable.Map.empty.asJava) for { - _ <- ZIO.unit @@ inject(Format.Builtin.TEXT_MAP, buffer) + _ <- tracing.inject(Format.Builtin.TEXT_MAP, buffer) _ <- ZIO.unit @@ spanFrom(Format.Builtin.TEXT_MAP, buffer, "child of remote span") } yield buffer } diff --git a/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala b/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala index 27193eea..920b9547 100644 --- a/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala +++ b/opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala @@ -100,24 +100,6 @@ trait Tracing { self => 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 - } - } } 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 ad76deae..1f2b84d3 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 @@ -2,7 +2,7 @@ package zio.telemetry.opentelemetry.example import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.trace.Tracer -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter import io.opentelemetry.sdk.OpenTelemetrySdk import io.opentelemetry.sdk.resources.Resource import io.opentelemetry.sdk.trace.SdkTracerProvider @@ -23,7 +23,7 @@ object JaegerTracer { def makeTracer(host: String): Task[Tracer] = for { - spanExporter <- ZIO.attempt(JaegerGrpcSpanExporter.builder().setEndpoint(host).build()) + spanExporter <- ZIO.attempt(OtlpGrpcSpanExporter.builder().setEndpoint(host).build()) spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter)) tracerProvider <- ZIO.attempt( 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 index 22eaaef5..b4e2153e 100644 --- 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 @@ -14,7 +14,7 @@ case class ProxyHttpApp(client: Client, tracing: Tracing, baggage: Baggage) { import tracing.aspects._ - private val statusMapper: StatusMapper[Throwable, Any] = StatusMapper.failure(StatusCode.UNSET) + private val statusMapper: StatusMapper[Throwable, Any] = StatusMapper.failureThrowable(_ => StatusCode.UNSET) val routes: HttpApp[Any, Throwable] = Http.collectZIO { case Method.GET -> _ / "statuses" => diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/StatusMapper.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/StatusMapper.scala index 0b579ba3..fc9b57a9 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/StatusMapper.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/StatusMapper.scala @@ -1,31 +1,80 @@ package zio.telemetry.opentelemetry.tracing import io.opentelemetry.api.trace.StatusCode -import zio.telemetry.opentelemetry.tracing.StatusMapper.StatusMapperResult +import zio.telemetry.opentelemetry.tracing.StatusMapper.Result -case class StatusMapper[-E, -A]( - failure: PartialFunction[E, StatusMapperResult[Throwable]], - success: PartialFunction[A, StatusMapperResult[String]] +/** + * Maps the result of a wrapped ZIO effect to the status of the [[io.opentelemetry.api.trace.Span]]. + * + * For more details, see: + * [[https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status Set status]] + * + * Usage examples: + * {{{ + * StatusMapper.failure[MyError](_ => StatusCode.ERROR)(e => Some(new RuntimeException(e.message))) + * StatusMapper.failureNoException(_ => StatusCode.ERROR) + * StatusMapper.failureThrowable(StatusCode.ERROR) + * + * StatusMapper.success[Response] { + * resp => if(resp.code == 500) StatusCode.ERROR else StatusCode.OK + * } { resp => + * if(resp.code == 500) Some(resp.errorMessage) else None + * } + * StatusMapper.successNoDescription[Response](_ => StatusCode.OK) + * + * StatusMapper.both( + * StatusMapper.failureThrowable(StatusCode.ERROR), + * StatusMapper.successNoDescription[Any](_ => StatusCode.OK) + * ) + * }}} + * @param failure + * partial function to map the ZIO failure to [[io.opentelemetry.api.trace.StatusCode]] and [[java.lang.Throwable]]. + * The latter is used to record the exception, see: + * [[https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md#recording-an-exception Recording an exception]] + * @param success + * partial function to map the ZIO success to [[io.opentelemetry.api.trace.StatusCode]] and status description + * @tparam E + * @tparam A + */ +sealed abstract class StatusMapper[-E, -A]( + val failure: PartialFunction[E, Result[Throwable]], + val success: PartialFunction[A, Result[String]] ) object StatusMapper { + final case class Failure[-E](pf: PartialFunction[E, Result[Throwable]]) + extends StatusMapper[E, Any](pf, PartialFunction.empty) + final case class Success[-A](pf: PartialFunction[A, Result[String]]) + extends StatusMapper[Any, A](PartialFunction.empty, pf) + + final case class Result[+T](statusCode: StatusCode, error: Option[T] = None) + + private[tracing] def apply[E, A]( + failure: PartialFunction[E, Result[Throwable]], + success: PartialFunction[A, Result[String]] + ): StatusMapper[E, A] = + new StatusMapper[E, A](failure, success) {} + + def both[E, A](failure: Failure[E], success: Success[A]): StatusMapper[E, A] = + StatusMapper[E, A](failure.pf, success.pf) + val default: StatusMapper[Any, Any] = StatusMapper(PartialFunction.empty, PartialFunction.empty) - final case class StatusMapperResult[+T](statusCode: StatusCode, error: Option[T] = None) - - def failure(statusCode: StatusCode): StatusMapper[Throwable, Any] = StatusMapper( - { case e => StatusMapperResult(statusCode, Option(e)) }, - PartialFunction.empty - ) - - def success[A]( - statusCode: StatusCode, - f: A => Option[String] = { (_: A) => Option.empty[String] } - ): StatusMapper[Any, A] = - StatusMapper( - PartialFunction.empty, - { case a => StatusMapperResult(statusCode, f(a)) } - ) + def failure[E](toStatusCode: E => StatusCode)(toError: E => Option[Throwable]): Failure[E] = + Failure { case e => Result(toStatusCode(e), toError(e)) } + + def failureNoException[E](toStatusCode: E => StatusCode): Failure[E] = + Failure { case e => Result(toStatusCode(e)) } + + def failureThrowable(toStatusCode: Throwable => StatusCode): Failure[Throwable] = + Failure { case e => Result(toStatusCode(e), Option(e)) } + + def success[A](toStatusCode: A => StatusCode)(toError: A => Option[String]): Success[A] = + Success { case a => Result(toStatusCode(a), toError(a)) } + + def successNoDescription[A](toStatusCode: A => StatusCode): Success[A] = + Success { case a => Result(toStatusCode(a)) } + } diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala index adf33849..415b4a1b 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala @@ -20,7 +20,7 @@ trait Tracing { self => * @param trace * @return */ - def addEvent(name: String)(implicit trace: Trace): UIO[Span] + def addEvent(name: String)(implicit trace: Trace): UIO[Unit] /** * Adds an event with attributes to the current span. @@ -34,7 +34,7 @@ trait Tracing { self => def addEventWithAttributes( name: String, attributes: Attributes - )(implicit trace: Trace): UIO[Span] + )(implicit trace: Trace): UIO[Unit] /** * Extracts the span from carrier `C` and set its child span with name 'spanName' as the current span. @@ -50,7 +50,7 @@ trait Tracing { self => * @param spanKind * kind of the child span * @param statusMapper - * error mapper + * status mapper * @param links * spanContexts of the linked Spans. * @param zio @@ -169,7 +169,7 @@ trait Tracing { self => * @param spanKind * kind of the child span * @param statusMapper - * error mapper + * status mapper * @param links * spanContexts of the linked Spans. * @param zio @@ -199,7 +199,7 @@ trait Tracing { self => * @param spanKind * name of the new root span * @param statusMapper - * error mapper + * status mapper * @param links * spanContexts of the linked Spans. * @param zio @@ -271,7 +271,7 @@ trait Tracing { self => * @param trace * @return */ - def setAttribute(name: String, value: Boolean)(implicit trace: Trace): UIO[Span] + def setAttribute(name: String, value: Boolean)(implicit trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -281,7 +281,7 @@ trait Tracing { self => * @param trace * @return */ - def setAttribute(name: String, value: Double)(implicit trace: Trace): UIO[Span] + def setAttribute(name: String, value: Double)(implicit trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -291,7 +291,7 @@ trait Tracing { self => * @param trace * @return */ - def setAttribute(name: String, value: Long)(implicit trace: Trace): UIO[Span] + def setAttribute(name: String, value: Long)(implicit trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -301,7 +301,7 @@ trait Tracing { self => * @param trace * @return */ - def setAttribute(name: String, value: String)(implicit trace: Trace): UIO[Span] + def setAttribute(name: String, value: String)(implicit trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -312,7 +312,7 @@ trait Tracing { self => * @tparam T * @return */ - def setAttribute[T](key: AttributeKey[T], value: T)(implicit trace: Trace): UIO[Span] + def setAttribute[T](key: AttributeKey[T], value: T)(implicit trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -322,7 +322,7 @@ trait Tracing { self => * @param trace * @return */ - def setAttribute(name: String, values: Seq[String])(implicit trace: Trace): UIO[Span] + def setAttribute(name: String, values: Seq[String])(implicit trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -334,7 +334,7 @@ trait Tracing { self => * @param trace * @return */ - def setAttribute(name: String, values: Seq[Boolean])(implicit i1: DummyImplicit, trace: Trace): UIO[Span] + def setAttribute(name: String, values: Seq[Boolean])(implicit i1: DummyImplicit, trace: Trace): UIO[Unit] /** * Sets an attribute of the current span. @@ -352,7 +352,7 @@ trait Tracing { self => i1: DummyImplicit, i2: DummyImplicit, trace: Trace - ): UIO[Span] + ): UIO[Unit] /** * Sets an attribute of the current span. @@ -373,7 +373,7 @@ trait Tracing { self => i2: DummyImplicit, i3: DummyImplicit, trace: Trace - ): UIO[Span] + ): UIO[Unit] /** * Sets the current span to be the child of the current span with name 'spanName'. @@ -385,7 +385,7 @@ trait Tracing { self => * @param spanKind * kind of the child span * @param statusMapper - * error mapper + * status mapper * @param links * spanContexts of the linked Spans. * @param zio @@ -414,7 +414,7 @@ trait Tracing { self => * @param spanKind * kind of the child span * @param statusMapper - * error mapper + * status mapper * @param links * spanContexts of the linked Spans. */ @@ -605,14 +605,17 @@ object Tracing { getCurrentContextUnsafe.flatMap { old => ZIO.acquireReleaseExit { for { - res <- createChild(old, spanName, spanKind, attributes, links) - _ <- ctxStorage.locallyScoped(res._2) - } yield res + childUnsafe <- createChild(old, spanName, spanKind, attributes, links) + (_, ctx) = childUnsafe + _ <- ctxStorage.locallyScoped(ctx) + } yield childUnsafe } { case ((endSpan, ctx), exit) => - (exit match { + val setStatus = exit match { case Exit.Success(_) => ZIO.unit - case Exit.Failure(cause) => setErrorStatus(Span.fromContext(ctx), cause, statusMapper) - }) *> endSpan + case Exit.Failure(cause) => setFailureStatus(Span.fromContext(ctx), cause, statusMapper) + } + + setStatus *> endSpan }.unit } @@ -687,56 +690,55 @@ object Tracing { finalizeSpanUsingEffect(zio, ctx, statusMapper) } - override def addEvent(name: String)(implicit trace: Trace): UIO[Span] = + override def addEvent(name: String)(implicit trace: Trace): UIO[Unit] = for { nanos <- currentNanos span <- getCurrentSpanUnsafe - } yield span.addEvent(name, nanos, TimeUnit.NANOSECONDS) + _ <- ZIO.succeed(span.addEvent(name, nanos, TimeUnit.NANOSECONDS)) + } yield () - override def addEventWithAttributes( - name: String, - attributes: Attributes - )(implicit trace: Trace): UIO[Span] = + override def addEventWithAttributes(name: String, attributes: Attributes)(implicit trace: Trace): UIO[Unit] = for { nanos <- currentNanos span <- getCurrentSpanUnsafe - } yield span.addEvent(name, attributes, nanos, TimeUnit.NANOSECONDS) + _ <- ZIO.succeed(span.addEvent(name, attributes, nanos, TimeUnit.NANOSECONDS)) + } yield () - override def setAttribute(name: String, value: Boolean)(implicit trace: Trace): UIO[Span] = - getCurrentSpanUnsafe.map(_.setAttribute(name, value)) + override def setAttribute(name: String, value: Boolean)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setAttribute(name, value)).unit - override def setAttribute(name: String, value: Double)(implicit trace: Trace): UIO[Span] = - getCurrentSpanUnsafe.map(_.setAttribute(name, value)) + override def setAttribute(name: String, value: Double)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setAttribute(name, value)).unit - override def setAttribute(name: String, value: Long)(implicit trace: Trace): UIO[Span] = - getCurrentSpanUnsafe.map(_.setAttribute(name, value)) + override def setAttribute(name: String, value: Long)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setAttribute(name, value)).unit - override def setAttribute(name: String, value: String)(implicit trace: Trace): UIO[Span] = - getCurrentSpanUnsafe.map(_.setAttribute(name, value)) + override def setAttribute(name: String, value: String)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setAttribute(name, value)).unit - override def setAttribute[T](key: AttributeKey[T], value: T)(implicit trace: Trace): UIO[Span] = - getCurrentSpanUnsafe.map(_.setAttribute(key, value)) + override def setAttribute[T](key: AttributeKey[T], value: T)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setAttribute(key, value)).unit - override def setAttribute(name: String, values: Seq[String])(implicit trace: Trace): UIO[Span] = { + override def setAttribute(name: String, values: Seq[String])(implicit trace: Trace): UIO[Unit] = { val v = values.asJava - getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.stringArrayKey(name), v)) + getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.stringArrayKey(name), v)).unit } override def setAttribute(name: String, values: Seq[Boolean])(implicit i1: DummyImplicit, trace: Trace - ): UIO[Span] = { + ): UIO[Unit] = { val v = values.map(Boolean.box).asJava - getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.booleanArrayKey(name), v)) + getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.booleanArrayKey(name), v)).unit } override def setAttribute(name: String, values: Seq[Long])(implicit i1: DummyImplicit, i2: DummyImplicit, trace: Trace - ): UIO[Span] = { + ): UIO[Unit] = { val v = values.map(Long.box).asJava - getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.longArrayKey(name), v)) + getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.longArrayKey(name), v)).unit } override def setAttribute(name: String, values: Seq[Double])(implicit @@ -744,26 +746,26 @@ object Tracing { i2: DummyImplicit, i3: DummyImplicit, trace: Trace - ): UIO[Span] = { + ): UIO[Unit] = { val v = values.map(Double.box).asJava - getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.doubleArrayKey(name), v)) + getCurrentSpanUnsafe.map(_.setAttribute(AttributeKey.doubleArrayKey(name), v)).unit } - private def setErrorFromResult[E, A](span: Span, a: A, statusMapper: StatusMapper[E, A]): UIO[Span] = + private def setSuccessStatus[E, A](span: Span, a: A, statusMapper: StatusMapper[E, A]): UIO[Span] = statusMapper.success .lift(a) - .fold(ZIO.succeed(span)) { case StatusMapper.StatusMapperResult(errorStatus, maybeError) => + .fold(ZIO.succeed(span)) { case StatusMapper.Result(statusCode, maybeError) => for { - result <- if (errorStatus == StatusCode.ERROR) - maybeError.fold(ZIO.succeed(span.setStatus(errorStatus)))(errorMessage => - ZIO.succeed(span.setStatus(errorStatus, errorMessage)) + result <- if (statusCode == StatusCode.ERROR) + maybeError.fold(ZIO.succeed(span.setStatus(statusCode)))(errorMessage => + ZIO.succeed(span.setStatus(statusCode, errorMessage)) ) else - ZIO.succeed(span.setStatus(errorStatus)) + ZIO.succeed(span.setStatus(statusCode)) } yield result } - private def setErrorStatus[E, A]( + private def setFailureStatus[E, A]( span: Span, cause: Cause[E], statusMapper: StatusMapper[E, A] @@ -771,7 +773,7 @@ object Tracing { val statusMapperResult = cause.failureOption .flatMap(statusMapper.failure.lift) - .getOrElse(StatusMapper.StatusMapperResult(StatusCode.ERROR, None)) + .getOrElse(StatusMapper.Result(StatusCode.ERROR, None)) for { _ <- if (statusMapperResult.statusCode == StatusCode.ERROR) @@ -794,8 +796,8 @@ object Tracing { )(implicit trace: Trace): ZIO[R, E, A] = ctxStorage .locally(ctx)(zio) - .tapErrorCause(setErrorStatus(Span.fromContext(ctx), _, statusMapper)) - .tap(setErrorFromResult(Span.fromContext(ctx), _, statusMapper)) + .tapErrorCause(setFailureStatus(Span.fromContext(ctx), _, statusMapper)) + .tap(setSuccessStatus(Span.fromContext(ctx), _, statusMapper)) private def currentNanos(implicit trace: Trace): UIO[Long] = Clock.currentTime(TimeUnit.NANOSECONDS) diff --git a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala index c319617a..55fe299f 100644 --- a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala @@ -9,10 +9,9 @@ import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import zio._ import zio.telemetry.opentelemetry.context.{ContextStorage, IncomingContextCarrier, OutgoingContextCarrier} -import zio.telemetry.opentelemetry.tracing.StatusMapper.StatusMapperResult import zio.telemetry.opentelemetry.tracing.propagation.TraceContextPropagator import zio.test.Assertion._ -import zio.test.{TestClock, ZIOSpecDefault, assert} +import zio.test.{Spec, TestClock, ZIOSpecDefault, assert} import scala.collection.mutable import scala.concurrent.Future @@ -40,7 +39,7 @@ object TracingTest extends ZIOSpecDefault { .service[InMemorySpanExporter] .map(_.getFinishedSpanItems.asScala.toList) - def spec = + def spec: Spec[Any, Throwable] = suite("zio opentelemetry")( suite("Tracing")( creationSpec, @@ -61,72 +60,36 @@ object TracingTest extends ZIOSpecDefault { private val spansSpec = suite("spans")( - test("span") { + test("root") { ZIO.serviceWithZIO[Tracing] { tracing => import tracing.aspects._ for { - _ <- ZIO.unit @@ span("Child") @@ span("Root") + _ <- ZIO.unit @@ root("ROOT2") @@ root("ROOT") spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - child = spans.find(_.getName == "Child") + root = spans.find(_.getName == "ROOT") + child = spans.find(_.getName == "ROOT2") } yield assert(root)(isSome(anything)) && assert(child)( isSome( hasField[SpanData, String]( - "parentSpanId", + "parent", _.getParentSpanId, - equalTo(root.get.getSpanId) + equalTo(SpanId.getInvalid) ) ) ) } }, - test("set status code for success span") { + test("span") { ZIO.serviceWithZIO[Tracing] { tracing => import tracing.aspects._ - val assertStatusCodeError = - hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.ERROR)) - val assertStatusDescriptionError = - hasField[SpanData, String]( - "statusDescription", - _.getStatus.getDescription, - equalTo("My error message. Result = success") - ) - val assertRecordedExceptionAttributes = hasField[SpanData, List[(String, String)]]( - "exceptionAttributes", - _.getEvents.asScala.toList - .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), - isEmpty - ) - val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError - val statusMapper = - StatusMapper.success[String](StatusCode.ERROR, r => Option(s"My error message. Result = $r")) for { - _ <- - ZIO.succeed("success") @@ span("Child", statusMapper = statusMapper) @@ span("Root") + _ <- ZIO.unit @@ span("Child") @@ span("Root") spans <- getFinishedSpans + root = spans.find(_.getName == "Root") child = spans.find(_.getName == "Child") - } yield assert(child)(isSome(assertion)) - } - }, - test("addLinks") { - ZIO.serviceWithZIO[Tracing] { tracing => - import tracing.aspects._ - - for { - res <- inMemoryTracer - (_, tracer) = res - externallyProvidedRootSpan1 = tracer.spanBuilder("external1").startSpan() - externallyProvidedRootSpan2 = tracer.spanBuilder("external2").startSpan() - externallyProvidedRootSpan3 = tracer.spanBuilder("external3").startSpan() - links = List(externallyProvidedRootSpan1, externallyProvidedRootSpan2, externallyProvidedRootSpan3) - .map(_.getSpanContext) - _ <- ZIO.unit @@ span("Child", links = links) @@ span("Root") - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - child = spans.find(_.getName == "Child") } yield assert(root)(isSome(anything)) && assert(child)( isSome( @@ -136,98 +99,6 @@ object TracingTest extends ZIOSpecDefault { equalTo(root.get.getSpanId) ) ) - ) && - assert(child.toList.flatMap(_.getLinks.asScala.toList.map(_.getSpanContext.getSpanId)))( - hasSameElements(links.map(_.getSpanId)) - ) - } - }, - test("setError") { - ZIO.serviceWithZIO[Tracing] { tracing => - import tracing.aspects._ - - val assertStatusCodeError = - hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.ERROR)) - val assertStatusDescriptionError = - hasField[SpanData, String]( - "statusDescription", - _.getStatus.getDescription, - containsString("java.lang.RuntimeException: some_error") - ) - val assertRecordedExceptionAttributes = hasField[SpanData, List[(String, String)]]( - "exceptionAttributes", - _.getEvents.asScala.toList - .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), - hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) - ) - val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError - val statusMapper = StatusMapper.failure(StatusCode.ERROR) - - val alwaysTrue = true - val failedEffect: ZIO[Any, Throwable, Unit] = - ZIO.fail(new RuntimeException("some_error")).when(alwaysTrue).unit - - for { - _ <- (failedEffect @@ span("Child", statusMapper = statusMapper) @@ span( - "Root", - statusMapper = statusMapper - )).ignore - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - child = spans.find(_.getName == "Child") - } yield assert(root)(isSome(assertion)) && assert(child)(isSome(assertion)) - } - }, - test("setError without description") { - ZIO.serviceWithZIO[Tracing] { tracing => - import tracing.aspects._ - - val assertStatusCodeError = - hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.UNSET)) - val assertStatusDescriptionError = - hasField[SpanData, String]("statusDescription", _.getStatus.getDescription, isEmptyString) - val assertRecordedExceptionAttributes = hasField[SpanData, List[(String, String)]]( - "exceptionAttributes", - _.getEvents.asScala.toList - .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), - hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) - ) - val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError - val statusMapper = StatusMapper.failure(StatusCode.UNSET) - - val alwaysTrue = true - val failedEffect: ZIO[Any, Throwable, Unit] = - ZIO.fail(new RuntimeException("some_error")).when(alwaysTrue).unit - - for { - _ <- (failedEffect @@ span("Child", statusMapper = statusMapper) @@ span( - "Root", - statusMapper = statusMapper - )).ignore - spans <- getFinishedSpans - root = spans.find(_.getName == "Root") - child = spans.find(_.getName == "Child") - } yield assert(root)(isSome(assertion)) && assert(child)(isSome(assertion)) - } - }, - test("root") { - 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) - ) - ) ) } }, @@ -456,6 +327,37 @@ object TracingTest extends ZIOSpecDefault { } } }, + test("addLinks") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + res <- inMemoryTracer + (_, tracer) = res + externallyProvidedRootSpan1 = tracer.spanBuilder("external1").startSpan() + externallyProvidedRootSpan2 = tracer.spanBuilder("external2").startSpan() + externallyProvidedRootSpan3 = tracer.spanBuilder("external3").startSpan() + links = List(externallyProvidedRootSpan1, externallyProvidedRootSpan2, externallyProvidedRootSpan3) + .map(_.getSpanContext) + _ <- ZIO.unit @@ span("Child", links = links) @@ 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) + ) + ) + ) && + assert(child.toList.flatMap(_.getLinks.asScala.toList.map(_.getSpanContext.getSpanId)))( + hasSameElements(links.map(_.getSpanId)) + ) + } + }, test("resources") { ZIO.serviceWithZIO[Tracing] { tracing => for { @@ -466,6 +368,191 @@ object TracingTest extends ZIOSpecDefault { released <- ref.get } yield assert(released)(isFalse) } + }, + test("status mapper for successful span") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + val assertStatusCodeError = + hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.ERROR)) + + val assertStatusDescriptionError = + hasField[SpanData, String]( + "statusDescription", + _.getStatus.getDescription, + equalTo("My error message. Result = success") + ) + + val assertRecordedExceptionAttributes = + hasField[SpanData, List[(String, String)]]( + "exceptionAttributes", + _.getEvents.asScala.toList + .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), + isEmpty + ) + + val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError + + val statusMapper = + StatusMapper.success[String](_ => StatusCode.ERROR)(r => Option(s"My error message. Result = $r")) + + for { + _ <- + ZIO.succeed("success") @@ span("Child", statusMapper = statusMapper) @@ span("Root") + spans <- getFinishedSpans + child = spans.find(_.getName == "Child") + } yield assert(child)(isSome(assertion)) + } + }, + test("status mapper for failed span") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + val assertStatusCodeError = + hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.ERROR)) + + val assertStatusDescriptionError = + hasField[SpanData, String]( + "statusDescription", + _.getStatus.getDescription, + containsString("java.lang.RuntimeException: some_error") + ) + + val assertRecordedExceptionAttributes = + hasField[SpanData, List[(String, String)]]( + "exceptionAttributes", + _.getEvents.asScala.toList + .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), + hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) + ) + + val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError + val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.ERROR) + + val failedEffect: ZIO[Any, Throwable, Unit] = + ZIO.fail(new RuntimeException("some_error")).when(true).unit + + for { + _ <- ( + failedEffect @@ + span("Child", statusMapper = statusMapper) @@ + span("Root", statusMapper = statusMapper) + ).ignore + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + child = spans.find(_.getName == "Child") + } yield assert(root)(isSome(assertion)) && assert(child)(isSome(assertion)) + } + }, + test("status mapper for failed span when error type is not Throwable") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + val assertStatusCodeError = + hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.ERROR)) + + val assertStatusDescriptionError = + hasField[SpanData, String]( + "statusDescription", + _.getStatus.getDescription, + containsString("Error(some_error)") + ) + + val assertRecordedExceptionAttributes = + hasField[SpanData, List[(String, String)]]( + "exceptionAttributes", + _.getEvents.asScala.toList + .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), + hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) + ) + + val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError + val statusMapper = + StatusMapper.failure[Error](_ => StatusCode.ERROR)(e => Option(new RuntimeException(e.msg))) + + final case class Error(msg: String) + val failedEffect: ZIO[Any, Error, Unit] = + ZIO.fail(Error("some_error")).when(true).unit + + for { + _ <- ( + failedEffect @@ + span("Child", statusMapper = statusMapper) @@ + span("Root", statusMapper = statusMapper) + ).ignore + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + child = spans.find(_.getName == "Child") + } yield assert(root)(isSome(assertion)) && assert(child)(isSome(assertion)) + } + }, + test("status mapper without description for failed span") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + val assertStatusCodeUnset = + hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.UNSET)) + + val assertStatusDescriptionEmpty = + hasField[SpanData, String]("statusDescription", _.getStatus.getDescription, isEmptyString) + + val assertRecordedExceptionAttributes = + hasField[SpanData, List[(String, String)]]( + "exceptionAttributes", + _.getEvents.asScala.toList + .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), + hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) + ) + + val assertion = assertStatusCodeUnset && assertRecordedExceptionAttributes && assertStatusDescriptionEmpty + val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET) + + val failedEffect: ZIO[Any, Throwable, Unit] = + ZIO.fail(new RuntimeException("some_error")).when(true).unit + + for { + _ <- ( + failedEffect @@ + span("Child", statusMapper = statusMapper) @@ + span("Root", statusMapper = statusMapper) + ).ignore + spans <- getFinishedSpans + root = spans.find(_.getName == "Root") + child = spans.find(_.getName == "Child") + } yield assert(root)(isSome(assertion)) && assert(child)(isSome(assertion)) + } + }, + test("combine status mappers") { + val assertErrorStatusCodeUnset = + hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.UNSET)) + + val assertSuccessStatusCodeOk = + hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.OK)) + + val assertStatusDescriptionEmpty = + hasField[SpanData, String]("statusDescription", _.getStatus.getDescription, isEmptyString) + + val failureAssertion = assertErrorStatusCodeUnset && assertStatusDescriptionEmpty + val successAssertion = assertSuccessStatusCodeOk && assertStatusDescriptionEmpty + + val failureMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET) + val successMapper = StatusMapper.successNoDescription[Unit](_ => StatusCode.OK) + val statusMapper = StatusMapper.both(failureMapper, successMapper) + + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- ( + ZIO.fail[Throwable](new RuntimeException("error")).unit @@ + span("KO", statusMapper = statusMapper) + ).ignore + _ <- ZIO.succeed(()) @@ span("OK", statusMapper = statusMapper) + spans <- getFinishedSpans + ko = spans.find(_.getName == "KO") + ok = spans.find(_.getName == "OK") + } yield assert(ko)(isSome(failureAssertion)) && assert(ok)(isSome(successAssertion)) + } } ).provideLayer(tracingMockLayer) @@ -475,9 +562,7 @@ object TracingTest extends ZIOSpecDefault { ZIO.serviceWithZIO[Tracing] { tracing => for { _ <- ZIO.scoped[Any]( - tracing.spanScoped("Root") *> ZIO.scoped[Any]( - tracing.spanScoped("Child") - ) + tracing.spanScoped("Root") *> ZIO.scoped[Any](tracing.spanScoped("Child")) ) spans <- getFinishedSpans root = spans.find(_.getName == "Root") @@ -518,38 +603,39 @@ object TracingTest extends ZIOSpecDefault { ) } }, - test("setError") { + test("status mapper for failed span") { ZIO.serviceWithZIO[Tracing] { tracing => - val assertStatusCodeError = + val assertStatusCodeError = hasField[SpanData, StatusCode]("statusCode", _.getStatus.getStatusCode, equalTo(StatusCode.ERROR)) - val assertStatusDescriptionError = + + val assertStatusDescriptionError = hasField[SpanData, String]( "statusDescription", _.getStatus.getDescription, containsString("java.lang.RuntimeException: some_error") ) - val assertRecordedExceptionAttributes = hasField[SpanData, List[(String, String)]]( - "exceptionAttributes", - _.getEvents.asScala.toList - .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), - hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) - ) - val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError - val statusMapper: StatusMapper[Any, Unit] = StatusMapper[Any, Unit]( - { case e => - StatusMapperResult(StatusCode.ERROR, Option(e.asInstanceOf[Throwable])) - }, - Map.empty - ) + + val assertRecordedExceptionAttributes = + hasField[SpanData, List[(String, String)]]( + "exceptionAttributes", + _.getEvents.asScala.toList + .flatMap(_.getAttributes.asMap().asScala.toList.map(x => x._1.getKey -> x._2.toString)), + hasSubset(List("exception.message" -> "some_error", "exception.type" -> "java.lang.RuntimeException")) + ) + + val assertion = assertStatusCodeError && assertRecordedExceptionAttributes && assertStatusDescriptionError + val statusMapper = StatusMapper.failure[Any](_ => StatusCode.ERROR)(e => Option(e.asInstanceOf[Throwable])) + val failedEffect: ZIO[Any, Throwable, Unit] = ZIO.fail(new RuntimeException("some_error")).unit for { _ <- ZIO .scoped[Any]( - tracing.spanScoped("Root", statusMapper = statusMapper) *> ZIO.scoped[Any]( - tracing.spanScoped("Child", statusMapper = statusMapper) *> failedEffect - ) + tracing.spanScoped("Root", statusMapper = statusMapper) *> + ZIO.scoped[Any]( + tracing.spanScoped("Child", statusMapper = statusMapper) *> failedEffect + ) ) .ignore spans <- getFinishedSpans 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 index 8a72d8a5..ad72e250 100644 --- 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 @@ -19,9 +19,9 @@ case class ProxyHttpApp(client: Client, tracing: OpenTracing) { 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") + _ <- tracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT) + _ <- tracing.tag(Tags.HTTP_METHOD.getKey, GET.method) + _ <- tracing.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) @@ -34,8 +34,7 @@ case class ProxyHttpApp(client: Client, tracing: OpenTracing) { ZIO.succeed { adapter.forEach { entry => - m.put(entry.getKey, entry.getValue): Unit - () + m.put(entry.getKey, entry.getValue).fold(())(_ => ()) } }.as(m.toMap) } diff --git a/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala b/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala index 55b5720b..391afee6 100644 --- a/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala +++ b/opentracing/src/main/scala/zio/telemetry/opentracing/OpenTracing.scala @@ -10,9 +10,9 @@ import scala.jdk.CollectionConverters._ trait OpenTracing { self => - def getCurrentSpan(implicit trace: Trace): UIO[Span] + def getCurrentSpanUnsafe(implicit trace: Trace): UIO[Span] - def getCurrentSpanContext(implicit trace: Trace): UIO[SpanContext] + def getCurrentSpanContextUnsafe(implicit trace: Trace): UIO[SpanContext] def error( span: Span, @@ -23,21 +23,21 @@ trait OpenTracing { self => 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 log(fields: Map[String, _])(implicit trace: Trace): UIO[Unit] - def log[R, E, A](msg: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + def log(msg: String)(implicit trace: Trace): UIO[Unit] 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] + )(zio: => 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] + )(zio: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] def spanFrom[R, E, S, C]( format: Format[C], @@ -45,17 +45,17 @@ trait OpenTracing { self => operation: String, tagError: Boolean = true, logError: Boolean = true - )(effect: => ZIO[R, E, S])(implicit trace: Trace): ZIO[R, E, S] + )(zio: => ZIO[R, E, S])(implicit trace: Trace): ZIO[R, E, S] - def tag[R, E, A](key: String, value: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + def tag(key: String, value: String)(implicit trace: Trace): UIO[Unit] - def tag[R, E, A](key: String, value: Int)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + def tag(key: String, value: Int)(implicit trace: Trace): UIO[Unit] - def tag[R, E, A](key: String, value: Boolean)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + def tag(key: String, value: Boolean)(implicit trace: Trace): UIO[Unit] def inject[C](format: Format[C], carrier: C)(implicit trace: Trace): UIO[Unit] - def setBaggageItem[R, E, A](key: String, value: String)(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] + def setBaggageItem(key: String, value: String)(implicit trace: Trace): UIO[Unit] def getBaggageItem(key: String)(implicit trace: Trace): UIO[Option[String]] @@ -93,48 +93,6 @@ trait OpenTracing { self => self.spanFrom(format, carrier, operation, tagError, logError)(zio) } - 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(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(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 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 - } - } } @@ -154,11 +112,11 @@ object OpenTracing { currentSpan <- FiberRef.make(span) currentMicros = Clock.currentTime(TimeUnit.MICROSECONDS) } yield new OpenTracing { self => - override def getCurrentSpan(implicit trace: Trace): UIO[Span] = + override def getCurrentSpanUnsafe(implicit trace: Trace): UIO[Span] = currentSpan.get - override def getCurrentSpanContext(implicit trace: Trace): UIO[SpanContext] = - getCurrentSpan.map(_.context) + override def getCurrentSpanContextUnsafe(implicit trace: Trace): UIO[SpanContext] = + getCurrentSpanUnsafe.map(_.context) override def error( span: Span, @@ -174,24 +132,22 @@ object OpenTracing { 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(fields: Map[String, _])(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.zipWith(currentMicros)((span, now) => span.log(now, fields.asJava)).unit - 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 log(msg: String)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.zipWith(currentMicros)((span, now) => span.log(now, msg)).unit 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] = + )(zio: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = for { root <- ZIO.succeed(tracer.buildSpan(operation).start()) - current <- getCurrentSpan + current <- getCurrentSpanUnsafe _ <- currentSpan.set(root) - res <- effect + res <- zio .catchAllCause(c => error(root, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) .ensuring(finish(root) *> currentSpan.set(current)) } yield res @@ -200,12 +156,12 @@ object OpenTracing { operation: String, tagError: Boolean = true, logError: Boolean = true - )(effect: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + )(zio: => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = for { - current <- getCurrentSpan + current <- getCurrentSpanUnsafe child <- ZIO.succeed(tracer.buildSpan(operation).asChildOf(current).start()) _ <- currentSpan.set(child) - res <- effect + res <- zio .catchAllCause(c => error(child, c, tagError, logError) *> ZIO.done(Exit.Failure(c))) .ensuring(finish(child) *> currentSpan.set(current)) } yield res @@ -216,58 +172,50 @@ object OpenTracing { operation: String, tagError: Boolean = true, logError: Boolean = true - )(effect: => ZIO[R, E, S])(implicit trace: Trace): ZIO[R, E, S] = + )(zio: => ZIO[R, E, S])(implicit trace: Trace): ZIO[R, E, S] = ZIO .attempt(tracer.extract(format, carrier)) .foldZIO( - _ => effect, + _ => zio, spanCtx => for { - current <- getCurrentSpan + current <- getCurrentSpanUnsafe span <- ZIO.succeed(tracer.buildSpan(operation).asChildOf(spanCtx).start()) _ <- currentSpan.set(span) - res <- effect + res <- zio .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(key: String, value: String)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setTag(key, value)).unit - 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(key: String, value: Int)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setTag(key, value)).unit - 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 tag(key: String, value: Boolean)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setTag(key, value)).unit override def inject[C](format: Format[C], carrier: C)(implicit trace: Trace): UIO[Unit] = for { - span <- getCurrentSpan + span <- getCurrentSpanUnsafe _ <- 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 setBaggageItem(key: String, value: String)(implicit trace: Trace): UIO[Unit] = + getCurrentSpanUnsafe.map(_.setBaggageItem(key, value)).unit override def getBaggageItem(key: String)(implicit trace: Trace): UIO[Option[String]] = for { - span <- getCurrentSpan + span <- getCurrentSpanUnsafe res <- ZIO.succeed(span.getBaggageItem(key)).map(Option(_)) } yield res } def release(tracing: OpenTracing) = - tracing.getCurrentSpan.flatMap(span => ZIO.succeed(span.finish())) + tracing.getCurrentSpanUnsafe.flatMap(span => ZIO.succeed(span.finish())) ZIO.acquireRelease(acquire)(release) } diff --git a/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala b/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala index c4fa6524..92f7ee71 100644 --- a/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala +++ b/opentracing/src/test/scala/zio/telemetry/opentracing/OpenTracingTest.scala @@ -163,9 +163,15 @@ object OpenTracingTest extends ZIOSpecDefault { ZIO.serviceWithZIO[OpenTracing] { tracing => import tracing.aspects._ + val zio = for { + _ <- tracing.tag("boolean", value = true) + _ <- tracing.tag("int", 1) + _ <- tracing.tag("string", "foo") + } yield () + for { tracer <- ZIO.service[MockTracer] - _ <- ZIO.unit @@ tag("boolean", value = true) @@ tag("int", 1) @@ tag("string", "foo") @@ span("foo") + _ <- zio @@ span("foo") } yield { val tags = tracer.finishedSpans().asScala.head.tags.asScala.toMap val expected = Map[String, Any]("boolean" -> true, "int" -> 1, "string" -> "foo") @@ -180,11 +186,15 @@ object OpenTracingTest extends ZIOSpecDefault { val duration = 1000.micros + val zio = for { + _ <- tracing.log("message") + _ <- TestClock.adjust(duration) + _ <- tracing.log(Map("msg" -> "message", "size" -> 1)) + } yield () + for { tracer <- ZIO.service[MockTracer] - logging = ZIO.unit @@ log("message") *> - TestClock.adjust(duration) @@ log(Map("msg" -> "message", "size" -> 1)) - _ <- tracing.span("foo")(logging) + _ <- zio @@ span("foo") } yield { val tags = tracer @@ -208,11 +218,9 @@ object OpenTracingTest extends ZIOSpecDefault { }, test("baggage") { ZIO.serviceWithZIO[OpenTracing] { tracing => - import tracing.aspects._ - for { - _ <- ZIO.unit @@ setBaggageItem("foo", "bar") - _ <- ZIO.unit @@ setBaggageItem("bar", "baz") + _ <- tracing.setBaggageItem("foo", "bar") + _ <- tracing.setBaggageItem("bar", "baz") fooBag <- tracing.getBaggageItem("foo") barBag <- tracing.getBaggageItem("bar") } yield assert(fooBag)(isSome(equalTo("bar"))) && diff --git a/project/Dependencies.scala b/project/Dependencies.scala index d25dab0e..d76d6494 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -84,10 +84,10 @@ object Dependencies { ) lazy val opentelemetryExample = example ++ Seq( - Orgs.opentelemetry % "opentelemetry-exporter-jaeger" % Versions.opentelemetry, - Orgs.opentelemetry % "opentelemetry-sdk" % Versions.opentelemetry, - Orgs.grpc % "grpc-netty-shaded" % ExampleVersions.grpcNetty, - Orgs.zio %% "zio-http" % ExampleVersions.zioHttp + Orgs.opentelemetry % "opentelemetry-exporter-otlp" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-sdk" % Versions.opentelemetry, + Orgs.grpc % "grpc-netty-shaded" % ExampleVersions.grpcNetty, + Orgs.zio %% "zio-http" % ExampleVersions.zioHttp ) lazy val opentelemetryInstrumentationExample = example ++ Seq(