From 94a548730cb94e2628656947a997120106549311 Mon Sep 17 00:00:00 2001 From: Marissa/Princess Date: Thu, 6 Jul 2023 15:54:13 -0400 Subject: [PATCH 1/3] Replace `SpanBuilder#wrapResource` API Replace `SpanBuilder#wrapResource` with `SpanOps#resource:Resource[F,F~>F]`, which no longer automatically provides 'acquire', 'use' and 'release' spans. This API change allows further simplification of the API in future commits. --- .../typelevel/otel4s/trace/TracerMacro.scala | 40 ------------ .../typelevel/otel4s/trace/TracerMacro.scala | 47 -------------- .../org/typelevel/otel4s/trace/Span.scala | 29 --------- .../typelevel/otel4s/trace/SpanBuilder.scala | 52 ++------------- .../org/typelevel/otel4s/trace/SpanOps.scala | 32 +++++++++ .../org/typelevel/otel4s/trace/Tracer.scala | 11 ++-- examples/src/main/scala/TraceExample.scala | 26 ++++---- examples/src/main/scala/TracingExample.scala | 37 +++++++---- .../otel4s/java/trace/SpanBuilderImpl.scala | 7 +- .../otel4s/java/trace/SpanRunner.scala | 52 --------------- .../otel4s/java/trace/TracerSuite.scala | 65 ++++++++++++++----- 11 files changed, 133 insertions(+), 265 deletions(-) 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..78c5a81af 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] => @@ -77,34 +75,6 @@ private[otel4s] trait TracerMacro[F[_]] { attributes: Attribute[_]* ): SpanOps.Aux[F, Span[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 +98,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..55e5b2573 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[_]] { @@ -83,35 +81,6 @@ private[otel4s] trait TracerMacro[F[_]] { ): SpanOps.Aux[F, Span[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..5f641610e 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,8 +18,10 @@ 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 cats.~> import scala.concurrent.duration.FiniteDuration @@ -110,41 +112,6 @@ trait SpanBuilder[F[_]] { */ def withParent(parent: SpanContext): Builder - /** 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] } @@ -168,14 +135,6 @@ object SpanBuilder { 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 addAttributes(attributes: Attribute[_]*): Builder = this @@ -207,6 +166,9 @@ object SpanBuilder { def surround[A](fa: F[A]): F[A] = fa + + def resource: Resource[F, (Res, F ~> F)] = + startSpan.map(res => res -> FunctionK.id) } } 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..0117d152d 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,6 +16,9 @@ package org.typelevel.otel4s.trace +import cats.effect.Resource +import cats.~> + trait SpanOps[F[_]] { type Result <: Span[F] @@ -104,6 +107,35 @@ trait SpanOps[F[_]] { * See [[use]] for more details regarding lifecycle strategy */ def surround[A](fa: F[A]): F[A] + + /** 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]] + * @example + * {{{ + * val tracer: Tracer[F] = ??? + * val ok: F[Unit] = + * tracer.spanBuilder("resource-span") + * .build + * .resource + * .use { case (span, wrap) => + * // `wrap` encloses its contents within the "resource-span" span; + * // anything not applied to `wrap` will not end up in the span + * wrap(span.setStatus(Status.Ok, "all good")) + * } + * }}} + */ + def resource: Resource[F, (Result, F ~> F)] } object SpanOps { 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..4c4af290e 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,9 @@ package org.typelevel.otel4s package trace import cats.Applicative -import cats.effect.kernel.MonadCancelThrow -import cats.effect.kernel.Resource +import cats.effect.MonadCancelThrow +import cats.effect.Resource +import cats.~> import org.typelevel.otel4s.meta.InstrumentMeta @annotation.implicitNotFound(""" @@ -180,10 +181,8 @@ object Tracer { 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) + final def noopResSpan: Resource[F, (Span[F], F ~> F)] = + noopSpanBuilder.build.resource } object Meta { 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..e8e69968f 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,30 @@ 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 { case (span, wrap) => + // `wrap` encloses its contents within the "resource" span; + // anything not applied to `wrap` will not end up in the span + wrap { + 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" + ) + ) + } + _ <- 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..534ecc8cf 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 @@ -78,11 +78,6 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync, Res <: Span[F]]( def withFinalizationStrategy(strategy: SpanFinalizer.Strategy): Builder = 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 @@ -98,6 +93,8 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync, Res <: Span[F]]( def surround[A](fa: F[A]): F[A] = use(_ => fa) + def resource: Resource[F, (Result, F ~> F)] = start + private def start: Resource[F, (Result, F ~> F)] = Resource.eval(runnerContext).flatMap(ctx => runner.start(ctx)) } 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..32d636b78 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,7 +25,6 @@ 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 @@ -61,57 +60,6 @@ private[java] object SpanRunner { } } - 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) - ) - } - - } - def startUnmanaged[F[_]: Sync](context: Option[RunnerContext]): F[Span[F]] = context match { case Some(RunnerContext(builder, _, ts, _)) => 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..c7e1ecd23 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,29 @@ 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 { case (_, nt) => + Resource[F, A] { + nt { + tracer + .span("acquire") + .surround { + resource.allocated.map { case (acquired, release) => + acquired -> nt(tracer.span("release").use(_ => release)) + } + } + } + }.map(_ => nt) + } + } + test("trace resource with use") { val attribute = Attribute("string-attribute", "value") @@ -464,14 +490,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 +556,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 +613,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))) From b5ca78101550eb3eea881b3bab4c89211724007e Mon Sep 17 00:00:00 2001 From: Marissa/Princess Date: Fri, 7 Jul 2023 14:10:33 -0400 Subject: [PATCH 2/3] Simplify `Tracer` and `Span{Builder,Ops}` --- .../typelevel/otel4s/trace/TracerMacro.scala | 9 +-- .../typelevel/otel4s/trace/TracerMacro.scala | 6 +- .../typelevel/otel4s/trace/SpanBuilder.scala | 78 +++++++------------ .../org/typelevel/otel4s/trace/SpanOps.scala | 73 ++++++++--------- .../org/typelevel/otel4s/trace/Tracer.scala | 12 +-- .../otel4s/java/trace/SpanBuilderImpl.scala | 49 ++++++------ .../otel4s/java/trace/SpanRunner.scala | 10 ++- .../otel4s/java/trace/TracerImpl.scala | 7 +- 8 files changed, 104 insertions(+), 140 deletions(-) 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 78c5a81af..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 @@ -37,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 @@ -52,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, @@ -70,10 +70,7 @@ 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 } 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 55e5b2573..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 @@ -39,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 @@ -57,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, @@ -78,7 +78,7 @@ 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) } } 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 5f641610e..d0f57852f 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 @@ -26,8 +26,6 @@ import cats.~> 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 @@ -36,7 +34,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 @@ -45,7 +43,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. * @@ -59,7 +57,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. * @@ -70,7 +71,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]]. @@ -78,7 +79,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. * @@ -92,12 +93,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. @@ -110,65 +111,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] - 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 addAttribute[A](attribute: Attribute[A]): Builder = this - - def addAttributes(attributes: Attribute[_]*): Builder = this + def addAttribute[A](attribute: Attribute[A]): SpanBuilder[F] = this - def addLink(ctx: SpanContext, attributes: Attribute[_]*): Builder = this + def addAttributes(attributes: Attribute[_]*): SpanBuilder[F] = 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 withSpanKind(spanKind: SpanKind): Builder = this + def withFinalizationStrategy( + strategy: SpanFinalizer.Strategy + ): SpanBuilder[F] = this - def withStartTimestamp(timestamp: FiniteDuration): Builder = this + def withParent(parent: SpanContext): SpanBuilder[F] = this - def build: SpanOps.Aux[F, Result] = new SpanOps[F] { - type Result = Res + def withSpanKind(spanKind: SpanKind): SpanBuilder[F] = this - def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]] = - Applicative[F].pure(span) + def withStartTimestamp(timestamp: FiniteDuration): SpanBuilder[F] = this - def use[A](f: Res => F[A]): F[A] = - startSpan.use(res => f(res)) + def build: SpanOps[F] = new SpanOps[F] { + def startUnmanaged: F[Span[F]] = + Applicative[F].pure(span) - def use_ : F[Unit] = - startSpan.use_ + def resource: Resource[F, (Span[F], F ~> F)] = + Resource.pure(span -> FunctionK.id) - def surround[A](fa: F[A]): F[A] = - fa + def use[A](f: Span[F] => F[A]): F[A] = f(span) - def resource: Resource[F, (Res, F ~> F)] = - startSpan.map(res => res -> FunctionK.id) + 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 0117d152d..32520cbb7 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 @@ -20,7 +20,6 @@ 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`. @@ -50,9 +49,38 @@ 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]] + * @example + * {{{ + * val tracer: Tracer[F] = ??? + * val ok: F[Unit] = + * tracer.spanBuilder("resource-span") + * .build + * .resource + * .use { case (span, wrap) => + * // `wrap` encloses its contents within the "resource-span" span; + * // anything not applied to `wrap` will not end up in the span + * wrap(span.setStatus(Status.Ok, "all good")) + * } + * }}} + */ + def resource: Resource[F, (Span[F], F ~> 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 @@ -77,7 +105,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. * @@ -106,40 +134,5 @@ trait SpanOps[F[_]] { * @see * See [[use]] for more details regarding lifecycle strategy */ - def surround[A](fa: F[A]): F[A] - - /** 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]] - * @example - * {{{ - * val tracer: Tracer[F] = ??? - * val ok: F[Unit] = - * tracer.spanBuilder("resource-span") - * .build - * .resource - * .use { case (span, wrap) => - * // `wrap` encloses its contents within the "resource-span" span; - * // anything not applied to `wrap` will not end up in the span - * wrap(span.setStatus(Status.Ok, "all good")) - * } - * }}} - */ - def resource: Resource[F, (Result, F ~> F)] -} - -object SpanOps { - type Aux[F[_], A] = SpanOps[F] { - type Result = A - } + final def surround[A](fa: F[A]): F[A] = use(_ => fa) } 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 4c4af290e..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 @@ -19,8 +19,6 @@ package trace import cats.Applicative import cats.effect.MonadCancelThrow -import cats.effect.Resource -import cats.~> import org.typelevel.otel4s.meta.InstrumentMeta @annotation.implicitNotFound(""" @@ -54,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`. @@ -180,9 +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: Resource[F, (Span[F], F ~> F)] = - noopSpanBuilder.build.resource + def noopSpanBuilder: SpanBuilder[F] } object Meta { @@ -196,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) } } @@ -210,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/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 534ecc8cf..1ba18e950 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 @@ -37,11 +37,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,53 +50,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 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 surround[A](fa: F[A]): F[A] = - use(_ => fa) + def resource: Resource[F, (Span[F], F ~> F)] = + Resource.eval(runnerContext).flatMap(ctx => runner.start(ctx)) - def resource: Resource[F, (Result, F ~> F)] = start + override def use[A](f: Span[F] => F[A]): F[A] = + resource.use { case (span, nt) => nt(f(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 32d636b78..77cc82a8d 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 @@ -29,8 +29,10 @@ import io.opentelemetry.context.{Context => JContext} import org.typelevel.otel4s.trace.Span import org.typelevel.otel4s.trace.SpanFinalizer -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, (Span[F], F ~> F)] } private[java] object SpanRunner { @@ -42,8 +44,8 @@ private[java] object SpanRunner { finalizationStrategy: SpanFinalizer.Strategy ) - def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F, Span[F]] = - new SpanRunner[F, Span[F]] { + def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F] = + new SpanRunner[F] { def start(ctx: Option[RunnerContext]): Resource[F, (Span[F], F ~> F)] = { ctx match { case Some(RunnerContext(builder, _, hasStartTs, finalization)) => 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 From caeea8d882b2d57a61c9e7fbd9d5138861a43881 Mon Sep 17 00:00:00 2001 From: Marissa/Princess Date: Tue, 18 Jul 2023 14:56:20 -0400 Subject: [PATCH 3/3] Change return type of `SpanOps#resource` Add `SpanOps.Res` type for the return type of `SpanOps#resource`, for clearer, more evolvable and more documentable API. --- .../typelevel/otel4s/trace/SpanBuilder.scala | 5 +-- .../org/typelevel/otel4s/trace/SpanOps.scala | 43 ++++++++++++++++--- examples/src/main/scala/TracingExample.scala | 8 ++-- .../otel4s/java/trace/SpanBuilderImpl.scala | 5 +-- .../otel4s/java/trace/SpanRunner.scala | 13 +++--- .../otel4s/java/trace/TracerSuite.scala | 10 +++-- 6 files changed, 58 insertions(+), 26 deletions(-) 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 d0f57852f..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 @@ -21,7 +21,6 @@ import cats.Applicative import cats.arrow.FunctionK import cats.effect.MonadCancelThrow import cats.effect.Resource -import cats.~> import scala.concurrent.duration.FiniteDuration @@ -145,8 +144,8 @@ object SpanBuilder { def startUnmanaged: F[Span[F]] = Applicative[F].pure(span) - def resource: Resource[F, (Span[F], F ~> F)] = - Resource.pure(span -> FunctionK.id) + def resource: Resource[F, SpanOps.Res[F]] = + Resource.pure(SpanOps.Res(span, FunctionK.id)) def use[A](f: Span[F] => F[A]): F[A] = f(span) 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 32520cbb7..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 @@ -66,6 +66,8 @@ trait SpanOps[F[_]] { * * @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] = ??? @@ -73,14 +75,15 @@ trait SpanOps[F[_]] { * tracer.spanBuilder("resource-span") * .build * .resource - * .use { case (span, wrap) => - * // `wrap` encloses its contents within the "resource-span" span; - * // anything not applied to `wrap` will not end up in the span - * wrap(span.setStatus(Status.Ok, "all good")) + * .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, (Span[F], F ~> F)] + 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 @@ -136,3 +139,33 @@ trait SpanOps[F[_]] { */ final def surround[A](fa: F[A]): F[A] = use(_ => fa) } + +object SpanOps { + + /** 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/examples/src/main/scala/TracingExample.scala b/examples/src/main/scala/TracingExample.scala index e8e69968f..5ba58429d 100644 --- a/examples/src/main/scala/TracingExample.scala +++ b/examples/src/main/scala/TracingExample.scala @@ -64,10 +64,8 @@ object TracingExample extends IOApp.Simple { tracer .span("resource") .resource - .use { case (span, wrap) => - // `wrap` encloses its contents within the "resource" span; - // anything not applied to `wrap` will not end up in the span - wrap { + .use { res => + res.trace { for { _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) _ <- tracer.span("use").surround { @@ -80,7 +78,7 @@ object TracingExample extends IOApp.Simple { ) ) } - _ <- span.addEvent("event") + _ <- 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 1ba18e950..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} @@ -85,11 +84,11 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync]( def startUnmanaged: F[Span[F]] = runnerContext.flatMap(ctx => SpanRunner.startUnmanaged(ctx)) - def resource: Resource[F, (Span[F], F ~> F)] = + def resource: Resource[F, SpanOps.Res[F]] = Resource.eval(runnerContext).flatMap(ctx => runner.start(ctx)) override def use[A](f: Span[F] => F[A]): F[A] = - resource.use { case (span, nt) => nt(f(span)) } + resource.use { res => res.trace(f(res.span)) } override def use_ : F[Unit] = use(_ => Sync[F].unit) } 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 77cc82a8d..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 @@ -28,11 +28,10 @@ import io.opentelemetry.api.trace.{SpanBuilder => JSpanBuilder} 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[_]] { - def start( - ctx: Option[SpanRunner.RunnerContext] - ): Resource[F, (Span[F], F ~> F)] + def start(ctx: Option[SpanRunner.RunnerContext]): Resource[F, SpanOps.Res[F]] } private[java] object SpanRunner { @@ -46,7 +45,7 @@ private[java] object SpanRunner { def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F] = new SpanRunner[F] { - def start(ctx: Option[RunnerContext]): Resource[F, (Span[F], F ~> F)] = { + def start(ctx: Option[RunnerContext]): Resource[F, SpanOps.Res[F]] = { ctx match { case Some(RunnerContext(builder, _, hasStartTs, finalization)) => startManaged( @@ -54,10 +53,12 @@ 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)) + Resource.pure( + SpanOps.Res(Span.fromBackend(Span.Backend.noop), FunctionK.id) + ) } } } 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 c7e1ecd23..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 @@ -408,18 +408,20 @@ class TracerSuite extends CatsEffectSuite { tracer .span("resource-span", attributes: _*) .resource - .flatMap { case (_, nt) => + .flatMap { res => Resource[F, A] { - nt { + res.trace { tracer .span("acquire") .surround { resource.allocated.map { case (acquired, release) => - acquired -> nt(tracer.span("release").use(_ => release)) + acquired -> res.trace( + tracer.span("release").surround(release) + ) } } } - }.map(_ => nt) + }.as(res.trace) } }