From 59a0fcf9a3f1533e0ef426f3e15e8b4fdc262d3c Mon Sep 17 00:00:00 2001 From: Ivan Malyshev Date: Tue, 15 Nov 2022 19:31:52 +0300 Subject: [PATCH 1/2] implement zio2 tofu logging like zio1, write example --- .github/workflows/ci.yml | 2 +- build.sbt | 27 ++++++++-- examples-zio2/src/main/resources/logback.xml | 13 +++++ .../example/logging/ZMakeSimpleExample.scala | 26 ++++++++++ .../example/logging/impl/FooService.scala | 21 ++++++++ .../example/logging/impl/SimpleContext.scala | 23 +++++++++ .../tofu/logging/impl/ZContextLogging.scala | 34 +++++++++++++ .../impl/ZUniversalContextLogging.scala | 50 +++++++++++++++++++ .../tofu/logging/zlogs/ContextProvider.scala | 8 +++ .../scala/tofu/logging/zlogs/ZLogging.scala | 48 ++++++++++++++++++ .../scala/tofu/logging/zlogs/package.scala | 10 ++++ .../main/scala/tofu/logging/zlogs/zlogs.scala | 41 +++++++++++++++ project/Dependencies.scala | 5 ++ 13 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 examples-zio2/src/main/resources/logback.xml create mode 100644 examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala create mode 100644 examples-zio2/src/main/scala/tofu/example/logging/impl/FooService.scala create mode 100644 examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala create mode 100644 modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala create mode 100644 modules/zio2/logging/src/main/scala/tofu/logging/impl/ZUniversalContextLogging.scala create mode 100644 modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala create mode 100644 modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ZLogging.scala create mode 100644 modules/zio2/logging/src/main/scala/tofu/logging/zlogs/package.scala create mode 100644 modules/zio2/logging/src/main/scala/tofu/logging/zlogs/zlogs.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c309a3c5..a0e7f48c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: run: sbt ++${{ matrix.scala }} docs/mdoc - name: Compress target directories - run: tar cf targets.tar modules/logging/interop/shapeless/target modules/kernel/target modules/logging/target examples/target modules/doobie/core-ce3/target modules/concurrent/target modules/logging/layout/target modules/kernelCE2Interop/target modules/doobie/core/target modules/zio/target modules/fs2-ce3/target examples-ce3/target tofu-docs/target modules/zio/logging/target modules/core3/target modules/kernel/interop/cats-mtl/target modules/config/target modules/doobie/logging-ce3/target modules/kernelCE3Interop/target modules/zio/core/target modules/streams/target modules/logging/structured/target modules/logging/interop/logstash-logback/target modules/logging/interop/log4cats/target modules/doobie/logging/target modules/enums/target target modules/fs2/target modules/logging/derivation/target modules/logging/interop/refined/target modules/higherKindCore/target modules/derivation/target modules/core/target modules/observable/target modules/memo/target modules/env/target project/target + run: tar cf targets.tar modules/logging/interop/shapeless/target modules/kernel/target modules/zio2/logging/target modules/logging/target examples/target modules/doobie/core-ce3/target modules/concurrent/target modules/logging/layout/target modules/kernelCE2Interop/target modules/doobie/core/target modules/zio/target modules/fs2-ce3/target examples-ce3/target tofu-docs/target modules/zio/logging/target modules/core3/target modules/kernel/interop/cats-mtl/target modules/config/target modules/doobie/logging-ce3/target modules/kernelCE3Interop/target modules/zio/core/target modules/streams/target modules/logging/structured/target modules/logging/interop/logstash-logback/target modules/logging/interop/log4cats/target modules/doobie/logging/target modules/enums/target target examples-zio2/target modules/fs2/target modules/logging/derivation/target modules/logging/interop/refined/target modules/higherKindCore/target modules/derivation/target modules/core/target modules/observable/target modules/memo/target modules/env/target project/target - name: Upload target directories uses: actions/upload-artifact@v2 diff --git a/build.sbt b/build.sbt index af4a6ab70..1b88b20d3 100644 --- a/build.sbt +++ b/build.sbt @@ -260,6 +260,26 @@ lazy val zioLogging = ) .dependsOn(loggingStr, loggingDer % "test", zioCore % Test) +lazy val zio2Logging = + project + .in(file("modules/zio2/logging")) + .settings( + defaultSettings, + name := "tofu-zio2-logging", + libraryDependencies ++= List(zio2, slf4j, logback % Test, zio2Test, zio2TestSbt) + ) + .dependsOn(loggingStr, loggingDer % "test") + +lazy val examplesZIO2 = + project + .in(file("examples-zio2")) + .settings( + defaultSettings, + name := "tofu-examples-zio2", + noPublishSettings + ) + .dependsOn(zio2Logging, loggingDer, loggingLayout) + lazy val zioInterop = project .in(file("modules/zio")) .settings( @@ -373,7 +393,7 @@ lazy val ce3CoreModules = Vector( ) lazy val commonModules = - Vector(observable, logging, enums, config, zioInterop, fs2Interop, doobie, doobieLogging) + Vector(observable, logging, enums, config, zioInterop, zio2Logging, fs2Interop, doobie, doobieLogging) lazy val ce3CommonModules = Vector(loggingStr, loggingDer, loggingLayout, doobieCE3, doobieLoggingCE3, fs2CE3Interop) @@ -407,9 +427,8 @@ lazy val tofu = project name := "tofu" ) .aggregate( - (coreModules ++ commonModules ++ ce3CoreModules ++ ce3CommonModules :+ docs :+ examples :+ examplesCE3).map(x => - x: ProjectReference - ): _* + (coreModules ++ commonModules ++ ce3CoreModules ++ ce3CommonModules :+ docs :+ examples :+ examplesCE3 :+ examplesZIO2) + .map(x => x: ProjectReference): _* ) .dependsOn(coreModules.map(x => x: ClasspathDep[ProjectReference]): _*) diff --git a/examples-zio2/src/main/resources/logback.xml b/examples-zio2/src/main/resources/logback.xml new file mode 100644 index 000000000..15add0b5c --- /dev/null +++ b/examples-zio2/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + UTF-8 + + + + + + + + diff --git a/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala b/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala new file mode 100644 index 000000000..c05837cbf --- /dev/null +++ b/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala @@ -0,0 +1,26 @@ +package tofu.example.logging + +import tofu.example.logging.impl.{FooService, SimpleContext} +import tofu.logging.zlogs.ZLogging +import zio._ + +object ZMakeSimpleExample extends ZIOAppDefault { + + val tasks = (1 to 3) + .map(i => + for { + id <- Random.nextIntBetween(1000, 4000) + _ <- ZIO.serviceWithZIO[SimpleContext](_.set(id.toHexString)) + _ <- ZIO.serviceWithZIO[FooService](_.foo(i)) + } yield () + ) + + override def run = + ZIO + .collectAllParDiscard(tasks) + .provide( + FooService.layer, + ZLogging.Make.layerPlainWithContext, + SimpleContext.layer + ) +} diff --git a/examples-zio2/src/main/scala/tofu/example/logging/impl/FooService.scala b/examples-zio2/src/main/scala/tofu/example/logging/impl/FooService.scala new file mode 100644 index 000000000..a952989ce --- /dev/null +++ b/examples-zio2/src/main/scala/tofu/example/logging/impl/FooService.scala @@ -0,0 +1,21 @@ +package tofu.example.logging.impl + +import tofu.logging.zlogs.{ULogging, ZLogging} +import zio._ + +class FooService( + logs: ZLogging.Make +) { + private val logger: ULogging = logs.forService[FooService] + + def foo(i: Int): UIO[Int] = + for { + _ <- logger.debug("Starting Foo! i={}", i) + _ <- Clock.sleep(i.seconds) + _ <- logger.info("Foo completed!") + } yield i +} + +object FooService { + val layer: URLayer[ZLogging.Make, FooService] = ZLayer.fromFunction(new FooService(_)) +} diff --git a/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala b/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala new file mode 100644 index 000000000..807d74329 --- /dev/null +++ b/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala @@ -0,0 +1,23 @@ +package tofu.example.logging.impl + +import tofu.logging.{Loggable, LoggedValue} +import tofu.logging.zlogs.ContextProvider +import zio.{FiberRef, UIO, ULayer, ZLayer} + +class SimpleContext(ref: FiberRef[String]) extends ContextProvider { + private implicit val requestIdLoggable: Loggable[String] = + Loggable.stringValue.named("requestId") + + override def getCtx: UIO[LoggedValue] = ref.get.map(x => x) + + def set(requestId: String): UIO[Unit] = + ref.set(requestId) +} + +object SimpleContext { + val layer: ULayer[SimpleContext] = ZLayer.scoped( + FiberRef + .make[String]("") + .map(new SimpleContext(_)) + ) +} diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala new file mode 100644 index 000000000..4539958ee --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala @@ -0,0 +1,34 @@ +package tofu.logging.impl + +import org.slf4j.{Logger, Marker} +import tofu.logging.zlogs.ZLogging +import tofu.logging.{Loggable, LoggedValue, Logging} +import zio._ + +class ZContextLogging[R, Ctx: Loggable](logger: => Logger, ctxLog: URIO[R, Ctx]) extends ZLogging[R] { + override def write(level: Logging.Level, message: String, values: LoggedValue*): URIO[R, Unit] = + ctxLog.flatMap { ctx => + ZIO.succeed( + ZUniversalContextLogging.write(level, logger, message, ctx, values, Nil) + ) + } + + override def writeMarker(level: Logging.Level, message: String, marker: Marker, values: LoggedValue*): URIO[R, Unit] = + ctxLog.flatMap { ctx => + ZIO.succeed( + ZUniversalContextLogging.write(level, logger, message, ctx, values, marker :: Nil) + ) + } + + override def writeCause( + level: Logging.Level, + message: String, + cause: Throwable, + values: LoggedValue* + ): URIO[R, Unit] = + ctxLog.flatMap { ctx => + ZIO.succeed( + ZUniversalContextLogging.writeCause(level, logger, cause, message, ctx, values, Nil) + ) + } +} diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZUniversalContextLogging.scala b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZUniversalContextLogging.scala new file mode 100644 index 000000000..c3ed78d85 --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZUniversalContextLogging.scala @@ -0,0 +1,50 @@ +package tofu.logging.impl + +import org.slf4j.{Logger, LoggerFactory, Marker} +import tofu.logging._ +import zio.URIO + +class ZUniversalContextLogging[R, Ctx: Loggable](name: String, ctxLog: URIO[R, Ctx]) + extends ZContextLogging[R, Ctx](LoggerFactory.getLogger(name), ctxLog) + +object ZUniversalContextLogging { + + private[logging] def write( + level: Logging.Level, + logger: Logger, + message: => String, + ctx: LoggedValue, + values: Seq[LoggedValue], + markers: List[Marker] = Nil + ): Unit = { + if (UniversalLogging.enabled(level, logger)) + UniversalLogging.writeMarker( + level = level, + logger = logger, + marker = ContextMarker(ctx, markers), + message = message, + values = values + ) + } + + private[logging] def writeCause( + level: Logging.Level, + logger: Logger, + cause: Throwable, + message: => String, + ctx: LoggedValue, + values: Seq[LoggedValue], + markers: List[Marker] = Nil + ): Unit = { + if (UniversalLogging.enabled(level, logger)) + UniversalLogging.writeMarkerCause( + level = level, + logger = logger, + marker = ContextMarker(ctx, markers), + cause = cause, + message = message, + values = values + ) + } + +} diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala new file mode 100644 index 000000000..3c1add113 --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala @@ -0,0 +1,8 @@ +package tofu.logging.zlogs + +import tofu.logging.LoggedValue +import zio.UIO + +trait ContextProvider { + def getCtx: UIO[LoggedValue] +} diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ZLogging.scala b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ZLogging.scala new file mode 100644 index 000000000..56587e42e --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ZLogging.scala @@ -0,0 +1,48 @@ +package tofu.logging.zlogs + +import tofu.logging._ +import tofu.logging.impl.ZUniversalContextLogging +import zio._ + +import scala.annotation.unused + +object ZLogging { + + type ZMake[R] = Logging.Make[URIO[R, *]] + + type Make = Logging.Make[UIO] + + def empty[R]: ZLogging[R] = new Logging[URIO[R, *]] { + override def write(level: Logging.Level, message: String, values: LoggedValue*): URIO[R, Unit] = ZIO.unit + } + + /** Unlike [[ZLogs]] layers, these helpers are effect-less. + */ + object Make { + + /** Creates layer with a helper producing simple [[tofu.logging.Logging]]. + */ + val layerPlain: ULayer[ZLogging.Make] = ZLayer.succeed(new ZUniversalContextLogging(_, ZIO.unit)) + + /** Creates layer with a helper [[ZMake]] producing logging `ZLogging[R]`. Logging methods will add the context from + * the ZIO environment. + * + * @tparam R + * the context, an environment of the logging methods. + */ + def layerContextual[R: Loggable](implicit @unused RT: Tag[R]): ULayer[ZLogging.ZMake[R]] = ZLayer.succeed( + new ZUniversalContextLogging(_, ZIO.service[R]) + ) + + /** Creates layer with a helper [[Make]] producing plain `ULogging` with encapsulated context. You have to provide + * an implementation of `ContextProvider`. + */ + def layerPlainWithContext: URLayer[ContextProvider, ZLogging.Make] = + ZLayer( + ZIO.serviceWith[ContextProvider](cp => new ZUniversalContextLogging(_, cp.getCtx)) + ) + + val layerEmpty: ULayer[ZLogging.Make] = ZLayer.succeed(_ => empty[Any]) + } + +} diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/package.scala b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/package.scala new file mode 100644 index 000000000..56c72a11f --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/package.scala @@ -0,0 +1,10 @@ +package tofu.logging + +import zio.{UIO, URIO} + +package object zlogs { + type ZLogs[R] = Logs[UIO, URIO[R, *]] + type ULogs = Logs[UIO, UIO] + type ZLogging[R] = Logging[URIO[R, *]] + type ULogging = Logging[UIO] +} diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/zlogs.scala b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/zlogs.scala new file mode 100644 index 000000000..e31a7d971 --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/zlogs.scala @@ -0,0 +1,41 @@ +package tofu.logging.zlogs + +import org.slf4j.{Logger, LoggerFactory} +import tofu.logging.impl.ZContextLogging +import tofu.logging.{Loggable, Logs} +import zio._ + +import scala.annotation.unused + +object ZLogs { + + private def make[R](f: Logger => ZLogging[R]) = + new Logs[UIO, URIO[R, *]] { + override def byName(name: String): UIO[ZLogging[R]] = + ZIO.succeed(LoggerFactory.getLogger(name)).map(f) + } + + /** Creates layer with a helper producing simple [[tofu.logging.Logging]]. + */ + def layerPlain: ULayer[ULogs] = + ZLayer.succeed(make(new ZContextLogging(_, ZIO.unit))) + + /** Creates layer with a helper [[ZLogs]] producing logging `ZLogging[R]`. Logging methods will add the context from + * the ZIO environment. + * + * @tparam R + * the context, an environment of the logging methods. + */ + def layerContextual[R: Loggable](implicit @unused RT: Tag[R]): ULayer[ZLogs[R]] = + ZLayer.succeed(make(new ZContextLogging(_, ZIO.service[R]))) + + /** Creates layer with a helper [[ULogs]] producing plain `ULogging` with encapsulated context. You have to provide an + * implementation of `ContextProvider`. + */ + val layerPlainWithContext: URLayer[ContextProvider, ULogs] = ZLayer( + ZIO.serviceWith[ContextProvider](cp => make(new ZContextLogging(_, cp.getCtx))) + ) + + val layerEmpty: ULayer[ULogs] = ZLayer.succeed(make(_ => ZLogging.empty[Any])) + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7474ff937..f11bdc054 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -46,6 +46,8 @@ object Dependencies { val zio = "1.0.17" + val zio2 = "2.0.3" + val zioCats = "2.5.1.0" val shapeless = "2.3.10" @@ -109,6 +111,9 @@ object Dependencies { val catsTagless = "org.typelevel" %% "cats-tagless-macros" % Version.catsTagless val typesafeConfig = "com.typesafe" % "config" % Version.typesafeConfig val zio = "dev.zio" %% "zio" % Version.zio + val zio2 = "dev.zio" %% "zio" % Version.zio2 + val zio2Test = "dev.zio" %% "zio-test" % Version.zio2 % Test + val zio2TestSbt = "dev.zio" %% "zio-test-sbt" % Version.zio2 % Test val zioCats = "dev.zio" %% "zio-interop-cats" % Version.zioCats val scalatest = "org.scalatest" %% "scalatest" % Version.scalatest % Test val shapeless = "com.chuusai" %% "shapeless" % Version.shapeless From 86303db7f9478c193d6836618dc530d67866b55d Mon Sep 17 00:00:00 2001 From: Ivan Malyshev Date: Fri, 18 Nov 2022 18:35:02 +0300 Subject: [PATCH 2/2] after review --- .../tofu/example/logging/ZMakeSimpleExample.scala | 6 +++--- .../tofu/example/logging/impl/SimpleContext.scala | 13 ++++++------- .../scala/tofu/logging/impl/ZContextLogging.scala | 2 +- .../scala/tofu/logging/zlogs/ContextProvider.scala | 8 +++++++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala b/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala index c05837cbf..0367dd45f 100644 --- a/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala +++ b/examples-zio2/src/main/scala/tofu/example/logging/ZMakeSimpleExample.scala @@ -11,7 +11,7 @@ object ZMakeSimpleExample extends ZIOAppDefault { for { id <- Random.nextIntBetween(1000, 4000) _ <- ZIO.serviceWithZIO[SimpleContext](_.set(id.toHexString)) - _ <- ZIO.serviceWithZIO[FooService](_.foo(i)) + _ <- ZIO.serviceWithZIO[FooService](_.foo(i)) // each task prints its own requestId set above } yield () ) @@ -20,7 +20,7 @@ object ZMakeSimpleExample extends ZIOAppDefault { .collectAllParDiscard(tasks) .provide( FooService.layer, - ZLogging.Make.layerPlainWithContext, - SimpleContext.layer + SimpleContext.layer, // ULayer[SimpleContext] + ZLogging.Make.layerPlainWithContext, // requires ContextProvider, but easily finds its implementation — SimpleContext ) } diff --git a/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala b/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala index 807d74329..88ee4e815 100644 --- a/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala +++ b/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala @@ -1,14 +1,13 @@ package tofu.example.logging.impl -import tofu.logging.{Loggable, LoggedValue} -import tofu.logging.zlogs.ContextProvider +import tofu.logging.Loggable +import tofu.logging.zlogs.ValueContextProvider import zio.{FiberRef, UIO, ULayer, ZLayer} -class SimpleContext(ref: FiberRef[String]) extends ContextProvider { - private implicit val requestIdLoggable: Loggable[String] = - Loggable.stringValue.named("requestId") +class SimpleContext(ref: FiberRef[String]) + extends ValueContextProvider[String]()(Loggable.stringValue.named("requestId")) { - override def getCtx: UIO[LoggedValue] = ref.get.map(x => x) + override def getA: UIO[String] = ref.get def set(requestId: String): UIO[Unit] = ref.set(requestId) @@ -17,7 +16,7 @@ class SimpleContext(ref: FiberRef[String]) extends ContextProvider { object SimpleContext { val layer: ULayer[SimpleContext] = ZLayer.scoped( FiberRef - .make[String]("") + .make[String]("undefined_request_id") .map(new SimpleContext(_)) ) } diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala index 4539958ee..f13100516 100644 --- a/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala +++ b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala @@ -5,7 +5,7 @@ import tofu.logging.zlogs.ZLogging import tofu.logging.{Loggable, LoggedValue, Logging} import zio._ -class ZContextLogging[R, Ctx: Loggable](logger: => Logger, ctxLog: URIO[R, Ctx]) extends ZLogging[R] { +private[logging] class ZContextLogging[R, Ctx: Loggable](logger: => Logger, ctxLog: URIO[R, Ctx]) extends ZLogging[R] { override def write(level: Logging.Level, message: String, values: LoggedValue*): URIO[R, Unit] = ctxLog.flatMap { ctx => ZIO.succeed( diff --git a/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala index 3c1add113..8cd2320ff 100644 --- a/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala +++ b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/ContextProvider.scala @@ -1,8 +1,14 @@ package tofu.logging.zlogs -import tofu.logging.LoggedValue +import tofu.logging.{Loggable, LoggedValue} import zio.UIO trait ContextProvider { def getCtx: UIO[LoggedValue] } + +abstract class ValueContextProvider[A](implicit L: Loggable[A]) extends ContextProvider { + protected def getA: UIO[A] + + override def getCtx: UIO[LoggedValue] = getA.map(x => x) +}