From dc7038d189d4c583e4de2b87a5b8f777a054d0ba Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 24 Jul 2023 17:47:52 +0300 Subject: [PATCH] Add `translate` and `mapK` API to the tracing components --- .../otel4s/meta/InstrumentMeta.scala | 13 ++ .../org/typelevel/otel4s/trace/Span.scala | 59 ++++++++ .../typelevel/otel4s/trace/SpanBuilder.scala | 84 +++++++++++ .../org/typelevel/otel4s/trace/SpanOps.scala | 71 ++++++++++ .../org/typelevel/otel4s/trace/Tracer.scala | 95 +++++++------ .../otel4s/java/trace/TracerImpl.scala | 134 +----------------- 6 files changed, 283 insertions(+), 173 deletions(-) diff --git a/core/common/src/main/scala/org/typelevel/otel4s/meta/InstrumentMeta.scala b/core/common/src/main/scala/org/typelevel/otel4s/meta/InstrumentMeta.scala index 946d13076..a9cb81cbd 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/meta/InstrumentMeta.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/meta/InstrumentMeta.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.meta import cats.Applicative +import cats.~> trait InstrumentMeta[F[_]] { @@ -38,6 +39,18 @@ object InstrumentMeta { def disabled[F[_]: Applicative]: InstrumentMeta[F] = make(enabled = false) + implicit final class InstrumentMetaSyntax[F[_]]( + private val m: InstrumentMeta[F] + ) extends AnyVal { + + def mapK[G[_]](fk: F ~> G): InstrumentMeta[G] = + new InstrumentMeta[G] { + def isEnabled: Boolean = m.isEnabled + def unit: G[Unit] = fk(m.unit) + } + + } + private def make[F[_]: Applicative](enabled: Boolean): InstrumentMeta[F] = new InstrumentMeta[F] { val isEnabled: Boolean = enabled diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Span.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Span.scala index 2a1258526..80ccfd2c6 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Span.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Span.scala @@ -18,6 +18,9 @@ package org.typelevel.otel4s package trace import cats.Applicative +import cats.Functor +import cats.data.OptionT +import cats.~> import org.typelevel.otel4s.meta.InstrumentMeta import scala.concurrent.duration.FiniteDuration @@ -151,10 +154,66 @@ object Span { private[otel4s] def end: F[Unit] = unit private[otel4s] def end(timestamp: FiniteDuration): F[Unit] = unit } + + implicit final class BackendSyntax[F[_]]( + private val backend: Backend[F] + ) extends AnyVal { + + def mapK[G[_]](fk: F ~> G): Backend[G] = + new Backend[G] { + def meta: InstrumentMeta[G] = + backend.meta.mapK(fk) + + def context: SpanContext = + backend.context + + def addAttributes(attributes: Attribute[_]*): G[Unit] = + fk(backend.addAttributes(attributes: _*)) + + def addEvent(name: String, attributes: Attribute[_]*): G[Unit] = + fk(backend.addEvent(name, attributes: _*)) + + def addEvent( + name: String, + timestamp: FiniteDuration, + attributes: Attribute[_]* + ): G[Unit] = + fk(backend.addEvent(name, timestamp, attributes: _*)) + + def recordException( + exception: Throwable, + attributes: Attribute[_]* + ): G[Unit] = + fk(backend.recordException(exception, attributes: _*)) + + def setStatus(status: Status): G[Unit] = + fk(backend.setStatus(status)) + + def setStatus(status: Status, description: String): G[Unit] = + fk(backend.setStatus(status, description)) + + private[otel4s] def end: G[Unit] = + fk(backend.end) + + private[otel4s] def end(timestamp: FiniteDuration): G[Unit] = + fk(backend.end(timestamp)) + } + + } + } + + implicit final class SpanSyntax[F[_]]( + private val span: Span[F] + ) extends AnyVal { + + def mapK[G[_]](fk: F ~> G): Span[G] = + Span.fromBackend(span.backend.mapK(fk)) + } private[otel4s] def fromBackend[F[_]](back: Backend[F]): Span[F] = new Span[F] { def backend: Backend[F] = back } + } diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala index c93c669cf..df40352c3 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala @@ -19,7 +19,11 @@ package trace import cats.Applicative import cats.arrow.FunctionK +import cats.data.OptionT +import cats.effect.MonadCancel import cats.effect.Resource +import cats.~> +import org.typelevel.otel4s.trace.SpanFinalizer.Strategy import scala.concurrent.duration.FiniteDuration @@ -152,4 +156,84 @@ object SpanBuilder { } } + implicit final class SpanBuilderSyntax[F[_]]( + private val builder: SpanBuilder[F] + ) extends AnyVal { + + def translate[G[_]](fk: F ~> G, gk: G ~> F)(implicit + F: MonadCancel[F, _], + G: MonadCancel[G, _] + ): SpanBuilder[G] = + new SpanBuilder[G] { + + def addAttribute[A](attribute: Attribute[A]): SpanBuilder[G] = + builder.addAttribute(attribute).translate(fk, gk) + + def addAttributes(attributes: Attribute[_]*): SpanBuilder[G] = + builder.addAttributes(attributes: _*).translate(fk, gk) + + def addLink( + spanContext: SpanContext, + attributes: Attribute[_]* + ): SpanBuilder[G] = + builder.addLink(spanContext, attributes: _*).translate(fk, gk) + + def withFinalizationStrategy(strategy: Strategy): SpanBuilder[G] = + builder.withFinalizationStrategy(strategy).translate(fk, gk) + + def withSpanKind(spanKind: SpanKind): SpanBuilder[G] = + builder.withSpanKind(spanKind).translate(fk, gk) + + def withStartTimestamp(timestamp: FiniteDuration): SpanBuilder[G] = + builder.withStartTimestamp(timestamp).translate(fk, gk) + + def root: SpanBuilder[G] = + builder.root.translate(fk, gk) + + def withParent(parent: SpanContext): SpanBuilder[G] = + builder.withParent(parent).translate(fk, gk) + + def build: SpanOps[G] = + builder.build.translate(fk, gk) + } + + } + + def liftOptionT[F[_]](builder: SpanBuilder[F])(implicit + F: MonadCancel[F, _] + ): SpanBuilder[OptionT[F, *]] = + new SpanBuilder[OptionT[F, *]] { + type Builder = SpanBuilder[OptionT[F, *]] + + def addAttribute[A](attribute: Attribute[A]): Builder = + liftOptionT(builder.addAttribute(attribute)) + + def addAttributes(attributes: Attribute[_]*): Builder = + liftOptionT(builder.addAttributes(attributes: _*)) + + def addLink( + spanContext: SpanContext, + attributes: Attribute[_]* + ): Builder = + liftOptionT(builder.addLink(spanContext, attributes: _*)) + + def withFinalizationStrategy(strategy: SpanFinalizer.Strategy): Builder = + liftOptionT(builder.withFinalizationStrategy(strategy)) + + def withSpanKind(spanKind: SpanKind): Builder = + liftOptionT(builder.withSpanKind(spanKind)) + + def withStartTimestamp(timestamp: FiniteDuration): Builder = + liftOptionT(builder.withStartTimestamp(timestamp)) + + def root: Builder = + liftOptionT(builder.root) + + def withParent(parent: SpanContext): Builder = + liftOptionT(builder.withParent(parent)) + + def build: SpanOps[OptionT[F, *]] = + SpanOps.liftOptionT(builder.build) + } + } diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala index 712f7097a..b6ec1c459 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala @@ -16,7 +16,10 @@ package org.typelevel.otel4s.trace +import cats.data.OptionT +import cats.effect.MonadCancel import cats.effect.Resource +import cats.syntax.functor._ import cats.~> trait SpanOps[F[_]] { @@ -167,5 +170,73 @@ object SpanOps { */ def apply[F[_]](span: Span[F], trace: F ~> F): Res[F] = Impl(span, trace) + + implicit final class SpanOpsResSyntax[F[_]]( + private val res: Res[F] + ) extends AnyVal { + + def translate[G[_]](fk: F ~> G, gk: G ~> F): Res[G] = + new Res[G] { + def span: Span[G] = res.span.mapK(fk) + def trace: G ~> G = res.trace.andThen(fk).compose(gk) + } + + } + } + + implicit final class SpanOpsSyntax[F[_]]( + private val ops: SpanOps[F] + ) extends AnyVal { + + def translate[G[_]](fk: F ~> G, gk: G ~> F)(implicit + F: MonadCancel[F, _], + G: MonadCancel[G, _] + ): SpanOps[G] = + new SpanOps[G] { + def startUnmanaged: G[Span[G]] = + fk(ops.startUnmanaged).map(span => span.mapK(fk)) + + def resource: Resource[G, Res[G]] = + ops.resource.map(res => res.translate(fk, gk)).mapK(fk) + + def use[A](f: Span[G] => G[A]): G[A] = + fk(ops.use(span => gk(f(span.mapK(fk))))) + + def use_ : G[Unit] = + fk(ops.use_) + } + + } + + def liftOptionT[F[_]](ops: SpanOps[F])(implicit + F: MonadCancel[F, _] + ): SpanOps[OptionT[F, *]] = { + new SpanOps[OptionT[F, *]] { + + def startUnmanaged: OptionT[F, Span[OptionT[F, *]]] = + OptionT.liftF(ops.startUnmanaged).map(span => span.mapK(OptionT.liftK)) + + def resource: Resource[OptionT[F, *], Res[OptionT[F, *]]] = + ops.resource + .map(res => + new Res[OptionT[F, *]] { + def span: Span[OptionT[F, *]] = + res.span.mapK(OptionT.liftK) + + def trace: OptionT[F, *] ~> OptionT[F, *] = + new (OptionT[F, *] ~> OptionT[F, *]) { + def apply[A](fa: OptionT[F, A]): OptionT[F, A] = + OptionT(res.trace.apply(fa.value)) + } + } + ) + .mapK(OptionT.liftK) + + def use[A](f: Span[OptionT[F, *]] => OptionT[F, A]): OptionT[F, A] = + OptionT(ops.use(span => f(span.mapK(OptionT.liftK)).value)) + + def use_ : OptionT[F, Unit] = + OptionT.liftF(ops.use_) + } } } diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala index f9a10c559..d5ccae25a 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala @@ -18,10 +18,8 @@ package org.typelevel.otel4s package trace import cats.Applicative -import cats.effect.kernel.CancelScope -import cats.effect.kernel.MonadCancelThrow -import cats.effect.kernel.Poll -import cats.effect.kernel.Resource +import cats.data.OptionT +import cats.effect.kernel.MonadCancel import cats.~> import org.typelevel.otel4s.meta.InstrumentMeta @@ -175,8 +173,6 @@ trait Tracer[F[_]] extends TracerMacro[F] { */ def noopScope[A](fa: F[A]): F[A] - def translate[G[_]](fk: F ~> G, gk: G ~> F): Tracer[G] - } object Tracer { @@ -192,7 +188,7 @@ object Tracer { def enabled[F[_]: Applicative]: Meta[F] = make(true) def disabled[F[_]: Applicative]: Meta[F] = make(false) - private def make[F[_]: Applicative](enabled: Boolean): Meta[F] = + private[Tracer] def make[F[_]: Applicative](enabled: Boolean): Meta[F] = new Meta[F] { private val noopBackend = Span.Backend.noop[F] @@ -214,51 +210,70 @@ object Tracer { def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = fa def spanBuilder(name: String): SpanBuilder[F] = builder def joinOrRoot[A, C: TextMapGetter](carrier: C)(fa: F[A]): F[A] = fa - def translate[G[_]](fk: F ~> G, gk: G ~> F): Tracer[G] = - noop[G](liftMonadCancelThrow[F, G](MonadCancelThrow[F], fk, gk)) + } + + def liftOptionT[F[_]](tracer: Tracer[F])(implicit + F: MonadCancel[F, _] + ): Tracer[OptionT[F, *]] = + new Tracer[OptionT[F, *]] { + def meta: Meta[OptionT[F, *]] = + Meta.make(tracer.meta.isEnabled) + + def currentSpanContext: OptionT[F, Option[SpanContext]] = + OptionT.liftF(tracer.currentSpanContext) + + def spanBuilder(name: String): SpanBuilder[OptionT[F, *]] = + SpanBuilder.liftOptionT(tracer.spanBuilder(name)) + + def childScope[A](parent: SpanContext)(fa: OptionT[F, A]): OptionT[F, A] = + OptionT(tracer.childScope(parent)(fa.value)) + + def joinOrRoot[A, C: TextMapGetter](carrier: C)( + fa: OptionT[F, A] + ): OptionT[F, A] = + OptionT(tracer.joinOrRoot(carrier)(fa.value)) + + def rootScope[A](fa: OptionT[F, A]): OptionT[F, A] = + OptionT(tracer.rootScope(fa.value)) + + def noopScope[A](fa: OptionT[F, A]): OptionT[F, A] = + OptionT(tracer.noopScope(fa.value)) } object Implicits { implicit def noop[F[_]: Applicative]: Tracer[F] = Tracer.noop } - private def liftMonadCancelThrow[F[_], G[_]]( - F: MonadCancelThrow[F], - fk: F ~> G, - gk: G ~> F - ): MonadCancelThrow[G] = - new MonadCancelThrow[G] { - def pure[A](x: A): G[A] = fk(F.pure(x)) - - // Members declared in cats.ApplicativeError - def handleErrorWith[A](ga: G[A])(f: Throwable => G[A]): G[A] = - fk(F.handleErrorWith(gk(ga))(ex => gk(f(ex)))) + implicit final class TracerSyntax[F[_]]( + private val tracer: Tracer[F] + ) extends AnyVal { - def raiseError[A](e: Throwable): G[A] = fk(F.raiseError[A](e)) + def translate[G[_]](fk: F ~> G, gk: G ~> F)(implicit + F: MonadCancel[F, _], + G: MonadCancel[G, _] + ): Tracer[G] = + new Tracer[G] { + def meta: Meta[G] = + Meta.make(tracer.meta.isEnabled) - // Members declared in cats.FlatMap - def flatMap[A, B](ga: G[A])(f: A => G[B]): G[B] = - fk(F.flatMap(gk(ga))(a => gk(f(a)))) + def currentSpanContext: G[Option[SpanContext]] = + fk(tracer.currentSpanContext) - def tailRecM[A, B](a: A)(f: A => G[Either[A, B]]): G[B] = - fk(F.tailRecM(a)(a => gk(f(a)))) + def spanBuilder(name: String): SpanBuilder[G] = + tracer.spanBuilder(name).translate(fk, gk) - // Members declared in cats.effect.kernel.MonadCancel - def canceled: G[Unit] = fk(F.canceled) + def childScope[A](parent: SpanContext)(fa: G[A]): G[A] = + fk(tracer.childScope(parent)(gk(fa))) - def forceR[A, B](ga: G[A])(gb: G[B]): G[B] = - fk(F.forceR(gk(ga))(gk(gb))) + def joinOrRoot[A, C: TextMapGetter](carrier: C)(fa: G[A]): G[A] = + fk(tracer.joinOrRoot(carrier)(gk(fa))) - def onCancel[A](ga: G[A], fin: G[Unit]): G[A] = - fk(F.onCancel(gk(ga), gk(fin))) + def rootScope[A](fa: G[A]): G[A] = + fk(tracer.rootScope(gk(fa))) - def rootCancelScope: CancelScope = F.rootCancelScope + def noopScope[A](fa: G[A]): G[A] = + fk(tracer.noopScope(gk(fa))) + } - def uncancelable[A](body: Poll[G] => G[A]): G[A] = - fk(F.uncancelable { pollF => - gk(body(new Poll[G] { - def apply[B](gb: G[B]): G[B] = fk(pollF(gk(gb))) - })) - }) - } + } } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala index b01638140..1db3b4c8a 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala @@ -16,25 +16,18 @@ package org.typelevel.otel4s.java.trace -import cats.effect.kernel.CancelScope -import cats.effect.kernel.MonadCancelThrow -import cats.effect.kernel.Poll -import cats.effect.kernel.Sync +import cats.effect.Sync import cats.syntax.flatMap._ import cats.syntax.functor._ -import cats.~> import io.opentelemetry.api.trace.{Span => JSpan} import io.opentelemetry.api.trace.{Tracer => JTracer} import org.typelevel.otel4s.ContextPropagators import org.typelevel.otel4s.TextMapGetter import org.typelevel.otel4s.trace.SpanBuilder -import org.typelevel.otel4s.trace.SpanBuilder.Aux import org.typelevel.otel4s.trace.SpanContext import org.typelevel.otel4s.trace.Tracer import org.typelevel.vault.Vault -import scala.concurrent.duration.FiniteDuration - private[java] class TracerImpl[F[_]: Sync]( jTracer: JTracer, scope: TraceScope[F], @@ -79,129 +72,4 @@ private[java] class TracerImpl[F[_]: Sync]( rootScope(fa) } } - - def translate[G[_]](fk: F ~> G, gk: G ~> F): Tracer[G] = - new Tracer[G] { - private implicit val G: Sync[G] = - TracerImpl.liftSync[F, G](Sync[F], fk, gk) - - private val traceScope: TraceScope[G] = - new TraceScope[G] { - def root: G[Scope.Root] = fk(scope.root) - - def current: G[Scope] = fk(scope.current) - - def makeScope(span: JSpan): G[G ~> G] = - fk(scope.makeScope(span)).map { transform => - new (G ~> G) { - def apply[A](fa: G[A]): G[A] = - fk(transform.apply(gk(fa))) - } - } - - def rootScope: G[G ~> G] = - fk(scope.rootScope).map { transform => - new (G ~> G) { - def apply[A](fa: G[A]): G[A] = - fk(transform.apply(gk(fa))) - } - } - - def noopScope: G ~> G = - new (G ~> G) { - def apply[A](fa: G[A]): G[A] = - fk(scope.noopScope(gk(fa))) - } - } - - private val spanRunner: SpanRunner[G, Span[G]] = - SpanRunner.span(traceScope) - - val meta: Tracer.Meta[G] = - new Tracer.Meta[G] { - val noopSpanBuilder: SpanBuilder.Aux[G, Span[G]] = - SpanBuilder.noop(Span.Backend.noop[G]) - val isEnabled: Boolean = self.meta.isEnabled - val unit: G[Unit] = MonadCancelThrow[G].unit - } - - def currentSpanContext: G[Option[SpanContext]] = - fk(self.currentSpanContext) - - def spanBuilder(name: String): Aux[G, Span[G]] = - new SpanBuilderImpl[G, Span[G]]( - jTracer, - name, - traceScope, - spanRunner - ) - - def childScope[A](parent: SpanContext)(fa: G[A]): G[A] = - fk(self.childScope(parent)(gk(fa))) - - def joinOrRoot[A, C: TextMapGetter](carrier: C)(fa: G[A]): G[A] = - fk(self.joinOrRoot(carrier)(gk(fa))) - - def rootScope[A](fa: G[A]): G[A] = - fk(self.rootScope(gk(fa))) - - def noopScope[A](fa: G[A]): G[A] = - fk(self.noopScope(gk(fa))) - - def translate[Q[_]](fk1: G ~> Q, gk1: Q ~> G): Tracer[Q] = - self.translate[Q](fk.andThen(fk1), gk1.andThen(gk)) - } - -} - -object TracerImpl { - - private def liftSync[F[_], G[_]]( - F: Sync[F], - fk: F ~> G, - gk: G ~> F - ): Sync[G] = - new Sync[G] { - def pure[A](x: A): G[A] = fk(F.pure(x)) - - // Members declared in cats.ApplicativeError - def handleErrorWith[A](ga: G[A])(f: Throwable => G[A]): G[A] = - fk(F.handleErrorWith(gk(ga))(ex => gk(f(ex)))) - - def raiseError[A](e: Throwable): G[A] = fk(F.raiseError[A](e)) - - // Members declared in cats.FlatMap - def flatMap[A, B](ga: G[A])(f: A => G[B]): G[B] = - fk(F.flatMap(gk(ga))(a => gk(f(a)))) - - def tailRecM[A, B](a: A)(f: A => G[Either[A, B]]): G[B] = - fk(F.tailRecM(a)(a => gk(f(a)))) - - // Members declared in cats.effect.kernel.MonadCancel - def canceled: G[Unit] = fk(F.canceled) - - def forceR[A, B](ga: G[A])(gb: G[B]): G[B] = - fk(F.forceR(gk(ga))(gk(gb))) - - def onCancel[A](ga: G[A], fin: G[Unit]): G[A] = - fk(F.onCancel(gk(ga), gk(fin))) - - def rootCancelScope: CancelScope = F.rootCancelScope - - def uncancelable[A](body: Poll[G] => G[A]): G[A] = - fk(F.uncancelable { pollF => - gk(body(new Poll[G] { - def apply[B](gb: G[B]): G[B] = fk(pollF(gk(gb))) - })) - }) - - def suspend[A](hint: Sync.Type)(thunk: => A): G[A] = - fk(F.suspend(hint)(thunk)) - - def monotonic: G[FiniteDuration] = - fk(F.monotonic) - - def realTime: G[FiniteDuration] = - fk(F.realTime) - } }