diff --git a/build.sbt b/build.sbt index dd2b55a..82db0d1 100644 --- a/build.sbt +++ b/build.sbt @@ -47,10 +47,11 @@ lazy val core = crossProject(JVMPlatform, JSPlatform) libraryDependencies ++= Seq( "org.tpolecat" %%% "natchez-core" % "0.3.4", "org.tpolecat" %%% "natchez-mtl" % "0.3.4", - "org.tpolecat" %%% "natchez-testkit" % "0.3.4" % Test, "org.typelevel" %%% "cats-tagless-core" % "0.15.0", "org.typelevel" %%% "cats-mtl" % "1.4.0", + "org.typelevel" %%% "log4cats-noop" % "2.6.0", "io.circe" %%% "circe-core" % "0.14.6", + "org.tpolecat" %%% "natchez-testkit" % "0.3.4" % Test, "org.typelevel" %% "munit-cats-effect" % "2.0.0-M4" % Test, "org.typelevel" %% "scalacheck-effect" % "2.0.0-M2" % Test, "org.typelevel" %% "scalacheck-effect-munit" % "2.0.0-M2" % Test, diff --git a/core/jvm/src/test/scala/com/dwolla/tracing/TraceInitializationExample.scala b/core/jvm/src/test/scala/com/dwolla/tracing/TraceInitializationExample.scala index b419a7a..0f53f24 100644 --- a/core/jvm/src/test/scala/com/dwolla/tracing/TraceInitializationExample.scala +++ b/core/jvm/src/test/scala/com/dwolla/tracing/TraceInitializationExample.scala @@ -1,16 +1,20 @@ package com.dwolla.tracing -import cats._ -import cats.data._ -import cats.effect.std._ -import cats.effect.syntax.all._ -import cats.effect.{IO, Resource, Trace => _, _} -import cats.syntax.all._ -import com.dwolla.tracing.instances._ +import cats.* +import cats.data.* +import cats.effect.std.* +import cats.effect.syntax.all.* +import cats.effect.{IO, Resource, Trace as _, *} +import cats.syntax.all.* +import com.dwolla.tracing.instances.* import natchez.{EntryPoint, Span, Trace} import natchez.mtl.localSpanForKleisli +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.noop.NoOpLogger object TraceInitializationExample extends IOApp.Simple { + private implicit def logger[F[_] : Applicative]: Logger[F] = NoOpLogger[F] + private def entryPoint[F[_] : Sync : Env]: Resource[F, EntryPoint[F]] = OpenTelemetryAtDwolla[F]("TraceInitializationSpec", DwollaEnvironment.Local) diff --git a/core/shared/src/main/scala/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala b/core/shared/src/main/scala-2.12/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala similarity index 83% rename from core/shared/src/main/scala/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala rename to core/shared/src/main/scala-2.12/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala index a503ec6..125aefe 100644 --- a/core/shared/src/main/scala/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala +++ b/core/shared/src/main/scala-2.12/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala @@ -2,9 +2,10 @@ package com.dwolla.tracing.syntax import cats.effect.{MonadCancelThrow, Resource} import cats.mtl.Local -import cats.syntax.all._ +import cats.syntax.all.* import com.dwolla.tracing.TraceResourceAcquisition import natchez.{EntryPoint, Span, Trace} +import org.typelevel.log4cats.noop.NoOpLogger trait ToTraceResourceLifecycleOps { implicit def toTraceResourceLifecycleOps[F[_], A](resource: Resource[F, A]): TraceResourceLifecycleOps[F, A] = @@ -42,10 +43,15 @@ class TraceResourceLifecycleOps[F[_], A](val resource: Resource[F, A]) extends A * and finalization phases of the passed `Resource[F, A]` are traced in * separate root spans. * + * Unfortunately on Scala 2.12, because of the way extention methods are + * encoded, it is not possible to add an implicit Logger[F] to warn if + * errors occurred when tracing. + * * @param name the base of the span name to use for acquisition (the finalization span will append ".finalize" to this value to form its span name) * @param entryPoint an `EntryPoint[F]` capable of creating new root spans * @param F a `MonadCancelThrow[F]` instance for the given effect type - * @param L a `Local[F, Span[F]]` instance for the given effect type + * @param L1 an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L2 an implicit `Logger[F]` instance for the given effect type * @return the input `Resource[F, A]` with its acquisition and release phases wrapped in Natchez root spans */ def traceResourceLifecycleInRootSpans(name: String, @@ -53,5 +59,5 @@ class TraceResourceLifecycleOps[F[_], A](val resource: Resource[F, A]) extends A (implicit F: MonadCancelThrow[F], L: Local[F, Span[F]]): Resource[F, A] = - TraceResourceAcquisition(entryPoint, name, resource) + TraceResourceAcquisition(entryPoint, name, resource)(implicitly, implicitly, NoOpLogger[F]) } diff --git a/core/shared/src/main/scala-2.13+/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala b/core/shared/src/main/scala-2.13+/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala new file mode 100644 index 0000000..53d2cd9 --- /dev/null +++ b/core/shared/src/main/scala-2.13+/com/dwolla/tracing/syntax/TraceResourceLifecycleOps.scala @@ -0,0 +1,68 @@ +package com.dwolla.tracing.syntax + +import cats.effect.{MonadCancelThrow, Resource} +import cats.mtl.Local +import cats.syntax.all.* +import com.dwolla.tracing.TraceResourceAcquisition +import natchez.{EntryPoint, Span, Trace} +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.noop.NoOpLogger + +trait ToTraceResourceLifecycleOps { + implicit def toTraceResourceLifecycleOps[F[_], A](resource: Resource[F, A]): TraceResourceLifecycleOps[F, A] = + new TraceResourceLifecycleOps(resource) +} + +class TraceResourceLifecycleOps[F[_], A](val resource: Resource[F, A]) extends AnyVal { + /** + * Wrap the acquisition and release phases of the given `Resource[F, A]` + * in children spans of the given `Trace[F]`. + * + * Note: this requires a `Trace[F]` built from `Local[F, Span[F]]` + * (e.g. using `natchez.mtl.natchezMtlTraceForLocal`) or + * `Trace[Kleisli[F, Span[F], *]]`. Notably, the `natchez.Trace.ioTrace` + * instance is *not* compatible with this method. + * + * @param name the base of the span name to use for acquisition (the finalization span will append ".finalize" to this value to form its span name) + * @param F a `MonadCancelThrow[F]` instance for the given effect type + * @param T a `Trace[F]` for the given effect type. See the note in the description above; not every `Trace[F]` instance will work + * @return the input `Resource[F, A]` with its acquisition and release phases wrapped in Natchez spans + */ + def traceResourceLifecycleAs(name: String) + (implicit F: MonadCancelThrow[F], + T: Trace[F]): Resource[F, A] = + Resource { + Trace[F].spanR(name) + .use(_(resource.allocated)) + .map { case (a, finalizer) => + a -> Trace[F].spanR(s"$name.finalize").use(_(finalizer)) + } + } + + @deprecated("use variant accepting Logger[F]", "0.2.5") + def traceResourceLifecycleInRootSpans(name: String, + entryPoint: EntryPoint[F], + F: MonadCancelThrow[F], + L: Local[F, Span[F]]): Resource[F, A] = + TraceResourceAcquisition(entryPoint, name, resource)(F, L, NoOpLogger(F)) + + /** + * Given an entrypoint and a root span name, ensure that the acquisition + * and finalization phases of the passed `Resource[F, A]` are traced in + * separate root spans. + * + * @param name the base of the span name to use for acquisition (the finalization span will append ".finalize" to this value to form its span name) + * @param entryPoint an `EntryPoint[F]` capable of creating new root spans + * @param F a `MonadCancelThrow[F]` instance for the given effect type + * @param L1 an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L2 an implicit `Logger[F]` instance for the given effect type + * @return the input `Resource[F, A]` with its acquisition and release phases wrapped in Natchez root spans + */ + def traceResourceLifecycleInRootSpans(name: String, + entryPoint: EntryPoint[F]) + (implicit + F: MonadCancelThrow[F], + L1: Local[F, Span[F]], + L2: Logger[F]): Resource[F, A] = + TraceResourceAcquisition(entryPoint, name, resource) +} diff --git a/core/shared/src/main/scala/com/dwolla/tracing/TraceResourceAcquisition.scala b/core/shared/src/main/scala/com/dwolla/tracing/TraceResourceAcquisition.scala index 995ffab..276048a 100644 --- a/core/shared/src/main/scala/com/dwolla/tracing/TraceResourceAcquisition.scala +++ b/core/shared/src/main/scala/com/dwolla/tracing/TraceResourceAcquisition.scala @@ -1,11 +1,13 @@ package com.dwolla.tracing -import cats.effect.{Trace => _, _} +import cats.effect.{Trace as _, *} import cats.mtl.Local -import cats.syntax.all._ -import com.dwolla.tracing.syntax._ -import natchez._ -import natchez.mtl._ +import cats.syntax.all.* +import com.dwolla.tracing.syntax.* +import natchez.* +import natchez.mtl.* +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.noop.NoOpLogger object TraceResourceAcquisition { /** @@ -17,14 +19,16 @@ object TraceResourceAcquisition { * @param entryPoint a Natchez `EntryPoint[F]` used to create the new root spans * @param name the name of the root span. a good default might be `"app initialization"` * @param resource function that returns a resource to be traced using the ambient `Trace[F]` passed to the function - * @param L an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L1 an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L2 an implicit `Logger[F]` instance for the given effect type * @tparam F the effect type in which to run * @tparam A inner type of the `Resource[F, A]` * @return `Resource[F, A]` that is the same as the one returned by the `resource` function, but with tracing introduced */ def apply[F[_] : MonadCancelThrow, A](entryPoint: EntryPoint[F], name: String) (resource: Trace[F] => Resource[F, A]) - (implicit L: Local[F, Span[F]]): Resource[F, A] = + (implicit L1: Local[F, Span[F]], + L2: Logger[F]): Resource[F, A] = TraceResourceAcquisition(entryPoint, name, Span.Options.Defaults)(resource) /** @@ -37,14 +41,16 @@ object TraceResourceAcquisition { * @param name the name of the root span. a good default might be `"app initialization"` * @param resource function that returns a resource to be traced using the ambient `Trace[F]` passed to the function * @param options options to set on the newly created root span - * @param L an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L1 an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L2 an implicit `Logger[F]` instance for the given effect type * @tparam F the effect type in which to run * @tparam A inner type of the `Resource[F, A]` * @return `Resource[F, A]` that is the same as the one returned by the `resource` function, but with tracing introduced */ def apply[F[_] : MonadCancelThrow, A](entryPoint: EntryPoint[F], name: String, options: Span.Options) (resource: Trace[F] => Resource[F, A]) - (implicit L: Local[F, Span[F]]): Resource[F, A] = + (implicit L1: Local[F, Span[F]], + L2: Logger[F]): Resource[F, A] = TraceResourceAcquisition(entryPoint, name, options, resource(natchezMtlTraceForLocal)) /** @@ -55,13 +61,16 @@ object TraceResourceAcquisition { * @param entryPoint a Natchez `EntryPoint[F]` used to create the new root spans * @param name the name of the root span. a good default might be `"app initialization"` * @param resource a resource that was traced using the `Local[F, Span[F]]` passed implicitly as `L` - * @param L an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L1 an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L2 an implicit `Logger[F]` instance for the given effect type * @tparam F the effect type in which to run * @tparam A inner type of the `Resource[F, A]` * @return `Resource[F, A]` that is the same as the one returned by the `resource` function, but with tracing introduced */ def apply[F[_] : MonadCancelThrow, A](entryPoint: EntryPoint[F], name: String, resource: Resource[F, A]) - (implicit L: Local[F, Span[F]]): Resource[F, A] = + (implicit L1: Local[F, Span[F]], + L2: Logger[F], + ): Resource[F, A] = TraceResourceAcquisition(entryPoint, name, Span.Options.Defaults, resource) /** @@ -73,7 +82,8 @@ object TraceResourceAcquisition { * @param name the name of the root span. a good default might be `"app initialization"` * @param resource a resource that was traced using the `Local[F, Span[F]]` passed implicitly as `L` * @param options options to set on the newly created root span - * @param L an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L1 an implicit `Local[F, Span[F]]` instance for the given effect type + * @param L2 an implicit `Logger[F]` instance for the given effect type * @tparam F the effect type in which to run * @tparam A inner type of the `Resource[F, A]` * @return `Resource[F, A]` that is the same as the one returned by the `resource` function, but with tracing introduced @@ -82,7 +92,8 @@ object TraceResourceAcquisition { name: String, options: Span.Options, resource: Resource[F, A]) - (implicit L: Local[F, Span[F]]): Resource[F, A] = + (implicit L1: Local[F, Span[F]], + L2: Logger[F]): Resource[F, A] = Resource { entryPoint .runInRoot(name, options) { @@ -97,4 +108,39 @@ object TraceResourceAcquisition { } } } + + @deprecated("use variant accepting Logger[F]", "0.2.5") + def apply[F[_], A](entryPoint: EntryPoint[F], + name: String, + options: Span.Options, + resource: Resource[F, A], + F: MonadCancelThrow[F], + L: Local[F, Span[F]]): Resource[F, A] = + apply(entryPoint, name, options, resource)(F, L, NoOpLogger(F)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + def apply[F[_], A](entryPoint: EntryPoint[F], + name: String, + resource: Resource[F, A], + F: MonadCancelThrow[F], + L: Local[F, Span[F]]): Resource[F, A] = + apply(entryPoint, name, Span.Options.Defaults, resource)(F, L, NoOpLogger(F)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + def apply[F[_], A](entryPoint: EntryPoint[F], + name: String, + options: Span.Options, + resource: Trace[F] => Resource[F, A], + F: MonadCancelThrow[F], + L: Local[F, Span[F]]): Resource[F, A] = + apply(entryPoint, name, options, resource(natchezMtlTraceForLocal(L, F)))(F, L, NoOpLogger(F)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + def apply[F[_], A](entryPoint: EntryPoint[F], + name: String, + resource: Trace[F] => Resource[F, A], + F: MonadCancelThrow[F], + L: Local[F, Span[F]]): Resource[F, A] = + apply(entryPoint, name, Span.Options.Defaults, resource, F, L) + } diff --git a/core/shared/src/main/scala/com/dwolla/tracing/syntax/EntryPointRootScope.scala b/core/shared/src/main/scala/com/dwolla/tracing/syntax/EntryPointRootScope.scala index a133f71..df8d829 100644 --- a/core/shared/src/main/scala/com/dwolla/tracing/syntax/EntryPointRootScope.scala +++ b/core/shared/src/main/scala/com/dwolla/tracing/syntax/EntryPointRootScope.scala @@ -1,8 +1,13 @@ package com.dwolla.tracing.syntax -import cats.effect.MonadCancelThrow -import cats.mtl._ -import natchez._ +import cats.effect.syntax.all.* +import cats.effect.kernel.Poll +import cats.syntax.all.* +import cats.effect.{MonadCancelThrow, Resource} +import cats.mtl.* +import natchez.* +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.noop.NoOpLogger trait ToEntryPointRootScopeOps { implicit def toEntryPointRootScopeOps[F[_]](entryPoint: EntryPoint[F]): EntryPointRootScopeOps[F] = @@ -10,45 +15,92 @@ trait ToEntryPointRootScopeOps { } class EntryPointRootScopeOps[F[_]](val entryPoint: EntryPoint[F]) extends AnyVal { + @deprecated("use variant accepting Logger[F]", "0.2.5") + private[syntax] def runInRoot[A](name: String, fa: F[A], F: MonadCancelThrow[F], L: Local[F, Span[F]]): F[A] = + runInRoot(name)(fa)(F, L, NoOpLogger(F)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + private[syntax] def runInRoot[A](name: String, options: Span.Options, fa: F[A], F: MonadCancelThrow[F], L: Local[F, Span[F]]): F[A] = + safeSpan(fa)(_.root(name, options))(F, L, NoOpLogger(F)) + def runInRoot[A](name: String) (fa: F[A]) (implicit F: MonadCancelThrow[F], - L: Local[F, Span[F]]): F[A] = + L1: Local[F, Span[F]], + L2: Logger[F], + ): F[A] = runInRoot(name, Span.Options.Defaults)(fa) def runInRoot[A](name: String, options: Span.Options) (fa: F[A]) (implicit F: MonadCancelThrow[F], - L: Local[F, Span[F]]): F[A] = - entryPoint - .root(name, options) - .use(Local[F, Span[F]].scope(fa)) + L1: Local[F, Span[F]], + L2: Logger[F], + ): F[A] = + safeSpan(fa)(_.root(name, options)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + private[syntax] def runInContinuation[A](name: String, kernel: Kernel, fa: F[A], F: MonadCancelThrow[F], L: Local[F, Span[F]]): F[A] = + runInContinuation(name, kernel, Span.Options.Defaults)(fa)(F, L, NoOpLogger(F)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + private[syntax] def runInContinuation[A](name: String, kernel: Kernel, options: Span.Options, fa: F[A], F: MonadCancelThrow[F], L: Local[F, Span[F]]): F[A] = + safeSpan(fa)(_.continue(name, kernel, options))(F, L, NoOpLogger(F)) def runInContinuation[A](name: String, kernel: Kernel) (fa: F[A]) (implicit F: MonadCancelThrow[F], - L: Local[F, Span[F]]): F[A] = + L1: Local[F, Span[F]], + L2: Logger[F], + ): F[A] = runInContinuation(name, kernel, Span.Options.Defaults)(fa) def runInContinuation[A](name: String, kernel: Kernel, options: Span.Options) (fa: F[A]) (implicit F: MonadCancelThrow[F], - L: Local[F, Span[F]]): F[A] = - entryPoint - .continue(name, kernel, options) - .use(Local[F, Span[F]].scope(fa)) + L1: Local[F, Span[F]], + L2: Logger[F], + ): F[A] = + safeSpan(fa)(_.continue(name, kernel, options)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + private[syntax] def runInContinuationOrElseRoot[A](name: String, kernel: Kernel, fa: F[A], F: MonadCancelThrow[F], L: Local[F, Span[F]]): F[A] = + runInContinuationOrElseRoot(name, kernel, Span.Options.Defaults)(fa)(F, L, NoOpLogger(F)) + + @deprecated("use variant accepting Logger[F]", "0.2.5") + private[syntax] def runInContinuationOrElseRoot[A](name: String, kernel: Kernel, options: Span.Options, fa: F[A], F: MonadCancelThrow[F], L: Local[F, Span[F]]): F[A] = + safeSpan(fa)(_.continueOrElseRoot(name, kernel, options))(F, L, NoOpLogger(F)) def runInContinuationOrElseRoot[A](name: String, kernel: Kernel) - (fa: F[A]) - (implicit F: MonadCancelThrow[F], - L: Local[F, Span[F]]): F[A] = + (fa: F[A]) + (implicit F: MonadCancelThrow[F], + L1: Local[F, Span[F]], + L2: Logger[F], + ): F[A] = runInContinuationOrElseRoot(name, kernel, Span.Options.Defaults)(fa) def runInContinuationOrElseRoot[A](name: String, kernel: Kernel, options: Span.Options) - (fa: F[A]) - (implicit F: MonadCancelThrow[F], - L: Local[F, Span[F]]): F[A] = - entryPoint - .continueOrElseRoot(name, kernel, options) + (fa: F[A]) + (implicit F: MonadCancelThrow[F], + L1: Local[F, Span[F]], + L2: Logger[F], + ): F[A] = + safeSpan(fa)(_.continueOrElseRoot(name, kernel, options)) + + private def safeSpan[A](fa: F[A]) + (f: EntryPoint[F] => Resource[F, Span[F]]) + (implicit F: MonadCancelThrow[F], + L1: Local[F, Span[F]], + L2: Logger[F]): F[A] = + Resource.applyFull { (poll: Poll[F]) => + poll { + f(entryPoint) + .handleErrorWith(Logger[F].warn(_: Throwable)("an error occurred initializing tracing").as(Span.noop[F]).toResource) + .allocatedCase + }.map { + case (a, release) => + (a, release.andThen(_.handleErrorWith(Logger[F].warn(_)("an error occurred initializing tracing")))) + } + } .use(Local[F, Span[F]].scope(fa)) } diff --git a/core/shared/src/test/scala/com/dwolla/tracing/EntryPointRootScopeOpsSpec.scala b/core/shared/src/test/scala/com/dwolla/tracing/EntryPointRootScopeOpsSpec.scala index 38cdcdc..02a4e2c 100644 --- a/core/shared/src/test/scala/com/dwolla/tracing/EntryPointRootScopeOpsSpec.scala +++ b/core/shared/src/test/scala/com/dwolla/tracing/EntryPointRootScopeOpsSpec.scala @@ -1,16 +1,17 @@ package com.dwolla.tracing -import cats.effect.{MonadCancelThrow, Trace => _} +import cats.ApplicativeThrow +import cats.effect.{IO, MonadCancelThrow, Resource, Trace as _} import cats.mtl.Local -import com.dwolla.tracing.syntax._ +import cats.syntax.all.* +import com.dwolla.tracing.syntax.* +import natchez.* import natchez.InMemory.Lineage.Root -import natchez.InMemory.NatchezCommand._ +import natchez.InMemory.NatchezCommand.* import natchez.InMemory.{Lineage, NatchezCommand} -import natchez._ -import natchez.mtl._ +import natchez.mtl.* class EntryPointRootScopeOpsSpec extends InMemorySuite { - traceTest("EntryPointRootScopeOps", new TraceTest { override def program[F[_] : MonadCancelThrow](entryPoint: EntryPoint[F]) (implicit L: Local[F, Span[F]]): F[Unit] = @@ -26,4 +27,64 @@ class EntryPointRootScopeOpsSpec extends InMemorySuite { ) }) + testWithLocalSpan("runInRoot succeeds even if finalization fails") { implicit L => + new FailOnFinalizationEntryPoint[IO].runInRoot("moot") { + IO.unit + } + } + + testWithLocalSpan("runInRoot succeeds even if initialization fails") { implicit L => + new FailOnInitializationEntryPoint[IO].runInRoot("moot") { + IO.pure(42) + } + .map(assertEquals(_, 42)) + } + + testWithLocalSpan("runInContinuation succeeds even if finalization fails") { implicit L => + new FailOnFinalizationEntryPoint[IO].runInContinuation("moot", Kernel(Map.empty)) { + IO.unit + } + } + + testWithLocalSpan("runInContinuation succeeds even if initialization fails") { implicit L => + new FailOnInitializationEntryPoint[IO].runInContinuation("moot", Kernel(Map.empty)) { + IO.pure(42) + } + .map(assertEquals(_, 42)) + } + + testWithLocalSpan("runInContinuationOrElseRoot succeeds even if finalization fails") { implicit L => + new FailOnFinalizationEntryPoint[IO].runInContinuationOrElseRoot("moot", Kernel(Map.empty)) { + IO.unit + } + } + + testWithLocalSpan("runInContinuationOrElseRoot succeeds even if initialization fails") { implicit L => + new FailOnInitializationEntryPoint[IO].runInContinuationOrElseRoot("moot", Kernel(Map.empty)) { + IO.pure(42) + } + .map(assertEquals(_, 42)) + } +} + +class FailOnFinalizationEntryPoint[F[_] : ApplicativeThrow] extends EntryPoint[F] { + private def failOnFinalization: Resource[F, Span[F]] = + Resource.make(Span.noop[F].pure[F]) { _ => + new RuntimeException("boom").raiseError[F, Unit] + } + + override def root(name: String, options: Span.Options): Resource[F, Span[F]] = failOnFinalization + override def continue(name: String, kernel: Kernel, options: Span.Options): Resource[F, Span[F]] = failOnFinalization + override def continueOrElseRoot(name: String, kernel: Kernel, options: Span.Options): Resource[F, Span[F]] = failOnFinalization +} + +class FailOnInitializationEntryPoint[F[_] : ApplicativeThrow] extends EntryPoint[F] { + private def failOnInitialization: Resource[F, Span[F]] = + Resource.make(new RuntimeException("boom").raiseError[F, Span[F]]) { _ => + ().pure[F] + } + + override def root(name: String, options: Span.Options): Resource[F, Span[F]] = failOnInitialization + override def continue(name: String, kernel: Kernel, options: Span.Options): Resource[F, Span[F]] = failOnInitialization + override def continueOrElseRoot(name: String, kernel: Kernel, options: Span.Options): Resource[F, Span[F]] = failOnInitialization } diff --git a/core/shared/src/test/scala/com/dwolla/tracing/InMemorySuite.scala b/core/shared/src/test/scala/com/dwolla/tracing/InMemorySuite.scala index 72d1718..7b356ce 100644 --- a/core/shared/src/test/scala/com/dwolla/tracing/InMemorySuite.scala +++ b/core/shared/src/test/scala/com/dwolla/tracing/InMemorySuite.scala @@ -8,8 +8,12 @@ import cats.syntax.all.* import munit.CatsEffectSuite import natchez.InMemory.{Lineage, NatchezCommand} import natchez.* +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.noop.NoOpLogger trait InMemorySuite extends CatsEffectSuite { + protected implicit def logger[F[_] : Applicative]: Logger[F] = NoOpLogger[F] + trait TraceTest { def program[F[_] : MonadCancelThrow](entryPoint: EntryPoint[F]) (implicit L: Local[F, Span[F]]): F[Unit] @@ -23,6 +27,13 @@ trait InMemorySuite extends CatsEffectSuite { test(s"$name - IOLocal")(testTraceIoLocal(implicit L => tt.program[IO], tt.expectedHistory)) } + def testWithLocalSpan[A](name: String)(body: Local[IO, Span[IO]] => IO[A]): Unit = + test(name) { + IOLocal(Span.noop[IO]) + .map(localViaIoLocal(_)) + .flatMap(body) + } + implicit def localSpan[F[_]](implicit F: MonadCancel[F, ?]): Local[Kleisli[F, Span[F], *], Span[Kleisli[F, Span[F], *]]] = new Local[Kleisli[F, Span[F], *], Span[Kleisli[F, Span[F], *]]] { override def local[A](fa: Kleisli[F, Span[F], A]) @@ -67,7 +78,7 @@ trait InMemorySuite extends CatsEffectSuite { } // from https://github.com/armanbilge/oxidized/blob/412be9cd0a60b901fd5f9157ea48bda8632c5527/core/src/main/scala/oxidized/instances/io.scala#L34-L43 - private def localViaIoLocal[E](implicit ioLocal: IOLocal[E]): Local[IO, E] = + protected def localViaIoLocal[E](implicit ioLocal: IOLocal[E]): Local[IO, E] = new Local[IO, E] { override def local[A](fa: IO[A])(f: E => E): IO[A] = ioLocal.get.flatMap { initial =>