From 8b47ed37bb70b8936f3d491f6290167c68b694d6 Mon Sep 17 00:00:00 2001 From: Ivan Malyshev Date: Tue, 15 Nov 2022 19:31:52 +0300 Subject: [PATCH] implement zio2 tofu logging like zio1, write example --- 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 | 22 ++++++++ .../tofu/logging/impl/ZContextLogging.scala | 29 +++++++++++ .../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 | 42 ++++++++++++++++ project/Dependencies.scala | 5 ++ 12 files changed, 297 insertions(+), 4 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/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..7e534c987 --- /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, ZLogs} +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..eebf0d86a --- /dev/null +++ b/examples-zio2/src/main/scala/tofu/example/logging/impl/SimpleContext.scala @@ -0,0 +1,22 @@ +package tofu.example.logging.impl + +import tofu.logging.{Loggable, LoggedValue} +import tofu.logging.zlogs.ContextProvider +import zio.{FiberRef, UIO, 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 = 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..65834e437 --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/impl/ZContextLogging.scala @@ -0,0 +1,29 @@ +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..5278a00b1 --- /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..879c5b168 --- /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..040ca4a07 --- /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..31dd32993 --- /dev/null +++ b/modules/zio2/logging/src/main/scala/tofu/logging/zlogs/zlogs.scala @@ -0,0 +1,42 @@ +package tofu.logging.zlogs + +import org.slf4j.{Logger, LoggerFactory} +import tofu.logging.impl.ZContextLogging +import tofu.logging.zlogs.ZLogging.ZMake +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