diff --git a/core/trace/src/main/scala-2/org/typelevel/otel4s/trace/TracerMacro.scala b/core/trace/src/main/scala-2/org/typelevel/otel4s/trace/TracerMacro.scala index 43f930c2b..eda45282f 100644 --- a/core/trace/src/main/scala-2/org/typelevel/otel4s/trace/TracerMacro.scala +++ b/core/trace/src/main/scala-2/org/typelevel/otel4s/trace/TracerMacro.scala @@ -17,8 +17,6 @@ package org.typelevel.otel4s package trace -import cats.effect.kernel.Resource - private[otel4s] trait TracerMacro[F[_]] { self: Tracer[F] => @@ -39,7 +37,7 @@ private[otel4s] trait TracerMacro[F[_]] { * {{{ * val tracer: Tracer[F] = ??? * val span: Span[F] = ??? - * val customParent: SpanOps.Aux[F, Span[F]] = tracer + * val customParent: SpanOps[F] = tracer * .spanBuilder("custom-parent") * .withParent(span.context) * .build @@ -54,7 +52,7 @@ private[otel4s] trait TracerMacro[F[_]] { * @param attributes * the set of attributes to associate with the span */ - def span(name: String, attributes: Attribute[_]*): SpanOps.Aux[F, Span[F]] = + def span(name: String, attributes: Attribute[_]*): SpanOps[F] = macro TracerMacro.span /** Creates a new root span. Even if a parent span is available in the scope, @@ -72,39 +70,8 @@ private[otel4s] trait TracerMacro[F[_]] { * @param attributes * the set of attributes to associate with the span */ - def rootSpan( - name: String, - attributes: Attribute[_]* - ): SpanOps.Aux[F, Span[F]] = + def rootSpan(name: String, attributes: Attribute[_]*): SpanOps[F] = macro TracerMacro.rootSpan - - /** Creates a new child span. The span is automatically attached to a parent - * span (based on the scope). - * - * The lifecycle of the span is managed automatically. That means the span is - * ended upon the finalization of a resource. - * - * The abnormal termination (error, cancelation) is recorded by - * [[SpanFinalizer.Strategy.reportAbnormal default finalization strategy]]. - * - * The structure of the inner spans: - * {{{ - * > name - * > acquire - * > use - * > release - * }}} - * - * @param name - * the name of the span - * - * @param attributes - * the set of attributes to associate with the span - */ - def resourceSpan[A](name: String, attributes: Attribute[_]*)( - resource: Resource[F, A] - ): SpanOps.Aux[F, Span.Res[F, A]] = - macro TracerMacro.resourceSpan[F, A] } object TracerMacro { @@ -128,14 +95,4 @@ object TracerMacro { q"(if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).root.addAttributes(..$attributes) else $meta.noopSpanBuilder).build" } - def resourceSpan[F[_], A](c: blackbox.Context)( - name: c.Expr[String], - attributes: c.Expr[Attribute[_]]* - )(resource: c.Expr[Resource[F, A]]): c.universe.Tree = { - import c.universe._ - val meta = q"${c.prefix}.meta" - - q"if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).addAttributes(..$attributes).wrapResource($resource).build else $meta.noopResSpan($resource).build" - } - } diff --git a/core/trace/src/main/scala-3/org/typelevel/otel4s/trace/TracerMacro.scala b/core/trace/src/main/scala-3/org/typelevel/otel4s/trace/TracerMacro.scala index 3e0ec38bb..ee46d22ad 100644 --- a/core/trace/src/main/scala-3/org/typelevel/otel4s/trace/TracerMacro.scala +++ b/core/trace/src/main/scala-3/org/typelevel/otel4s/trace/TracerMacro.scala @@ -17,8 +17,6 @@ package org.typelevel.otel4s package trace -import cats.effect.kernel.Resource - import scala.quoted.* private[otel4s] trait TracerMacro[F[_]] { @@ -41,7 +39,7 @@ private[otel4s] trait TracerMacro[F[_]] { * {{{ * val tracer: Tracer[F] = ??? * val span: Span[F] = ??? - * val customParent: SpanOps.Aux[F, Span[F]] = tracer + * val customParent: SpanOps[F] = tracer * .spanBuilder("custom-parent") * .withParent(span.context) * .build @@ -59,7 +57,7 @@ private[otel4s] trait TracerMacro[F[_]] { inline def span( inline name: String, inline attributes: Attribute[_]* - ): SpanOps.Aux[F, Span[F]] = + ): SpanOps[F] = ${ TracerMacro.span('self, 'name, 'attributes) } /** Creates a new root span. Even if a parent span is available in the scope, @@ -80,38 +78,9 @@ private[otel4s] trait TracerMacro[F[_]] { inline def rootSpan( inline name: String, inline attributes: Attribute[_]* - ): SpanOps.Aux[F, Span[F]] = + ): SpanOps[F] = ${ TracerMacro.rootSpan('self, 'name, 'attributes) } - /** Creates a new child span. The span is automatically attached to a parent - * span (based on the scope). - * - * The lifecycle of the span is managed automatically. That means the span is - * ended upon the finalization of a resource. - * - * The abnormal termination (error, cancelation) is recorded by - * [[SpanFinalizer.Strategy.reportAbnormal default finalization strategy]]. - * - * The structure of the inner spans: - * {{{ - * > name - * > acquire - * > use - * > release - * }}} - * - * @param name - * the name of the span - * - * @param attributes - * the set of attributes to associate with the span - */ - inline def resourceSpan[A]( - inline name: String, - inline attributes: Attribute[_]* - )(inline resource: Resource[F, A]): SpanOps.Aux[F, Span.Res[F, A]] = - ${ TracerMacro.resourceSpan('self, 'name, 'attributes, 'resource) } - } object TracerMacro { @@ -138,20 +107,4 @@ object TracerMacro { else $tracer.meta.noopSpanBuilder.build } - def resourceSpan[F[_], A]( - tracer: Expr[Tracer[F]], - name: Expr[String], - attributes: Expr[Seq[Attribute[_]]], - resource: Expr[Resource[F, A]] - )(using Quotes, Type[F], Type[A]) = - '{ - if ($tracer.meta.isEnabled) - $tracer - .spanBuilder($name) - .addAttributes($attributes*) - .wrapResource($resource) - .build - else $tracer.meta.noopResSpan($resource).build - } - } 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 991f53e6a..2a1258526 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 @@ -157,33 +157,4 @@ object Span { new Span[F] { def backend: Backend[F] = back } - - /** The allocation and release stages of a supplied resource are traced by - * separate spans. Carries a value of a wrapped resource. - * - * The structure of the inner spans: - * {{{ - * > span-name - * > acquire - * > use - * > release - * }}} - */ - trait Res[F[_], A] extends Span[F] { - def value: A - } - - object Res { - def unapply[F[_], A](span: Span.Res[F, A]): Option[A] = - Some(span.value) - - private[otel4s] def fromBackend[F[_], A]( - a: A, - back: Backend[F] - ): Res[F, A] = - new Res[F, A] { - def value: A = a - 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 3e47b32f4..eabae2494 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 @@ -18,14 +18,13 @@ package org.typelevel.otel4s package trace import cats.Applicative -import cats.effect.kernel.MonadCancelThrow -import cats.effect.kernel.Resource +import cats.arrow.FunctionK +import cats.effect.MonadCancelThrow +import cats.effect.Resource import scala.concurrent.duration.FiniteDuration trait SpanBuilder[F[_]] { - type Result <: Span[F] - type Builder = SpanBuilder.Aux[F, Result] /** Adds an attribute to the newly created span. If [[SpanBuilder]] previously * contained a mapping for the key, the old value is replaced by the @@ -34,7 +33,7 @@ trait SpanBuilder[F[_]] { * @param attribute * the attribute to associate with the span */ - def addAttribute[A](attribute: Attribute[A]): Builder + def addAttribute[A](attribute: Attribute[A]): SpanBuilder[F] /** Adds attributes to the [[SpanBuilder]]. If the SpanBuilder previously * contained a mapping for any of the keys, the old values are replaced by @@ -43,7 +42,7 @@ trait SpanBuilder[F[_]] { * @param attributes * the set of attributes to associate with the span */ - def addAttributes(attributes: Attribute[_]*): Builder + def addAttributes(attributes: Attribute[_]*): SpanBuilder[F] /** Adds a link to the newly created span. * @@ -57,7 +56,10 @@ trait SpanBuilder[F[_]] { * @param attributes * the set of attributes to associate with the link */ - def addLink(spanContext: SpanContext, attributes: Attribute[_]*): Builder + def addLink( + spanContext: SpanContext, + attributes: Attribute[_]* + ): SpanBuilder[F] /** Sets the finalization strategy for the newly created span. * @@ -68,7 +70,7 @@ trait SpanBuilder[F[_]] { * @param strategy * the strategy to apply upon span finalization */ - def withFinalizationStrategy(strategy: SpanFinalizer.Strategy): Builder + def withFinalizationStrategy(strategy: SpanFinalizer.Strategy): SpanBuilder[F] /** Sets the [[SpanKind]] for the newly created span. If not called, the * implementation will provide a default value [[SpanKind.Internal]]. @@ -76,7 +78,7 @@ trait SpanBuilder[F[_]] { * @param spanKind * the kind of the newly created span */ - def withSpanKind(spanKind: SpanKind): Builder + def withSpanKind(spanKind: SpanKind): SpanBuilder[F] /** Sets an explicit start timestamp for the newly created span. * @@ -90,12 +92,12 @@ trait SpanBuilder[F[_]] { * @param timestamp * the explicit start timestamp from the epoch */ - def withStartTimestamp(timestamp: FiniteDuration): Builder + def withStartTimestamp(timestamp: FiniteDuration): SpanBuilder[F] /** Indicates that the span should be the root one and the scope parent should * be ignored. */ - def root: Builder + def root: SpanBuilder[F] /** Sets the parent to use from the specified [[SpanContext]]. If not set, the * span that is currently available in the scope will be used as parent. @@ -108,105 +110,46 @@ trait SpanBuilder[F[_]] { * @param parent * the span context to use as a parent */ - def withParent(parent: SpanContext): Builder + def withParent(parent: SpanContext): SpanBuilder[F] - /** Wraps the given resource to trace it upon the start. - * - * The span is started upon resource allocation and ended upon finalization. - * The allocation and release stages of the `resource` are traced by separate - * spans. Carries a value of the given `resource`. - * - * The structure of the inner spans: - * {{{ - * > span-name - * > acquire - * > use - * > release - * }}} - * - * The finalization strategy is determined by [[SpanFinalizer.Strategy]]. By - * default, the abnormal termination (error, cancelation) is recorded. - * - * @see - * default finalization strategy [[SpanFinalizer.Strategy.reportAbnormal]] - * @example - * {{{ - * val tracer: Tracer[F] = ??? - * val resource: Resource[F, String] = Resource.eval(Sync[F].delay("string")) - * val ok: F[Unit] = - * tracer.spanBuilder("wrapped-resource").wrapResource(resource).build.use { case span @ Span.Res(value) => - * span.setStatus(Status.Ok, s"all good. resource value: $${value}") - * } - * }}} - * @param resource - * the resource to trace - */ - def wrapResource[A]( - resource: Resource[F, A] - )(implicit ev: Result =:= Span[F]): SpanBuilder.Aux[F, Span.Res[F, A]] - - def build: SpanOps.Aux[F, Result] + def build: SpanOps[F] } object SpanBuilder { - type Aux[F[_], A] = SpanBuilder[F] { - type Result = A - } - - def noop[F[_]: MonadCancelThrow]( - back: Span.Backend[F] - ): SpanBuilder.Aux[F, Span[F]] = - make(back, Resource.pure(Span.fromBackend(back))) - - private def make[F[_]: MonadCancelThrow, Res <: Span[F]]( - back: Span.Backend[F], - startSpan: Resource[F, Res] - ): SpanBuilder.Aux[F, Res] = + def noop[F[_]: MonadCancelThrow](back: Span.Backend[F]): SpanBuilder[F] = new SpanBuilder[F] { - type Result = Res - private val span: Span[F] = Span.fromBackend(back) - def wrapResource[A]( - resource: Resource[F, A] - )(implicit ev: Result =:= Span[F]): SpanBuilder.Aux[F, Span.Res[F, A]] = - make( - back, - resource.map(r => Span.Res.fromBackend(r, back)) - ) - - def addAttribute[A](attribute: Attribute[A]): Builder = this + def addAttribute[A](attribute: Attribute[A]): SpanBuilder[F] = this - def addAttributes(attributes: Attribute[_]*): Builder = this + def addAttributes(attributes: Attribute[_]*): SpanBuilder[F] = this - def addLink(ctx: SpanContext, attributes: Attribute[_]*): Builder = this - - def root: Builder = this - - def withFinalizationStrategy(strategy: SpanFinalizer.Strategy): Builder = + def addLink(ctx: SpanContext, attributes: Attribute[_]*): SpanBuilder[F] = this - def withParent(parent: SpanContext): Builder = this + def root: SpanBuilder[F] = this + + def withFinalizationStrategy( + strategy: SpanFinalizer.Strategy + ): SpanBuilder[F] = this - def withSpanKind(spanKind: SpanKind): Builder = this + def withParent(parent: SpanContext): SpanBuilder[F] = this - def withStartTimestamp(timestamp: FiniteDuration): Builder = this + def withSpanKind(spanKind: SpanKind): SpanBuilder[F] = this - def build: SpanOps.Aux[F, Result] = new SpanOps[F] { - type Result = Res + def withStartTimestamp(timestamp: FiniteDuration): SpanBuilder[F] = this - def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]] = + def build: SpanOps[F] = new SpanOps[F] { + def startUnmanaged: F[Span[F]] = Applicative[F].pure(span) - def use[A](f: Res => F[A]): F[A] = - startSpan.use(res => f(res)) + def resource: Resource[F, SpanOps.Res[F]] = + Resource.pure(SpanOps.Res(span, FunctionK.id)) - def use_ : F[Unit] = - startSpan.use_ + def use[A](f: Span[F] => F[A]): F[A] = f(span) - def surround[A](fa: F[A]): F[A] = - fa + override def use_ : F[Unit] = Applicative[F].unit } } 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 a06d8c603..a603ce2a1 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,8 +16,10 @@ package org.typelevel.otel4s.trace +import cats.effect.Resource +import cats.~> + trait SpanOps[F[_]] { - type Result <: Span[F] /** Creates a [[Span]]. The span requires to be ended ''explicitly'' by * invoking `end`. @@ -47,9 +49,41 @@ trait SpanOps[F[_]] { * }}} * * @see - * [[use]], [[use_]], or [[surround]] for a managed lifecycle + * [[use]], [[use_]], [[surround]], or [[resource]] for a managed lifecycle */ - def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]] + def startUnmanaged: F[Span[F]] + + /** Creates a [[Span]] and a [[cats.effect.Resource Resource]] for using it. + * Unlike [[startUnmanaged]], the lifecycle of the span is fully managed. + * + * The finalization strategy is determined by [[SpanFinalizer.Strategy]]. By + * default, the abnormal termination (error, cancelation) is recorded. + * + * If the start timestamp is not configured explicitly in a builder, the + * `Clock[F].realTime` is used to determine the timestamp. + * + * `Clock[F].realTime` is always used as the end timestamp. + * + * @see + * default finalization strategy [[SpanFinalizer.Strategy.reportAbnormal]] + * @see + * [[SpanOps.Res]] for the semantics and usage of the resource's value + * @example + * {{{ + * val tracer: Tracer[F] = ??? + * val ok: F[Unit] = + * tracer.spanBuilder("resource-span") + * .build + * .resource + * .use { res => + * // `res.include` encloses its contents within the "resource-span" + * // span; anything not applied to `res.include` will not end up in + * // the span + * res.include(res.span.setStatus(Status.Ok, "all good")) + * } + * }}} + */ + def resource: Resource[F, SpanOps.Res[F]] /** Creates and uses a [[Span]]. Unlike [[startUnmanaged]], the lifecycle of * the span is fully managed. The span is started and passed to `f` to @@ -74,7 +108,7 @@ trait SpanOps[F[_]] { * } * }}} */ - def use[A](f: Result => F[A]): F[A] + def use[A](f: Span[F] => F[A]): F[A] /** Starts a span and ends it immediately. * @@ -103,11 +137,35 @@ trait SpanOps[F[_]] { * @see * See [[use]] for more details regarding lifecycle strategy */ - def surround[A](fa: F[A]): F[A] + final def surround[A](fa: F[A]): F[A] = use(_ => fa) } object SpanOps { - type Aux[F[_], A] = SpanOps[F] { - type Result = A + + /** The span and associated natural transformation [[`trace`]] provided and + * managed by a call to [[SpanOps.resource]]. In order to trace something in + * the span, it must be applied to [[`trace`]]. + */ + sealed trait Res[F[_]] { + + /** The managed span. */ + def span: Span[F] + + /** A natural transformation that traces everything applied to it in the + * span. Note: anything not applied to this + * [[cats.arrow.FunctionK FunctionK]] will not be traced. + */ + def trace: F ~> F + } + + object Res { + private[this] final case class Impl[F[_]](span: Span[F], trace: F ~> F) + extends Res[F] + + /** Creates a [[Res]] from a managed span and a natural transformation for + * tracing operations in the span. + */ + def apply[F[_]](span: Span[F], trace: F ~> F): Res[F] = + Impl(span, trace) } } 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 c800a627e..536a8dc00 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,8 +18,7 @@ package org.typelevel.otel4s package trace import cats.Applicative -import cats.effect.kernel.MonadCancelThrow -import cats.effect.kernel.Resource +import cats.effect.MonadCancelThrow import org.typelevel.otel4s.meta.InstrumentMeta @annotation.implicitNotFound(""" @@ -53,7 +52,7 @@ trait Tracer[F[_]] extends TracerMacro[F] { * @param name * the name of the span */ - def spanBuilder(name: String): SpanBuilder.Aux[F, Span[F]] + def spanBuilder(name: String): SpanBuilder[F] /** Creates a new tracing scope with a custom parent. A newly created non-root * span will be a child of the given `parent`. @@ -179,11 +178,7 @@ object Tracer { def apply[F[_]](implicit ev: Tracer[F]): Tracer[F] = ev trait Meta[F[_]] extends InstrumentMeta[F] { - def noopSpanBuilder: SpanBuilder.Aux[F, Span[F]] - final def noopResSpan[A]( - resource: Resource[F, A] - ): SpanBuilder.Aux[F, Span.Res[F, A]] = - noopSpanBuilder.wrapResource(resource) + def noopSpanBuilder: SpanBuilder[F] } object Meta { @@ -197,7 +192,7 @@ object Tracer { val isEnabled: Boolean = enabled val unit: F[Unit] = Applicative[F].unit - val noopSpanBuilder: SpanBuilder.Aux[F, Span[F]] = + val noopSpanBuilder: SpanBuilder[F] = SpanBuilder.noop(noopBackend) } } @@ -211,7 +206,7 @@ object Tracer { def rootScope[A](fa: F[A]): F[A] = fa def noopScope[A](fa: F[A]): F[A] = fa def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = fa - def spanBuilder(name: String): SpanBuilder.Aux[F, Span[F]] = builder + def spanBuilder(name: String): SpanBuilder[F] = builder def joinOrRoot[A, C: TextMapGetter](carrier: C)(fa: F[A]): F[A] = fa } diff --git a/examples/src/main/scala/TraceExample.scala b/examples/src/main/scala/TraceExample.scala index 41c366d68..44985aefd 100644 --- a/examples/src/main/scala/TraceExample.scala +++ b/examples/src/main/scala/TraceExample.scala @@ -16,7 +16,6 @@ import cats.effect.IO import cats.effect.IOApp -import cats.effect.Resource import cats.effect.Temporal import cats.effect.implicits._ import cats.implicits._ @@ -133,17 +132,22 @@ object TraceExample extends IOApp.Simple { InstitutionServiceClient.apply[IO], UserDatabase.apply[IO] ) - val resource: Resource[IO, Unit] = - Resource.make(IO.sleep(50.millis))(_ => IO.sleep(100.millis)) tracer - .resourceSpan("Start up")(resource) - .surround( - userIdAlg - .getAllUsersForInstitution( - "9902181e-1d8d-4e00-913d-51532b493f1b" - ) - .flatMap(IO.println) - ) + .span("Start up") + .use { span => + for { + _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) + _ <- span.addEvent("event") + _ <- tracer.span("use").surround { + userIdAlg + .getAllUsersForInstitution( + "9902181e-1d8d-4e00-913d-51532b493f1b" + ) + .flatMap(IO.println) + } + _ <- tracer.span("release").surround(IO.sleep(100.millis)) + } yield () + } } } } diff --git a/examples/src/main/scala/TracingExample.scala b/examples/src/main/scala/TracingExample.scala index 27ffce992..5ba58429d 100644 --- a/examples/src/main/scala/TracingExample.scala +++ b/examples/src/main/scala/TracingExample.scala @@ -17,7 +17,6 @@ import cats.Monad import cats.effect.IO import cats.effect.IOApp -import cats.effect.Resource import cats.effect.std.Console import cats.syntax.all._ import org.typelevel.otel4s.Otel4s @@ -62,20 +61,28 @@ object TracingExample extends IOApp.Simple { OtelJava.global.flatMap { (otel4s: Otel4s[IO]) => otel4s.tracerProvider.tracer("example").get.flatMap { implicit tracer: Tracer[IO] => - val resource: Resource[IO, Unit] = - Resource.make(IO.sleep(50.millis))(_ => IO.sleep(100.millis)) tracer - .resourceSpan("resource")(resource) - .surround( - Work[IO].request( - Map( - "X-B3-TraceId" -> "80f198ee56343ba864fe8b2a57d3eff7", - "X-B3-ParentSpanId" -> "05e3ac9a4f6e3b90", - "X-B3-SpanId" -> "e457b5a2e4d86bd1", - "X-B3-Sampled" -> "1" - ) - ) - ) + .span("resource") + .resource + .use { res => + res.trace { + for { + _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) + _ <- tracer.span("use").surround { + Work[IO].request( + Map( + "X-B3-TraceId" -> "80f198ee56343ba864fe8b2a57d3eff7", + "X-B3-ParentSpanId" -> "05e3ac9a4f6e3b90", + "X-B3-SpanId" -> "e457b5a2e4d86bd1", + "X-B3-Sampled" -> "1" + ) + ) + } + _ <- res.span.addEvent("event") + _ <- tracer.span("release").surround(IO.sleep(100.millis)) + } yield () + } + } } } } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala index 232485996..5aaa5b816 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala @@ -21,7 +21,6 @@ import cats.effect.Resource 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.{SpanBuilder => JSpanBuilder} import io.opentelemetry.api.trace.{SpanKind => JSpanKind} @@ -37,11 +36,11 @@ import org.typelevel.otel4s.trace.SpanOps import scala.concurrent.duration.FiniteDuration -private[java] final case class SpanBuilderImpl[F[_]: Sync, Res <: Span[F]]( +private[java] final case class SpanBuilderImpl[F[_]: Sync]( jTracer: JTracer, name: String, scope: TraceScope[F], - runner: SpanRunner[F, Res], + runner: SpanRunner[F], parent: SpanBuilderImpl.Parent = SpanBuilderImpl.Parent.Propagate, finalizationStrategy: SpanFinalizer.Strategy = SpanFinalizer.Strategy.reportAbnormal, @@ -50,56 +49,48 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync, Res <: Span[F]]( attributes: Seq[Attribute[_]] = Nil, startTimestamp: Option[FiniteDuration] = None ) extends SpanBuilder[F] { - type Result = Res - import SpanBuilderImpl._ - def withSpanKind(spanKind: SpanKind): Builder = + def withSpanKind(spanKind: SpanKind): SpanBuilder[F] = copy(kind = Some(spanKind)) - def addAttribute[A](attribute: Attribute[A]): Builder = + def addAttribute[A](attribute: Attribute[A]): SpanBuilder[F] = copy(attributes = attributes :+ attribute) - def addAttributes(attributes: Attribute[_]*): Builder = + def addAttributes(attributes: Attribute[_]*): SpanBuilder[F] = copy(attributes = this.attributes ++ attributes) - def addLink(spanContext: SpanContext, attributes: Attribute[_]*): Builder = + def addLink( + spanContext: SpanContext, + attributes: Attribute[_]* + ): SpanBuilder[F] = copy(links = links :+ (spanContext, attributes)) - def root: Builder = + def root: SpanBuilder[F] = copy(parent = Parent.Root) - def withParent(parent: SpanContext): Builder = + def withParent(parent: SpanContext): SpanBuilder[F] = copy(parent = Parent.Explicit(parent)) - def withStartTimestamp(timestamp: FiniteDuration): Builder = + def withStartTimestamp(timestamp: FiniteDuration): SpanBuilder[F] = copy(startTimestamp = Some(timestamp)) - def withFinalizationStrategy(strategy: SpanFinalizer.Strategy): Builder = + def withFinalizationStrategy( + strategy: SpanFinalizer.Strategy + ): SpanBuilder[F] = copy(finalizationStrategy = strategy) - def wrapResource[A]( - resource: Resource[F, A] - )(implicit ev: Result =:= Span[F]): SpanBuilder.Aux[F, Span.Res[F, A]] = - copy(runner = SpanRunner.resource(scope, resource, jTracer)) - - def build: SpanOps.Aux[F, Result] = new SpanOps[F] { - type Result = Res - - def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]] = + def build: SpanOps[F] = new SpanOps[F] { + def startUnmanaged: F[Span[F]] = runnerContext.flatMap(ctx => SpanRunner.startUnmanaged(ctx)) - def use[A](f: Result => F[A]): F[A] = - start.use { case (span, nt) => nt(f(span)) } - - def use_ : F[Unit] = - start.use_ + def resource: Resource[F, SpanOps.Res[F]] = + Resource.eval(runnerContext).flatMap(ctx => runner.start(ctx)) - def surround[A](fa: F[A]): F[A] = - use(_ => fa) + override def use[A](f: Span[F] => F[A]): F[A] = + resource.use { res => res.trace(f(res.span)) } - private def start: Resource[F, (Result, F ~> F)] = - Resource.eval(runnerContext).flatMap(ctx => runner.start(ctx)) + override def use_ : F[Unit] = use(_ => Sync[F].unit) } private[trace] def makeJBuilder(parent: JContext): JSpanBuilder = { diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala index 20f8f0824..d009e3d29 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala @@ -25,13 +25,13 @@ import cats.syntax.foldable._ import cats.syntax.functor._ import cats.~> import io.opentelemetry.api.trace.{SpanBuilder => JSpanBuilder} -import io.opentelemetry.api.trace.{Tracer => JTracer} import io.opentelemetry.context.{Context => JContext} import org.typelevel.otel4s.trace.Span import org.typelevel.otel4s.trace.SpanFinalizer +import org.typelevel.otel4s.trace.SpanOps -private[java] sealed trait SpanRunner[F[_], Res <: Span[F]] { - def start(ctx: Option[SpanRunner.RunnerContext]): Resource[F, (Res, F ~> F)] +private[java] sealed trait SpanRunner[F[_]] { + def start(ctx: Option[SpanRunner.RunnerContext]): Resource[F, SpanOps.Res[F]] } private[java] object SpanRunner { @@ -43,9 +43,9 @@ private[java] object SpanRunner { finalizationStrategy: SpanFinalizer.Strategy ) - def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F, Span[F]] = - new SpanRunner[F, Span[F]] { - def start(ctx: Option[RunnerContext]): Resource[F, (Span[F], F ~> F)] = { + def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F] = + new SpanRunner[F] { + def start(ctx: Option[RunnerContext]): Resource[F, SpanOps.Res[F]] = { ctx match { case Some(RunnerContext(builder, _, hasStartTs, finalization)) => startManaged( @@ -53,63 +53,14 @@ private[java] object SpanRunner { hasStartTimestamp = hasStartTs, finalizationStrategy = finalization, scope = scope - ).map { case (back, nt) => (Span.fromBackend(back), nt) } + ).map { case (back, nt) => SpanOps.Res(Span.fromBackend(back), nt) } case None => - Resource.pure((Span.fromBackend(Span.Backend.noop), FunctionK.id)) - } - } - } - - def resource[F[_]: Sync, A]( - scope: TraceScope[F], - resource: Resource[F, A], - jTracer: JTracer - ): SpanRunner[F, Span.Res[F, A]] = - new SpanRunner[F, Span.Res[F, A]] { - def start( - ctx: Option[RunnerContext] - ): Resource[F, (Span.Res[F, A], F ~> F)] = - ctx match { - case Some(RunnerContext(builder, parent, hasStartTimestamp, fin)) => - def child( - name: String, - parent: JContext - ): Resource[F, (SpanBackendImpl[F], F ~> F)] = - startManaged( - builder = jTracer.spanBuilder(name).setParent(parent), - hasStartTimestamp = false, - finalizationStrategy = fin, - scope = scope - ) - - for { - rootBackend <- startManaged( - builder = builder, - hasStartTimestamp = hasStartTimestamp, - finalizationStrategy = fin, - scope = scope - ) - - rootCtx <- Resource.pure(parent.`with`(rootBackend._1.jSpan)) - - pair <- Resource.make( - child("acquire", rootCtx).use(b => b._2(resource.allocated)) - ) { case (_, release) => - child("release", rootCtx).use(b => b._2(release)) - } - (value, _) = pair - - pair2 <- child("use", rootCtx) - (useSpanBackend, nt) = pair2 - } yield (Span.Res.fromBackend(value, useSpanBackend), nt) - - case None => - resource.map(a => - (Span.Res.fromBackend(a, Span.Backend.noop), FunctionK.id) + Resource.pure( + SpanOps.Res(Span.fromBackend(Span.Backend.noop), FunctionK.id) ) } - + } } def startUnmanaged[F[_]: Sync](context: Option[RunnerContext]): F[Span[F]] = 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 1fa8617d3..9f9dd5ab5 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 @@ -23,7 +23,6 @@ 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.Span import org.typelevel.otel4s.trace.SpanBuilder import org.typelevel.otel4s.trace.SpanContext import org.typelevel.otel4s.trace.Tracer @@ -35,7 +34,7 @@ private[java] class TracerImpl[F[_]: Sync]( propagators: ContextPropagators[F] ) extends Tracer[F] { - private val runner: SpanRunner[F, Span[F]] = SpanRunner.span(scope) + private val runner: SpanRunner[F] = SpanRunner.span(scope) val meta: Tracer.Meta[F] = Tracer.Meta.enabled @@ -49,8 +48,8 @@ private[java] class TracerImpl[F[_]: Sync]( None } - def spanBuilder(name: String): SpanBuilder.Aux[F, Span[F]] = - new SpanBuilderImpl[F, Span[F]](jTracer, name, scope, runner) + def spanBuilder(name: String): SpanBuilder[F] = + new SpanBuilderImpl[F](jTracer, name, scope, runner) def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = scope diff --git a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala index c4642ea11..9be294902 100644 --- a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala +++ b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala @@ -19,7 +19,10 @@ package org.typelevel.otel4s.java.trace import cats.effect.IO import cats.effect.IOLocal import cats.effect.Resource +import cats.effect.kernel.MonadCancelThrow import cats.effect.testkit.TestControl +import cats.syntax.functor._ +import cats.~> import io.opentelemetry.api.common.{AttributeKey => JAttributeKey} import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.trace.StatusCode @@ -397,6 +400,31 @@ class TracerSuite extends CatsEffectSuite { } } + private def wrapResource[F[_]: MonadCancelThrow, A]( + tracer: Tracer[F], + resource: Resource[F, A], + attributes: Attribute[_]* + ): Resource[F, F ~> F] = { + tracer + .span("resource-span", attributes: _*) + .resource + .flatMap { res => + Resource[F, A] { + res.trace { + tracer + .span("acquire") + .surround { + resource.allocated.map { case (acquired, release) => + acquired -> res.trace( + tracer.span("release").surround(release) + ) + } + } + } + }.as(res.trace) + } + } + test("trace resource with use") { val attribute = Attribute("string-attribute", "value") @@ -464,14 +492,17 @@ class TracerSuite extends CatsEffectSuite { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 sdk <- makeSdk() tracer <- sdk.provider.get("tracer") - _ <- tracer - .resourceSpan("resource-span", attribute)(mkRes(tracer)) - .use { _ => - for { - _ <- tracer.span("body-1").surround(IO.sleep(100.millis)) - _ <- tracer.span("body-2").surround(IO.sleep(200.millis)) - _ <- tracer.span("body-3").surround(IO.sleep(50.millis)) - } yield () + _ <- wrapResource(tracer, mkRes(tracer), attribute) + .use { + _ { + tracer.span("use").surround { + for { + _ <- tracer.span("body-1").surround(IO.sleep(100.millis)) + _ <- tracer.span("body-2").surround(IO.sleep(200.millis)) + _ <- tracer.span("body-3").surround(IO.sleep(50.millis)) + } yield () + } + } } spans <- sdk.finishedSpans tree <- IO.pure(SpanNode.fromSpans(spans)) @@ -527,11 +558,14 @@ class TracerSuite extends CatsEffectSuite { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 sdk <- makeSdk() tracer <- sdk.provider.tracer("tracer").get - _ <- tracer - .resourceSpan("resource-span")(mkRes) - .surround( - tracer.span("body").surround(IO.sleep(100.millis)) - ) + _ <- wrapResource(tracer, mkRes) + .use { + _ { + tracer.span("use").surround { + tracer.span("body").surround(IO.sleep(100.millis)) + } + } + } spans <- sdk.finishedSpans tree <- IO.pure(SpanNode.fromSpans(spans)) } yield assertEquals(tree, List(expected(now))) @@ -581,9 +615,10 @@ class TracerSuite extends CatsEffectSuite { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 sdk <- makeSdk() tracer <- sdk.provider.tracer("tracer").get - _ <- tracer - .resourceSpan("resource-span")(mkRes) - .use_ + _ <- wrapResource(tracer, mkRes) + .use { + _(tracer.span("use").use_) + } spans <- sdk.finishedSpans tree <- IO.pure(SpanNode.fromSpans(spans)) } yield assertEquals(tree, List(expected(now)))