diff --git a/build.sbt b/build.sbt index ff9a216e..b26d9618 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ import com.typesafe.tools.mima.core._ -ThisBuild / tlBaseVersion := "0.2" +ThisBuild / tlBaseVersion := "0.3" val scala212Version = "2.12.17" val scala213Version = "2.13.10" diff --git a/modules/core/shared/src/main/scala/Span.scala b/modules/core/shared/src/main/scala/Span.scala index 273d56fd..cef0640c 100644 --- a/modules/core/shared/src/main/scala/Span.scala +++ b/modules/core/shared/src/main/scala/Span.scala @@ -32,11 +32,8 @@ trait Span[F[_]] { */ def kernel: F[Kernel] - /** Resource that yields a child span with the given name. */ - def span(name: String): Resource[F, Span[F]] - - /** Resource that yields a child span of both this span and the given kernel. */ - def span(name: String, kernel: Kernel): Resource[F, Span[F]] + /** Resource that yields a child span of this span. */ + def span(name: String, options: Span.Options = Span.Options.Defaults): Resource[F, Span[F]] /** A unique ID for the trace of this span, if available. * This can be useful to include in error messages for example, so you can quickly find the associated trace. @@ -75,8 +72,8 @@ trait Span[F[_]] { override def log(fields: (String, TraceValue)*) = f(outer.log(fields: _*)) - override def span(name: String): Resource[G, Span[G]] = outer - .span(name) + override def span(name: String, options: Span.Options): Resource[G, Span[G]] = outer + .span(name, options) .map(_.mapK(f)) .mapK(f) @@ -85,18 +82,26 @@ trait Span[F[_]] { override def traceId: G[Option[String]] = f(outer.traceId) override def traceUri: G[Option[URI]] = f(outer.traceUri) - - /** Create resource with new span and add current span and kernel to parents of new span */ - override def span(name: String, kernel: Kernel): Resource[G, Span[G]] = outer - .span(name, kernel) - .map(_.mapK(f)) - .mapK(f) } } } object Span { + abstract class Default[F[_]: Applicative] extends Span[F] { + protected val spanCreationPolicy: Options.SpanCreationPolicy + + def span(name: String, options: Options): Resource[F, Span[F]] = + spanCreationPolicy match { + case Options.SpanCreationPolicy.Suppress => Resource.pure(Span.noop[F]) + case Options.SpanCreationPolicy.Coalesce => Resource.pure(this) + case Options.SpanCreationPolicy.Default => makeSpan(name, options) + } + + /** Like `span` but always creates a child span -- i.e., `options.spanCreationPolicy` is ignored. */ + def makeSpan(name: String, options: Options): Resource[F, Span[F]] + } + /** Ensure that Fields mixin data is added to a span when an error is raised. */ def putErrorFields[F[_]: Applicative](span: Resource[F, Span[F]]): Resource[F, Span[F]] = @@ -130,14 +135,13 @@ object Span { } private class NoopSpan[F[_]: Applicative] extends EphemeralSpan[F] { - def span(name: String): Resource[F, Span[F]] = Resource.pure(this) - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = Resource.pure(this) + override def span(name: String, options: Span.Options): Resource[F, Span[F]] = + Resource.pure(this) } private class RootsSpan[F[_]: Applicative](ep: EntryPoint[F]) extends EphemeralSpan[F] { - def span(name: String): Resource[F, Span[F]] = ep.root(name) - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = - ep.continueOrElseRoot(name, kernel) + override def span(name: String, options: Span.Options): Resource[F, Span[F]] = + options.parentKernel.fold(ep.root(name))(ep.continueOrElseRoot(name, _)) } private def resolve[F[_]](span: Span[F]): Kleisli[F, Span[F], *] ~> F = @@ -154,4 +158,52 @@ object Span { def rootTracing[F[_]: Applicative](ep: EntryPoint[F]): Kleisli[F, Span[F], *] ~> F = resolve( makeRoots(ep) ) + + /** Options for creating a new span. */ + sealed trait Options { + + /** Optional parent kernel for the child span, in addition to the parent span. + * + * Some backends do not support multiple parents, in which case the + * parent span is preferred and this parent kernel is ignored. + */ + def parentKernel: Option[Kernel] + + /** Specifies how additional span creation requests are handled on the new span. */ + def spanCreationPolicy: Options.SpanCreationPolicy + + def withParentKernel(kernel: Kernel): Options + def withoutParentKernel: Options + def withSpanCreationPolicy(p: Options.SpanCreationPolicy): Options + } + + object Options { + sealed trait SpanCreationPolicy + object SpanCreationPolicy { + + /** Span creation behaves normally. */ + case object Default extends SpanCreationPolicy + + /** Requests for span creation are ignored and any information provided to the returned span are also ignored. */ + case object Suppress extends SpanCreationPolicy + + /** Requests for span creation are ignored but information provided to the returned span are attached to the original span. */ + case object Coalesce extends SpanCreationPolicy + } + + private case class OptionsImpl( + parentKernel: Option[Kernel], + spanCreationPolicy: SpanCreationPolicy + ) extends Options { + def withParentKernel(kernel: Kernel): Options = OptionsImpl(Some(kernel), spanCreationPolicy) + def withoutParentKernel: Options = OptionsImpl(None, spanCreationPolicy) + def withSpanCreationPolicy(p: SpanCreationPolicy): Options = OptionsImpl(parentKernel, p) + } + + val Defaults: Options = OptionsImpl(None, SpanCreationPolicy.Default) + val Suppress: Options = Defaults.withSpanCreationPolicy(SpanCreationPolicy.Suppress) + val Coalesce: Options = Defaults.withSpanCreationPolicy(SpanCreationPolicy.Coalesce) + + def parentKernel(kernel: Kernel): Options = Defaults.withParentKernel(kernel) + } } diff --git a/modules/core/shared/src/main/scala/Trace.scala b/modules/core/shared/src/main/scala/Trace.scala index e0777248..a80f0bf1 100644 --- a/modules/core/shared/src/main/scala/Trace.scala +++ b/modules/core/shared/src/main/scala/Trace.scala @@ -33,13 +33,10 @@ trait Trace[F[_]] { def kernel: F[Kernel] /** Creates a new span as a resource. */ - def spanR(name: String, kernel: Option[Kernel] = None): Resource[F, F ~> F] + def spanR(name: String, options: Span.Options = Span.Options.Defaults): Resource[F, F ~> F] /** Create a new span, and within it run the continuation `k`. */ - def span[A](name: String)(k: F[A]): F[A] - - /** Create a new span and add current span and kernel to parents of new span */ - def span[A](name: String, kernel: Kernel)(k: F[A]): F[A] + def span[A](name: String, options: Span.Options = Span.Options.Defaults)(k: F[A]): F[A] /** A unique ID for this trace, if available. This can be useful to include in error messages for * example, so you can quickly find the associated trace. @@ -76,10 +73,10 @@ object Trace { override def kernel: IO[Kernel] = local.get.flatMap(_.kernel) - override def spanR(name: String, kernel: Option[Kernel]): Resource[IO, IO ~> IO] = + override def spanR(name: String, options: Span.Options): Resource[IO, IO ~> IO] = for { parent <- Resource.eval(local.get) - child <- kernel.fold(parent.span(name))(parent.span(name, _)) + child <- parent.span(name, options) } yield new (IO ~> IO) { def apply[A](fa: IO[A]): IO[A] = local @@ -87,11 +84,8 @@ object Trace { .bracket(_ => fa.onError(child.attachError(_)))(_ => local.set(parent)) } - override def span[A](name: String)(k: IO[A]): IO[A] = - spanR(name).use(_(k)) - - override def span[A](name: String, kernel: Kernel)(k: IO[A]): IO[A] = - spanR(name, Some(kernel)).use(_(k)) + override def span[A](name: String, options: Span.Options)(k: IO[A]): IO[A] = + spanR(name, options).use(_(k)) override def traceId: IO[Option[String]] = local.get.flatMap(_.traceId) @@ -115,10 +109,9 @@ object Trace { override def attachError(err: Throwable): F[Unit] = void override def log(fields: (String, TraceValue)*): F[Unit] = void override def log(event: String): F[Unit] = void - override def spanR(name: String, kernel: Option[Kernel]): Resource[F, F ~> F] = + override def spanR(name: String, options: Span.Options): Resource[F, F ~> F] = Resource.pure(FunctionK.id) - override def span[A](name: String)(k: F[A]): F[A] = k - override def span[A](name: String, kernel: Kernel)(k: F[A]): F[A] = k + override def span[A](name: String, options: Span.Options)(k: F[A]): F[A] = k override def traceId: F[Option[String]] = none.pure[F] override def traceUri: F[Option[URI]] = none.pure[F] } @@ -154,11 +147,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[Kleisli[F, Span[F], *], Kleisli[F, Span[F], *] ~> Kleisli[F, Span[F], *]] = Resource( Kleisli((span: Span[F]) => - kernel.fold(span.span(name))(span.span(name, _)).allocated.map { case (child, release) => + span.span(name, options).allocated.map { case (child, release) => new (Kleisli[F, Span[F], *] ~> Kleisli[F, Span[F], *]) { def apply[A](fa: Kleisli[F, Span[F], A]): Kleisli[F, Span[F], A] = fa.local((_: Span[F]) => child).mapF(_.onError { case e => child.attachError(e) }) @@ -167,13 +160,10 @@ object Trace { ) ) - override def span[A](name: String)(k: Kleisli[F, Span[F], A]): Kleisli[F, Span[F], A] = - spanR(name).use(_(k)) - - override def span[A](name: String, kernel: Kernel)( + override def span[A](name: String, options: Span.Options)( k: Kleisli[F, Span[F], A] ): Kleisli[F, Span[F], A] = - spanR(name, Some(kernel)).use(_(k)) + spanR(name, options).use(_(k)) def lens[E](f: E => Span[F], g: (E, Span[F]) => E): Trace[Kleisli[F, E, *]] = new Trace[Kleisli[F, E, *]] { @@ -195,26 +185,24 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[Kleisli[F, E, *], Kleisli[F, E, *] ~> Kleisli[F, E, *]] = Resource( Kleisli((e: E) => - kernel.fold(f(e).span(name))(f(e).span(name, _)).allocated.map { - case (child, release) => - new (Kleisli[F, E, *] ~> Kleisli[F, E, *]) { - def apply[A](fa: Kleisli[F, E, A]): Kleisli[F, E, A] = - fa.local((_: E) => g(e, child)) - .mapF(_.onError { case e => child.attachError(e) }) - } -> Kleisli.liftF[F, E, Unit](release) + f(e).span(name, options).allocated.map { case (child, release) => + new (Kleisli[F, E, *] ~> Kleisli[F, E, *]) { + def apply[A](fa: Kleisli[F, E, A]): Kleisli[F, E, A] = + fa.local((_: E) => g(e, child)) + .mapF(_.onError { case e => child.attachError(e) }) + } -> Kleisli.liftF[F, E, Unit](release) } ) ) - override def span[A](name: String)(k: Kleisli[F, E, A]): Kleisli[F, E, A] = - spanR(name).use(_(k)) - - override def span[A](name: String, kernel: Kernel)(k: Kleisli[F, E, A]): Kleisli[F, E, A] = - spanR(name, Some(kernel)).use(_(k)) + override def span[A](name: String, options: Span.Options)( + k: Kleisli[F, E, A] + ): Kleisli[F, E, A] = + spanR(name, options).use(_(k)) override def traceId: Kleisli[F, E, Option[String]] = Kleisli(e => f(e).traceId) @@ -252,22 +240,21 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[Kleisli[F, E, *], Kleisli[F, E, *] ~> Kleisli[F, E, *]] = Resource( Kleisli((e: E) => - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => f.compose(Kleisli.applyK(e)).andThen(Kleisli.liftK[F, E]) -> Kleisli.liftF[F, E, Unit](f(release)) } ) ) - override def span[A](name: String)(k: Kleisli[F, E, A]): Kleisli[F, E, A] = - Kleisli(e => trace.span[A](name)(k.run(e))) - - override def span[A](name: String, kernel: Kernel)(k: ReaderT[F, E, A]): ReaderT[F, E, A] = - Kleisli(e => trace.span[A](name, kernel)(k.run(e))) + override def span[A](name: String, options: Span.Options)( + k: ReaderT[F, E, A] + ): ReaderT[F, E, A] = + Kleisli(e => trace.span[A](name, options)(k.run(e))) override def traceId: Kleisli[F, E, Option[String]] = Kleisli.liftF(trace.traceId) @@ -297,11 +284,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[StateT[F, S, *], StateT[F, S, *] ~> StateT[F, S, *]] = Resource( StateT.liftF( - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => new (StateT[F, S, *] ~> StateT[F, S, *]) { def apply[A](fa: StateT[F, S, A]): StateT[F, S, A] = StateT.applyF(f(fa.runF)) @@ -311,11 +298,10 @@ object Trace { ) ) - override def span[A](name: String)(k: StateT[F, S, A]): StateT[F, S, A] = - StateT(s => trace.span[(S, A)](name)(k.run(s))) - - override def span[A](name: String, kernel: Kernel)(k: StateT[F, S, A]): StateT[F, S, A] = - StateT(s => trace.span[(S, A)](name, kernel)(k.run(s))) + override def span[A](name: String, options: Span.Options)( + k: StateT[F, S, A] + ): StateT[F, S, A] = + StateT(s => trace.span[(S, A)](name, options)(k.run(s))) override def traceId: StateT[F, S, Option[String]] = StateT.liftF(trace.traceId) @@ -346,11 +332,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[EitherT[F, E, *], EitherT[F, E, *] ~> EitherT[F, E, *]] = Resource( EitherT.liftF( - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => new (EitherT[F, E, *] ~> EitherT[F, E, *]) { def apply[A](fa: EitherT[F, E, A]): EitherT[F, E, A] = EitherT(f(fa.value)) @@ -360,11 +346,10 @@ object Trace { ) ) - override def span[A](name: String)(k: EitherT[F, E, A]): EitherT[F, E, A] = - EitherT(trace.span(name)(k.value)) - - override def span[A](name: String, kernel: Kernel)(k: EitherT[F, E, A]): EitherT[F, E, A] = - EitherT(trace.span(name, kernel)(k.value)) + override def span[A](name: String, options: Span.Options)( + k: EitherT[F, E, A] + ): EitherT[F, E, A] = + EitherT(trace.span(name, options)(k.value)) override def traceId: EitherT[F, E, Option[String]] = EitherT.liftF(trace.traceId) @@ -393,11 +378,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[OptionT[F, *], OptionT[F, *] ~> OptionT[F, *]] = Resource( OptionT.liftF( - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => new (OptionT[F, *] ~> OptionT[F, *]) { def apply[A](fa: OptionT[F, A]): OptionT[F, A] = OptionT(f(fa.value)) @@ -407,11 +392,8 @@ object Trace { ) ) - override def span[A](name: String)(k: OptionT[F, A]): OptionT[F, A] = - OptionT(trace.span(name)(k.value)) - - override def span[A](name: String, kernel: Kernel)(k: OptionT[F, A]): OptionT[F, A] = - OptionT(trace.span(name, kernel)(k.value)) + override def span[A](name: String, options: Span.Options)(k: OptionT[F, A]): OptionT[F, A] = + OptionT(trace.span(name, options)(k.value)) override def traceId: OptionT[F, Option[String]] = OptionT.liftF(trace.traceId) @@ -443,11 +425,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[Nested[F, G, *], Nested[F, G, *] ~> Nested[F, G, *]] = Resource( Nested( - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => ( new (Nested[F, G, *] ~> Nested[F, G, *]) { def apply[A](fa: Nested[F, G, A]): Nested[F, G, A] = @@ -459,11 +441,10 @@ object Trace { ) ) - override def span[A](name: String)(k: Nested[F, G, A]): Nested[F, G, A] = - trace.span(name)(k.value).nested - - override def span[A](name: String, kernel: Kernel)(k: Nested[F, G, A]): Nested[F, G, A] = - trace.span(name, kernel)(k.value).nested + override def span[A](name: String, options: Span.Options)( + k: Nested[F, G, A] + ): Nested[F, G, A] = + trace.span(name, options)(k.value).nested override def traceId: Nested[F, G, Option[String]] = trace.traceId.map(_.pure[G]).nested @@ -493,11 +474,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[Resource[F, *], Resource[F, *] ~> Resource[F, *]] = Resource( Resource.eval( - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => new (Resource[F, *] ~> Resource[F, *]) { def apply[A](fa: Resource[F, A]): Resource[F, A] = fa.mapK(f) @@ -507,15 +488,8 @@ object Trace { ) ) - override def span[A](name: String)(k: Resource[F, A]): Resource[F, A] = - trace.spanR(name).flatMap { f => - Resource(f(k.allocated).map { case (a, release) => - a -> f(release) - }) - } - - override def span[A](name: String, kernel: Kernel)(k: Resource[F, A]): Resource[F, A] = - trace.spanR(name, kernel.some).flatMap { f => + override def span[A](name: String, options: Span.Options)(k: Resource[F, A]): Resource[F, A] = + trace.spanR(name, options).flatMap { f => Resource(f(k.allocated).map { case (a, release) => a -> f(release) }) @@ -547,11 +521,11 @@ object Trace { override def spanR( name: String, - kernel: Option[Kernel] + options: Span.Options ): Resource[Stream[F, *], Stream[F, *] ~> Stream[F, *]] = Resource( Stream.eval( - trace.spanR(name, kernel).allocated.map { case (f, release) => + trace.spanR(name, options).allocated.map { case (f, release) => new (Stream[F, *] ~> Stream[F, *]) { def apply[A](fa: Stream[F, A]): Stream[F, A] = fa.translate(f) @@ -561,11 +535,8 @@ object Trace { ) ) - override def span[A](name: String)(k: Stream[F, A]): Stream[F, A] = - Stream.resource(trace.spanR(name)).flatMap(k.translate) - - override def span[A](name: String, kernel: Kernel)(k: Stream[F, A]): Stream[F, A] = - Stream.resource(trace.spanR(name, kernel.some)).flatMap(k.translate) + override def span[A](name: String, options: Span.Options)(k: Stream[F, A]): Stream[F, A] = + Stream.resource(trace.spanR(name, options)).flatMap(k.translate) override def traceId: Stream[F, Option[String]] = Stream.eval(trace.traceId) diff --git a/modules/core/shared/src/test/scala/InMemory.scala b/modules/core/shared/src/test/scala/InMemory.scala index de31091a..eecc8a13 100644 --- a/modules/core/shared/src/test/scala/InMemory.scala +++ b/modules/core/shared/src/test/scala/InMemory.scala @@ -6,16 +6,20 @@ package natchez import java.net.URI -import cats.data.Chain +import cats.data.{Chain, Kleisli} import cats.effect.{IO, Ref, Resource} +import natchez.Span.Options +import munit.CatsEffectSuite + object InMemory { class Span( lineage: Lineage, k: Kernel, - ref: Ref[IO, Chain[(Lineage, NatchezCommand)]] - ) extends natchez.Span[IO] { + ref: Ref[IO, Chain[(Lineage, NatchezCommand)]], + val spanCreationPolicy: Options.SpanCreationPolicy + ) extends natchez.Span.Default[IO] { def put(fields: (String, natchez.TraceValue)*): IO[Unit] = ref.update(_.append(lineage -> NatchezCommand.Put(fields.toList))) @@ -32,20 +36,10 @@ object InMemory { def kernel: IO[Kernel] = ref.update(_.append(lineage -> NatchezCommand.AskKernel(k))).as(k) - def span(name: String): Resource[IO, natchez.Span[IO]] = { - val acquire = ref - .update(_.append(lineage -> NatchezCommand.CreateSpan(name, None))) - .as(new Span(lineage / name, k, ref)) - - val release = ref.update(_.append(lineage -> NatchezCommand.ReleaseSpan(name))) - - Resource.make(acquire)(_ => release) - } - - def span(name: String, kernel: Kernel): Resource[IO, natchez.Span[IO]] = { + def makeSpan(name: String, options: Options): Resource[IO, natchez.Span[IO]] = { val acquire = ref - .update(_.append(lineage -> NatchezCommand.CreateSpan(name, Some(kernel)))) - .as(new Span(lineage / name, k, ref)) + .update(_.append(lineage -> NatchezCommand.CreateSpan(name, options.parentKernel))) + .as(new Span(lineage / name, k, ref, options.spanCreationPolicy)) val release = ref.update(_.append(lineage -> NatchezCommand.ReleaseSpan(name))) @@ -77,7 +71,7 @@ object InMemory { private def newSpan(name: String, kernel: Kernel): Resource[IO, Span] = { val acquire = ref .update(_.append(Lineage.Root -> NatchezCommand.CreateRootSpan(name, kernel))) - .as(new Span(Lineage.Root, kernel, ref)) + .as(new Span(Lineage.Root, kernel, ref, Options.SpanCreationPolicy.Default)) val release = ref.update(_.append(Lineage.Root -> NatchezCommand.ReleaseRootSpan(name))) @@ -116,3 +110,52 @@ object InMemory { } } + +trait InMemorySuite extends CatsEffectSuite { + type Lineage = InMemory.Lineage + val Lineage = InMemory.Lineage + type NatchezCommand = InMemory.NatchezCommand + val NatchezCommand = InMemory.NatchezCommand + + trait TraceTest { + def program[F[_]: Trace]: F[Unit] + def expectedHistory: List[(Lineage, NatchezCommand)] + } + + def traceTest(name: String, tt: TraceTest) = { + test(s"$name - Kleisli")( + testTraceKleisli(tt.program[Kleisli[IO, Span[IO], *]](_), tt.expectedHistory) + ) + test(s"$name - IOLocal")(testTraceIoLocal(tt.program[IO](_), tt.expectedHistory)) + } + + def testTraceKleisli( + traceProgram: Trace[Kleisli[IO, Span[IO], *]] => Kleisli[IO, Span[IO], Unit], + expectedHistory: List[(Lineage, NatchezCommand)] + ) = testTrace[Kleisli[IO, Span[IO], *]]( + traceProgram, + root => IO.pure(Trace[Kleisli[IO, Span[IO], *]] -> (k => k.run(root))), + expectedHistory + ) + + def testTraceIoLocal( + traceProgram: Trace[IO] => IO[Unit], + expectedHistory: List[(Lineage, NatchezCommand)] + ) = testTrace[IO](traceProgram, Trace.ioTrace(_).map(_ -> identity), expectedHistory) + + def testTrace[F[_]]( + traceProgram: Trace[F] => F[Unit], + makeTraceAndResolver: Span[IO] => IO[(Trace[F], F[Unit] => IO[Unit])], + expectedHistory: List[(Lineage, NatchezCommand)] + ) = + InMemory.EntryPoint.create.flatMap { ep => + val traced = ep.root("root").use { r => + makeTraceAndResolver(r).flatMap { case (traceInstance, resolve) => + resolve(traceProgram(traceInstance)) + } + } + traced *> ep.ref.get.map { history => + assertEquals(history.toList, expectedHistory) + } + } +} diff --git a/modules/core/shared/src/test/scala/SpanCoalesceTest.scala b/modules/core/shared/src/test/scala/SpanCoalesceTest.scala new file mode 100644 index 00000000..a4c693e9 --- /dev/null +++ b/modules/core/shared/src/test/scala/SpanCoalesceTest.scala @@ -0,0 +1,45 @@ +// Copyright (c) 2019-2020 by Rob Norris and Contributors +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package natchez + +class SpanCoalesceTest extends InMemorySuite { + + traceTest( + "suppress - nominal", + new TraceTest { + def program[F[_]: Trace] = { + def detailed = + Trace[F].span("parent")(Trace[F].span("child")(Trace[F].put("answer" -> 42))) + Trace[F].span("suppressed", Span.Options.Suppress)(detailed) + } + + def expectedHistory = List( + (Lineage.Root, NatchezCommand.CreateRootSpan("root", Kernel(Map()))), + (Lineage.Root, NatchezCommand.CreateSpan("suppressed", None)), + (Lineage.Root, NatchezCommand.ReleaseSpan("suppressed")), + (Lineage.Root, NatchezCommand.ReleaseRootSpan("root")) + ) + } + ) + + traceTest( + "coaslesce - nominal", + new TraceTest { + def program[F[_]: Trace] = { + def detailed = + Trace[F].span("parent")(Trace[F].span("child")(Trace[F].put("answer" -> 42))) + Trace[F].span("coalesced", Span.Options.Coalesce)(detailed) + } + + def expectedHistory = List( + (Lineage.Root, NatchezCommand.CreateRootSpan("root", Kernel(Map()))), + (Lineage.Root, NatchezCommand.CreateSpan("coalesced", None)), + (Lineage.Root / "coalesced", NatchezCommand.Put(List("answer" -> 42))), + (Lineage.Root, NatchezCommand.ReleaseSpan("coalesced")), + (Lineage.Root, NatchezCommand.ReleaseRootSpan("root")) + ) + } + ) +} diff --git a/modules/core/shared/src/test/scala/SpanPropagationTest.scala b/modules/core/shared/src/test/scala/SpanPropagationTest.scala index 74070cb9..13893e74 100644 --- a/modules/core/shared/src/test/scala/SpanPropagationTest.scala +++ b/modules/core/shared/src/test/scala/SpanPropagationTest.scala @@ -4,46 +4,23 @@ package natchez -import cats.data.Kleisli -import cats.effect.IO -import munit.CatsEffectSuite +class SpanPropagationTest extends InMemorySuite { -import InMemory.{Lineage, NatchezCommand} + traceTest( + "propagation", + new TraceTest { + def program[F[_]: Trace] = + Trace[F].span("parent")(Trace[F].span("child")(Trace[F].put("answer" -> 42))) -class SpanPropagationTest extends CatsEffectSuite { - def prg[F[_]: Trace] = - Trace[F].span("parent")(Trace[F].span("child")(Trace[F].put("answer" -> 42))) - - def testPropagation[F[_]](f: Span[IO] => IO[(Trace[F], F[Unit] => IO[Unit])]) = - InMemory.EntryPoint.create.flatMap { ep => - val traced = ep.root("root").use { r => - f(r).flatMap { case (traceInstance, resolve) => - resolve(prg(traceInstance)) - } - } - traced *> ep.ref.get.map { history => - assertEquals( - history.toList, - List( - (Lineage.Root, NatchezCommand.CreateRootSpan("root", Kernel(Map()))), - (Lineage.Root, NatchezCommand.CreateSpan("parent", None)), - (Lineage.Root / "parent", NatchezCommand.CreateSpan("child", None)), - (Lineage.Root / "parent" / "child", NatchezCommand.Put(List("answer" -> 42))), - (Lineage.Root / "parent", NatchezCommand.ReleaseSpan("child")), - (Lineage.Root, NatchezCommand.ReleaseSpan("parent")), - (Lineage.Root, NatchezCommand.ReleaseRootSpan("root")) - ) - ) - } + def expectedHistory = List( + (Lineage.Root, NatchezCommand.CreateRootSpan("root", Kernel(Map()))), + (Lineage.Root, NatchezCommand.CreateSpan("parent", None)), + (Lineage.Root / "parent", NatchezCommand.CreateSpan("child", None)), + (Lineage.Root / "parent" / "child", NatchezCommand.Put(List("answer" -> 42))), + (Lineage.Root / "parent", NatchezCommand.ReleaseSpan("child")), + (Lineage.Root, NatchezCommand.ReleaseSpan("parent")), + (Lineage.Root, NatchezCommand.ReleaseRootSpan("root")) + ) } - - test("kleisli") { - testPropagation[Kleisli[IO, Span[IO], *]](root => - IO.pure(Trace[Kleisli[IO, Span[IO], *]] -> (k => k.run(root))) - ) - } - - test("io") { - testPropagation[IO](root => Trace.ioTrace(root).map(_ -> identity)) - } + ) } diff --git a/modules/datadog/src/main/scala/DDEntryPoint.scala b/modules/datadog/src/main/scala/DDEntryPoint.scala index dae28280..313bf651 100644 --- a/modules/datadog/src/main/scala/DDEntryPoint.scala +++ b/modules/datadog/src/main/scala/DDEntryPoint.scala @@ -18,7 +18,7 @@ final class DDEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[URI]) override def root(name: String): Resource[F, Span[F]] = Resource .make(Sync[F].delay(tracer.buildSpan(name).start()))(s => Sync[F].delay(s.finish())) - .map(DDSpan(tracer, _, uriPrefix)) + .map(DDSpan(tracer, _, uriPrefix, Span.Options.SpanCreationPolicy.Default)) override def continue(name: String, kernel: Kernel): Resource[F, Span[F]] = Resource @@ -31,7 +31,7 @@ final class DDEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[URI]) tracer.buildSpan(name).asChildOf(spanContext).start() } )(s => Sync[F].delay(s.finish())) - .map(DDSpan(tracer, _, uriPrefix)) + .map(DDSpan(tracer, _, uriPrefix, Span.Options.SpanCreationPolicy.Default)) override def continueOrElseRoot(name: String, kernel: Kernel): Resource[F, Span[F]] = continue(name, kernel).flatMap { diff --git a/modules/datadog/src/main/scala/DDSpan.scala b/modules/datadog/src/main/scala/DDSpan.scala index caeabd2e..25075087 100644 --- a/modules/datadog/src/main/scala/DDSpan.scala +++ b/modules/datadog/src/main/scala/DDSpan.scala @@ -23,8 +23,9 @@ import java.net.URI final case class DDSpan[F[_]: Sync]( tracer: ot.Tracer, span: ot.Span, - uriPrefix: Option[URI] -) extends Span[F] { + uriPrefix: Option[URI], + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { def kernel: F[Kernel] = Sync[F].delay { @@ -52,26 +53,19 @@ final case class DDSpan[F[_]: Sync]( override def log(event: String): F[Unit] = Sync[F].delay(span.log(event)).void - def span(name: String): Resource[F, Span[F]] = - Span.putErrorFields( - Resource - .makeCase(Sync[F].delay(tracer.buildSpan(name).asChildOf(span).start)) { - case (span, ExitCase.Errored(e)) => Sync[F].delay(span.log(e.toString).finish()) - case (span, _) => Sync[F].delay(span.finish()) - } - .map(DDSpan(tracer, _, uriPrefix)) + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = { + val parent = options.parentKernel.map(k => + tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(k.toHeaders.asJava)) ) - - def span(name: String, kernel: Kernel): Resource[F, Span[F]] = { - val parent = - tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(kernel.toHeaders.asJava)) Span.putErrorFields( Resource - .makeCase(Sync[F].delay(tracer.buildSpan(name).asChildOf(parent).asChildOf(span).start)) { + .makeCase( + Sync[F].delay(tracer.buildSpan(name).asChildOf(parent.orNull).asChildOf(span).start) + ) { case (span, ExitCase.Errored(e)) => Sync[F].delay(span.log(e.toString).finish()) case (span, _) => Sync[F].delay(span.finish()) } - .map(DDSpan(tracer, _, uriPrefix)) + .map(DDSpan(tracer, _, uriPrefix, options.spanCreationPolicy)) ) } diff --git a/modules/honeycomb/src/main/scala/HoneycombSpan.scala b/modules/honeycomb/src/main/scala/HoneycombSpan.scala index f6a2132b..401c6833 100644 --- a/modules/honeycomb/src/main/scala/HoneycombSpan.scala +++ b/modules/honeycomb/src/main/scala/HoneycombSpan.scala @@ -22,8 +22,9 @@ private[honeycomb] final case class HoneycombSpan[F[_]: Sync]( parentId: Option[UUID], traceUUID: UUID, timestamp: Instant, - fields: Ref[F, Map[String, TraceValue]] -) extends Span[F] { + fields: Ref[F, Map[String, TraceValue]], + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import HoneycombSpan._ def get(key: String): F[Option[TraceValue]] = @@ -46,15 +47,12 @@ private[honeycomb] final case class HoneycombSpan[F[_]: Sync]( override def log(event: String): F[Unit] = log("event" -> TraceValue.StringValue(event)) - def span(label: String): Resource[F, Span[F]] = - Span.putErrorFields( - Resource.makeCase(HoneycombSpan.child(this, label))(HoneycombSpan.finish[F]).widen - ) - - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = Span.putErrorFields( Resource - .makeCase(HoneycombSpan.fromKernel(client, name, kernel))(HoneycombSpan.finish[F]) + .makeCase(HoneycombSpan.child(this, name, options.spanCreationPolicy))( + HoneycombSpan.finish[F] + ) .widen ) @@ -115,7 +113,8 @@ private[honeycomb] object HoneycombSpan { def child[F[_]: Sync]( parent: HoneycombSpan[F], - name: String + name: String, + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[HoneycombSpan[F]] = for { spanUUID <- uuid[F] @@ -128,7 +127,8 @@ private[honeycomb] object HoneycombSpan { parentId = Some(parent.spanUUID), traceUUID = parent.traceUUID, timestamp = timestamp, - fields = fields + fields = fields, + spanCreationPolicy = spanCreationPolicy ) def root[F[_]: Sync]( @@ -147,7 +147,8 @@ private[honeycomb] object HoneycombSpan { parentId = None, traceUUID = traceUUID, timestamp = timestamp, - fields = fields + fields = fields, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) def fromKernel[F[_]]( @@ -168,7 +169,8 @@ private[honeycomb] object HoneycombSpan { parentId = Some(parentId), traceUUID = traceUUID, timestamp = timestamp, - fields = fields + fields = fields, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) def fromKernelOrElseRoot[F[_]]( diff --git a/modules/jaeger/src/main/scala/JaegerEntryPoint.scala b/modules/jaeger/src/main/scala/JaegerEntryPoint.scala index 7f671e8d..faaceaee 100644 --- a/modules/jaeger/src/main/scala/JaegerEntryPoint.scala +++ b/modules/jaeger/src/main/scala/JaegerEntryPoint.scala @@ -27,12 +27,12 @@ final class JaegerEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[UR tracer.buildSpan(name).asChildOf(p).start() } )(s => Sync[F].delay(s.finish)) - .map(JaegerSpan(tracer, _, uriPrefix)) + .map(JaegerSpan(tracer, _, uriPrefix, Span.Options.SpanCreationPolicy.Default)) def root(name: String): Resource[F, Span[F]] = Resource .make(Sync[F].delay(tracer.buildSpan(name).start()))(s => Sync[F].delay(s.finish)) - .map(JaegerSpan(tracer, _, uriPrefix)) + .map(JaegerSpan(tracer, _, uriPrefix, Span.Options.SpanCreationPolicy.Default)) def continueOrElseRoot(name: String, kernel: Kernel): Resource[F, Span[F]] = continue(name, kernel) diff --git a/modules/jaeger/src/main/scala/JaegerSpan.scala b/modules/jaeger/src/main/scala/JaegerSpan.scala index 96bbd966..a1a5800c 100644 --- a/modules/jaeger/src/main/scala/JaegerSpan.scala +++ b/modules/jaeger/src/main/scala/JaegerSpan.scala @@ -22,8 +22,9 @@ import java.net.URI private[jaeger] final case class JaegerSpan[F[_]: Sync]( tracer: ot.Tracer, span: ot.Span, - prefix: Option[URI] -) extends Span[F] { + prefix: Option[URI], + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import TraceValue._ override def kernel: F[Kernel] = @@ -68,25 +69,18 @@ private[jaeger] final case class JaegerSpan[F[_]: Sync]( override def log(event: String): F[Unit] = Sync[F].delay(span.log(event)).void - override def span(name: String): Resource[F, Span[F]] = - Span.putErrorFields { - Resource.makeCase( - Sync[F] - .delay(tracer.buildSpan(name).asChildOf(span).start) - .map(JaegerSpan(tracer, _, prefix)) - )(JaegerSpan.finish) - } - - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = Span.putErrorFields { Resource.makeCase { - val p = tracer.extract( - Format.Builtin.HTTP_HEADERS, - new TextMapAdapter(kernel.toHeaders.asJava) + val p = options.parentKernel.map(k => + tracer.extract( + Format.Builtin.HTTP_HEADERS, + new TextMapAdapter(k.toHeaders.asJava) + ) ) Sync[F] - .delay(tracer.buildSpan(name).asChildOf(p).asChildOf(span).start) - .map(JaegerSpan(tracer, _, prefix)) + .delay(tracer.buildSpan(name).asChildOf(p.orNull).asChildOf(span).start) + .map(JaegerSpan(tracer, _, prefix, options.spanCreationPolicy)) }(JaegerSpan.finish) } diff --git a/modules/lightstep/src/main/scala/LightstepEntryPoint.scala b/modules/lightstep/src/main/scala/LightstepEntryPoint.scala index 6b57a8df..a3c08af9 100644 --- a/modules/lightstep/src/main/scala/LightstepEntryPoint.scala +++ b/modules/lightstep/src/main/scala/LightstepEntryPoint.scala @@ -16,7 +16,7 @@ final class LightstepEntryPoint[F[_]: Sync](tracer: Tracer) extends EntryPoint[F override def root(name: String): Resource[F, Span[F]] = Resource .make(Sync[F].delay(tracer.buildSpan(name).start()))(s => Sync[F].delay(s.finish())) - .map(LightstepSpan(tracer, _)) + .map(LightstepSpan(tracer, _, Span.Options.SpanCreationPolicy.Default)) override def continue(name: String, kernel: Kernel): Resource[F, Span[F]] = Resource @@ -27,7 +27,7 @@ final class LightstepEntryPoint[F[_]: Sync](tracer: Tracer) extends EntryPoint[F tracer.buildSpan(name).asChildOf(p).start() } )(s => Sync[F].delay(s.finish())) - .map(LightstepSpan(tracer, _)) + .map(LightstepSpan(tracer, _, Span.Options.SpanCreationPolicy.Default)) override def continueOrElseRoot(name: String, kernel: Kernel): Resource[F, Span[F]] = continue(name, kernel).flatMap { diff --git a/modules/lightstep/src/main/scala/LightstepSpan.scala b/modules/lightstep/src/main/scala/LightstepSpan.scala index 0b7f938e..a21dc590 100644 --- a/modules/lightstep/src/main/scala/LightstepSpan.scala +++ b/modules/lightstep/src/main/scala/LightstepSpan.scala @@ -17,8 +17,9 @@ import java.net.URI private[lightstep] final case class LightstepSpan[F[_]: Sync]( tracer: ot.Tracer, - span: ot.Span -) extends Span[F] { + span: ot.Span, + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import TraceValue._ @@ -60,25 +61,18 @@ private[lightstep] final case class LightstepSpan[F[_]: Sync]( Sync[F].delay(span.log(map)).void } - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = { - val p = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(kernel.toHeaders.asJava)) - Span.putErrorFields( - Resource - .make(Sync[F].delay(tracer.buildSpan(name).asChildOf(p).asChildOf(span).start()))(s => - Sync[F].delay(s.finish()) - ) - .map(LightstepSpan(tracer, _)) + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = { + val p = options.parentKernel.map(k => + tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(k.toHeaders.asJava)) ) - } - - override def span(name: String): Resource[F, Span[F]] = Span.putErrorFields( Resource - .make(Sync[F].delay(tracer.buildSpan(name).asChildOf(span).start()))(s => - Sync[F].delay(s.finish()) + .make(Sync[F].delay(tracer.buildSpan(name).asChildOf(p.orNull).asChildOf(span).start()))( + s => Sync[F].delay(s.finish()) ) - .map(LightstepSpan(tracer, _)) + .map(LightstepSpan(tracer, _, options.spanCreationPolicy)) ) + } override def spanId: F[Option[String]] = Sync[F].pure { diff --git a/modules/log-odin/src/main/scala/LogSpan.scala b/modules/log-odin/src/main/scala/LogSpan.scala index 24e852f8..935ce353 100644 --- a/modules/log-odin/src/main/scala/LogSpan.scala +++ b/modules/log-odin/src/main/scala/LogSpan.scala @@ -28,8 +28,9 @@ private[logodin] final case class LogSpan[F[_]: Sync: Logger]( tid: UUID, timestamp: Instant, fields: Ref[F, Map[String, Json]], - children: Ref[F, List[JsonObject]] -) extends Span[F] { + children: Ref[F, List[JsonObject]], + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import LogSpan._ def spanId: F[Option[String]] = @@ -68,11 +69,10 @@ private[logodin] final case class LogSpan[F[_]: Sync: Logger]( def log(fields: (String, TraceValue)*): F[Unit] = Applicative[F].unit - def span(label: String): Resource[F, Span[F]] = - Resource.makeCase(LogSpan.child(this, label))(LogSpan.finish[F]).widen - - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = - Resource.makeCase(LogSpan.fromKernel(service, name, kernel))(LogSpan.finish[F]).widen + def makeSpan(label: String, options: Span.Options): Resource[F, Span[F]] = + Resource + .makeCase(LogSpan.child(this, label, options.spanCreationPolicy))(LogSpan.finish[F]) + .widen def json(finish: Instant, exitCase: ExitCase): F[JsonObject] = (fields.get, children.get).mapN { (fs, cs) => @@ -153,7 +153,8 @@ private[logodin] object LogSpan { def child[F[_]: Sync: Logger]( parent: LogSpan[F], - name: String + name: String, + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[LogSpan[F]] = for { spanId <- uuid[F] @@ -168,7 +169,8 @@ private[logodin] object LogSpan { tid = parent.tid, timestamp = timestamp, fields = fields, - children = children + children = children, + spanCreationPolicy = spanCreationPolicy ) def root[F[_]: Sync: Logger]( @@ -189,7 +191,8 @@ private[logodin] object LogSpan { tid = traceId, timestamp = timestamp, fields = fields, - children = children + children = children, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) def fromKernel[F[_]: Sync: Logger]( @@ -212,7 +215,8 @@ private[logodin] object LogSpan { tid = traceId, timestamp = timestamp, fields = fields, - children = children + children = children, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) def fromKernelOrElseRoot[F[_]: Sync: Logger]( diff --git a/modules/log/shared/src/main/scala/LogSpan.scala b/modules/log/shared/src/main/scala/LogSpan.scala index aabd2a27..4841ff76 100644 --- a/modules/log/shared/src/main/scala/LogSpan.scala +++ b/modules/log/shared/src/main/scala/LogSpan.scala @@ -28,11 +28,13 @@ private[log] final case class LogSpan[F[_]: Sync: Logger]( name: String, sid: UUID, parent: Option[Either[UUID, LogSpan[F]]], + parentKernel: Option[Kernel], traceUUID: UUID, timestamp: Instant, fields: Ref[F, Map[String, Json]], - children: Ref[F, List[JsonObject]] -) extends Span[F] { + children: Ref[F, List[JsonObject]], + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import LogSpan._ def parentId: Option[UUID] = @@ -61,8 +63,10 @@ private[log] final case class LogSpan[F[_]: Sync: Logger]( override def log(event: String): F[Unit] = log("event" -> TraceValue.StringValue(event)) - def span(label: String): Resource[F, Span[F]] = - Span.putErrorFields(Resource.makeCase(LogSpan.child(this, label))(LogSpan.finishChild[F]).widen) + def makeSpan(label: String, options: Span.Options): Resource[F, Span[F]] = + Span.putErrorFields( + Resource.makeCase(LogSpan.child(this, label, options))(LogSpan.finishChild[F]).widen + ) def attachError(err: Throwable): F[Unit] = putAny( @@ -99,12 +103,6 @@ private[log] final case class LogSpan[F[_]: Sync: Logger]( sid.toString.some.pure[F] def traceUri: F[Option[URI]] = none.pure[F] - - def span(name: String, kernel: Kernel): Resource[F, Span[F]] = - Span.putErrorFields( - Resource.makeCase(LogSpan.fromKernel(service, name, kernel))(LogSpan.finishChild[F]).widen - ) - } private[log] object LogSpan { @@ -163,7 +161,8 @@ private[log] object LogSpan { def child[F[_]: Sync: Logger]( parent: LogSpan[F], - name: String + name: String, + options: Span.Options ): F[LogSpan[F]] = for { spanId <- uuid[F] @@ -175,10 +174,12 @@ private[log] object LogSpan { name = name, sid = spanId, parent = Some(Right(parent)), + parentKernel = options.parentKernel, traceUUID = parent.traceUUID, timestamp = timestamp, fields = fields, - children = children + children = children, + spanCreationPolicy = options.spanCreationPolicy ) def root[F[_]: Sync: Logger]( @@ -196,10 +197,12 @@ private[log] object LogSpan { name = name, sid = spanId, parent = None, + parentKernel = None, traceUUID = traceUUID, timestamp = timestamp, fields = fields, - children = children + children = children, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) def fromKernel[F[_]: Sync: Logger]( @@ -219,10 +222,12 @@ private[log] object LogSpan { name = name, sid = spanId, parent = Some(Left(parentId)), + parentKernel = None, traceUUID = traceUUID, timestamp = timestamp, fields = fields, - children = children + children = children, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) def fromKernelOrElseRoot[F[_]: Sync: Logger]( diff --git a/modules/mtl/shared/src/main/scala/LocalTrace.scala b/modules/mtl/shared/src/main/scala/LocalTrace.scala index a84ed9e5..1e9505f4 100644 --- a/modules/mtl/shared/src/main/scala/LocalTrace.scala +++ b/modules/mtl/shared/src/main/scala/LocalTrace.scala @@ -32,10 +32,10 @@ private[mtl] class LocalTrace[F[_]](local: Local[F, Span[F]])(implicit override def log(event: String): F[Unit] = local.ask.flatMap(_.log(event)) - override def spanR(name: String, kernel: Option[Kernel]): Resource[F, F ~> F] = + override def spanR(name: String, options: Span.Options): Resource[F, F ~> F] = Resource( local.ask.flatMap(t => - kernel.map(t.span(name, _)).getOrElse(t.span(name)).allocated.map { case (child, release) => + t.span(name, options).allocated.map { case (child, release) => new (F ~> F) { def apply[A](fa: F[A]): F[A] = local.scope(fa)(child) @@ -44,18 +44,13 @@ private[mtl] class LocalTrace[F[_]](local: Local[F, Span[F]])(implicit ) ) - override def span[A](name: String)(k: F[A]): F[A] = + override def span[A](name: String, options: Span.Options)(k: F[A]): F[A] = local.ask.flatMap { span => - span.span(name).use { s => - ev.onError(local.scope(k)(s)) { case err => s.attachError(err) } + span.span(name, options).use { s => + local.scope(k)(s).onError { case err => s.attachError(err) } } } - override def span[A](name: String, kernel: Kernel)(k: F[A]): F[A] = - local.ask.flatMap { span => - span.span(name, kernel).use(local.scope(k)) - } - override def traceId: F[Option[String]] = local.ask.flatMap(_.traceId) diff --git a/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala b/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala index 2e3ac43c..922b9494 100644 --- a/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala +++ b/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala @@ -27,8 +27,9 @@ private[newrelic] final case class NewrelicSpan[F[_]: Sync]( attributes: Ref[F, Attributes], children: Ref[F, List[Span]], parent: Option[Either[String, NewrelicSpan[F]]], - sender: SpanBatchSender -) extends natchez.Span[F] { + sender: SpanBatchSender, + spanCreationPolicy: natchez.Span.Options.SpanCreationPolicy +) extends natchez.Span.Default[F] { override def kernel: F[Kernel] = Sync[F].delay { @@ -55,14 +56,11 @@ private[newrelic] final case class NewrelicSpan[F[_]: Sync]( override def log(event: String): F[Unit] = Sync[F].unit - override def span(name: String, kernel: Kernel): Resource[F, natchez.Span[F]] = + override def makeSpan(name: String, options: natchez.Span.Options): Resource[F, natchez.Span[F]] = Resource - .make(NewrelicSpan.fromKernel(service, name, kernel)(sender))(NewrelicSpan.finish[F]) + .make(NewrelicSpan.child(name, this, options.spanCreationPolicy))(NewrelicSpan.finish[F]) .widen - override def span(name: String): Resource[F, natchez.Span[F]] = - Resource.make(NewrelicSpan.child(name, this))(NewrelicSpan.finish[F]).widen - override def spanId: F[Option[String]] = id.some.pure[F] override def traceId: F[Option[String]] = traceIdS.some.pure[F] @@ -97,7 +95,8 @@ object NewrelicSpan { startTime = timestamp, attributes = attributes, children = children, - sender = sender + sender = sender, + spanCreationPolicy = natchez.Span.Options.SpanCreationPolicy.Default ) def root[F[_]: Sync](service: String, name: String, sender: SpanBatchSender): F[NewrelicSpan[F]] = @@ -116,10 +115,15 @@ object NewrelicSpan { attributes, children, None, - sender + sender, + spanCreationPolicy = natchez.Span.Options.SpanCreationPolicy.Default ) - def child[F[_]: Sync](name: String, parent: NewrelicSpan[F]): F[NewrelicSpan[F]] = + def child[F[_]: Sync]( + name: String, + parent: NewrelicSpan[F], + spanCreationPolicy: natchez.Span.Options.SpanCreationPolicy + ): F[NewrelicSpan[F]] = for { spanId <- Sync[F].delay(UUID.randomUUID().toString) startTime <- Sync[F].delay(System.currentTimeMillis()) @@ -134,7 +138,8 @@ object NewrelicSpan { attributes, children, Some(Right(parent)), - parent.sender + parent.sender, + spanCreationPolicy = spanCreationPolicy ) def finish[F[_]: Sync](nrs: NewrelicSpan[F]): F[Unit] = diff --git a/modules/noop/shared/src/main/scala/NoopSpan.scala b/modules/noop/shared/src/main/scala/NoopSpan.scala index 655126b2..4633217c 100644 --- a/modules/noop/shared/src/main/scala/NoopSpan.scala +++ b/modules/noop/shared/src/main/scala/NoopSpan.scala @@ -27,10 +27,7 @@ final case class NoopSpan[F[_]: Applicative]() extends Span[F] { override def kernel: F[Kernel] = Applicative[F].pure(Kernel(Map.empty)) - override def span(name: String): Resource[F, Span[F]] = - Resource.eval(NoopSpan[F]().pure[F]) - - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = + override def span(name: String, options: Span.Options): Resource[F, Span[F]] = Resource.eval(NoopSpan[F]().pure[F]) // TODO diff --git a/modules/noop/shared/src/main/scala/NoopTrace.scala b/modules/noop/shared/src/main/scala/NoopTrace.scala index 5afa8abc..d2e6b87b 100644 --- a/modules/noop/shared/src/main/scala/NoopTrace.scala +++ b/modules/noop/shared/src/main/scala/NoopTrace.scala @@ -26,13 +26,10 @@ final case class NoopTrace[F[_]: Applicative]() extends Trace[F] { override def log(event: String): F[Unit] = Applicative[F].unit - override def spanR(name: String, kernel: Option[Kernel] = None): Resource[F, F ~> F] = + override def spanR(name: String, options: Span.Options): Resource[F, F ~> F] = Resource.pure(FunctionK.id) - override def span[A](name: String)(k: F[A]): F[A] = - k - - override def span[A](name: String, kernel: Kernel)(k: F[A]): F[A] = + override def span[A](name: String, options: Span.Options)(k: F[A]): F[A] = k def traceId: F[Option[String]] = diff --git a/modules/opencensus/src/main/scala/OpenCensusSpan.scala b/modules/opencensus/src/main/scala/OpenCensusSpan.scala index 7b6ddfd4..a4e28bed 100644 --- a/modules/opencensus/src/main/scala/OpenCensusSpan.scala +++ b/modules/opencensus/src/main/scala/OpenCensusSpan.scala @@ -21,8 +21,9 @@ import scala.jdk.CollectionConverters._ private[opencensus] final case class OpenCensusSpan[F[_]: Sync]( tracer: Tracer, - span: io.opencensus.trace.Span -) extends Span[F] { + span: io.opencensus.trace.Span, + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import OpenCensusSpan._ @@ -56,17 +57,17 @@ private[opencensus] final case class OpenCensusSpan[F[_]: Sync]( Kernel(headers.toMap) } - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = Span.putErrorFields( - Resource - .makeCase(OpenCensusSpan.fromKernelWithSpan(tracer, name, kernel, span))( - OpenCensusSpan.finish - ) - .widen - ) - - override def span(name: String): Resource[F, Span[F]] = + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = Span.putErrorFields( - Resource.makeCase(OpenCensusSpan.child(this, name))(OpenCensusSpan.finish).widen + Resource + .makeCase(options.parentKernel match { + case None => OpenCensusSpan.child(this, name, options.spanCreationPolicy) + case Some(k) => + OpenCensusSpan.fromKernelWithSpan(tracer, name, k, span, options.spanCreationPolicy) + })( + OpenCensusSpan.finish + ) + .widen ) def traceId: F[Option[String]] = @@ -116,7 +117,8 @@ private[opencensus] object OpenCensusSpan { def child[F[_]: Sync]( parent: OpenCensusSpan[F], - name: String + name: String, + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[OpenCensusSpan[F]] = Sync[F] .delay( @@ -124,7 +126,7 @@ private[opencensus] object OpenCensusSpan { .spanBuilderWithExplicitParent(name, parent.span) .startSpan() ) - .map(OpenCensusSpan(parent.tracer, _)) + .map(OpenCensusSpan(parent.tracer, _, spanCreationPolicy)) def root[F[_]: Sync]( tracer: Tracer, @@ -138,13 +140,14 @@ private[opencensus] object OpenCensusSpan { .setSampler(sampler) .startSpan() ) - .map(OpenCensusSpan(tracer, _)) + .map(OpenCensusSpan(tracer, _, Span.Options.SpanCreationPolicy.Default)) def fromKernelWithSpan[F[_]: Sync]( tracer: Tracer, name: String, kernel: Kernel, - span: io.opencensus.trace.Span + span: io.opencensus.trace.Span, + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[OpenCensusSpan[F]] = Sync[F] .delay { val ctx = Tracing.getPropagationComponent.getB3Format @@ -154,7 +157,7 @@ private[opencensus] object OpenCensusSpan { .setParentLinks(List(span).asJava) .startSpan() } - .map(OpenCensusSpan(tracer, _)) + .map(OpenCensusSpan(tracer, _, spanCreationPolicy)) def fromKernel[F[_]: Sync]( tracer: Tracer, @@ -167,7 +170,7 @@ private[opencensus] object OpenCensusSpan { .extract(kernel, spanContextGetter) tracer.spanBuilderWithRemoteParent(name, ctx).startSpan() } - .map(OpenCensusSpan(tracer, _)) + .map(OpenCensusSpan(tracer, _, Span.Options.SpanCreationPolicy.Default)) def fromKernelOrElseRoot[F[_]]( tracer: Tracer, diff --git a/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala b/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala index 30f2efcd..fc3ecffd 100644 --- a/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala +++ b/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala @@ -27,8 +27,9 @@ private[opentelemetry] final case class OpenTelemetrySpan[F[_]: Sync]( otel: OTel, tracer: Tracer, span: TSpan, - prefix: Option[URI] -) extends Span[F] { + prefix: Option[URI], + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import OpenTelemetrySpan._ @@ -82,17 +83,18 @@ private[opentelemetry] final case class OpenTelemetrySpan[F[_]: Sync]( override def log(event: String): F[Unit] = Sync[F].delay(span.addEvent(event)).void - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = Span.putErrorFields( - Resource - .makeCase(OpenTelemetrySpan.fromKernelWithSpan(otel, tracer, name, kernel, span, prefix))( - OpenTelemetrySpan.finish - ) - .widen - ) - - override def span(name: String): Resource[F, Span[F]] = + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = Span.putErrorFields( - Resource.makeCase(OpenTelemetrySpan.child(this, name))(OpenTelemetrySpan.finish).widen + Resource + .makeCase(options.parentKernel match { + case None => OpenTelemetrySpan.child(this, name, options.spanCreationPolicy) + case Some(k) => + OpenTelemetrySpan + .fromKernelWithSpan(otel, tracer, name, k, span, prefix, options.spanCreationPolicy) + })( + OpenTelemetrySpan.finish + ) + .widen ) override def spanId: F[Option[String]] = @@ -138,7 +140,8 @@ private[opentelemetry] object OpenTelemetrySpan { def child[F[_]: Sync]( parent: OpenTelemetrySpan[F], - name: String + name: String, + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[OpenTelemetrySpan[F]] = Sync[F] .delay( @@ -147,7 +150,7 @@ private[opentelemetry] object OpenTelemetrySpan { .setParent(Context.current().`with`(parent.span)) .startSpan() ) - .map(OpenTelemetrySpan(parent.otel, parent.tracer, _, parent.prefix)) + .map(OpenTelemetrySpan(parent.otel, parent.tracer, _, parent.prefix, spanCreationPolicy)) def root[F[_]: Sync]( otel: OTel, @@ -161,7 +164,7 @@ private[opentelemetry] object OpenTelemetrySpan { .spanBuilder(name) .startSpan() ) - .map(OpenTelemetrySpan(otel, tracer, _, prefix)) + .map(OpenTelemetrySpan(otel, tracer, _, prefix, Span.Options.SpanCreationPolicy.Default)) def fromKernelWithSpan[F[_]: Sync]( sdk: OTel, @@ -169,14 +172,15 @@ private[opentelemetry] object OpenTelemetrySpan { name: String, kernel: Kernel, span: TSpan, - prefix: Option[URI] + prefix: Option[URI], + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[OpenTelemetrySpan[F]] = Sync[F] .delay { val ctx = sdk.getPropagators.getTextMapPropagator .extract(Context.current(), kernel, spanContextGetter) tracer.spanBuilder(name).setParent(ctx).addLink(span.getSpanContext).startSpan } - .map(OpenTelemetrySpan(sdk, tracer, _, prefix)) + .map(OpenTelemetrySpan(sdk, tracer, _, prefix, spanCreationPolicy)) def fromKernel[F[_]: Sync]( otel: OTel, @@ -191,7 +195,7 @@ private[opentelemetry] object OpenTelemetrySpan { .extract(Context.current(), kernel, spanContextGetter) tracer.spanBuilder(name).setParent(ctx).startSpan() } - .map(OpenTelemetrySpan(otel, tracer, _, prefix)) + .map(OpenTelemetrySpan(otel, tracer, _, prefix, Span.Options.SpanCreationPolicy.Default)) def fromKernelOrElseRoot[F[_]]( otel: OTel, diff --git a/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala b/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala index 66dcbc3a..665b71e1 100644 --- a/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala +++ b/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala @@ -34,8 +34,9 @@ private[xray] final case class XRaySpan[F[_]: Concurrent: Clock: Random]( startTime: FiniteDuration, fields: Ref[F, Map[String, Json]], children: Ref[F, List[JsonObject]], - sampled: Boolean -) extends Span[F] { + sampled: Boolean, + spanCreationPolicy: Span.Options.SpanCreationPolicy +) extends Span.Default[F] { import XRaySpan._ def put(fields: (String, TraceValue)*): F[Unit] = { @@ -53,8 +54,8 @@ private[xray] final case class XRaySpan[F[_]: Concurrent: Clock: Random]( def log(fields: (String, TraceValue)*): F[Unit] = Applicative[F].unit - def span(name: String): Resource[F, Span[F]] = - Resource.makeCase(XRaySpan.child(this, name))( + override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = + Resource.makeCase(XRaySpan.child(this, name, options.spanCreationPolicy))( XRaySpan.finish[F](_, entry, _) ) @@ -128,15 +129,6 @@ private[xray] final case class XRaySpan[F[_]: Concurrent: Clock: Random]( private def header: String = encodeHeader(xrayTraceId, Some(segmentId), sampled) - - override def span(name: String, kernel: Kernel): Resource[F, Span[F]] = - Resource.makeCase( - kernel.toHeaders - .get(Header) - .flatMap(parseHeader) - .traverse(XRaySpan.fromHeader(name, _, entry)) - .map(_.getOrElse(this)) - )(XRaySpan.finish[F](_, entry, _)) } private[xray] object XRaySpan { @@ -231,7 +223,8 @@ private[xray] object XRaySpan { fields = fields, children = children, parent = header.parentId.map(_.asLeft), - sampled = header.sampled + sampled = header.sampled, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) } @@ -285,13 +278,15 @@ private[xray] object XRaySpan { fields = fields, children = children, parent = None, - sampled = true + sampled = true, + spanCreationPolicy = Span.Options.SpanCreationPolicy.Default ) } def child[F[_]: Concurrent: Clock: Random]( parent: XRaySpan[F], - name: String + name: String, + spanCreationPolicy: Span.Options.SpanCreationPolicy ): F[XRaySpan[F]] = ( segmentId[F], @@ -308,7 +303,8 @@ private[xray] object XRaySpan { fields = fields, children = children, parent = Some(Right(parent)), - sampled = parent.sampled + sampled = parent.sampled, + spanCreationPolicy = spanCreationPolicy ) }