Skip to content

Commit

Permalink
ZIO-2 logging (Part 1) (#1038)
Browse files Browse the repository at this point in the history
Implement zio2 tofu logging like zio1, write example


Co-authored-by: Ivan Malyshev <[email protected]>
  • Loading branch information
vagroz and Ivan Malyshev authored Nov 25, 2022
1 parent ab5cdd4 commit 55ed568
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 23 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]): _*)

Expand Down
13 changes: 13 additions & 0 deletions examples-zio2/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 minutes" >
<appender name="structured" class="ch.qos.logback.core.ConsoleAppender" packagingData="true">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<charset>UTF-8</charset>
<layout class="tofu.logging.ELKLayout"/>
</encoder>
</appender>

<root level="DEBUG">
<appender-ref ref="structured"/>
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -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)) // each task prints its own requestId set above
} yield ()
)

override def run =
ZIO
.collectAllParDiscard(tasks)
.provide(
FooService.layer,
SimpleContext.layer, // ULayer[SimpleContext]
ZLogging.Make.layerPlainWithContext, // requires ContextProvider, but easily finds its implementation — SimpleContext
)
}
Original file line number Diff line number Diff line change
@@ -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(_))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tofu.example.logging.impl

import tofu.logging.Loggable
import tofu.logging.zlogs.ValueContextProvider
import zio.{FiberRef, UIO, ULayer, ZLayer}

class SimpleContext(ref: FiberRef[String])
extends ValueContextProvider[String]()(Loggable.stringValue.named("requestId")) {

override def getA: UIO[String] = ref.get

def set(requestId: String): UIO[Unit] =
ref.set(requestId)
}

object SimpleContext {
val layer: ULayer[SimpleContext] = ZLayer.scoped(
FiberRef
.make[String]("undefined_request_id")
.map(new SimpleContext(_))
)
}
Original file line number Diff line number Diff line change
@@ -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._

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(
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)
)
}
}
Original file line number Diff line number Diff line change
@@ -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
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tofu.logging.zlogs

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)
}
Original file line number Diff line number Diff line change
@@ -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])
}

}
Original file line number Diff line number Diff line change
@@ -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]
}
41 changes: 41 additions & 0 deletions modules/zio2/logging/src/main/scala/tofu/logging/zlogs/zlogs.scala
Original file line number Diff line number Diff line change
@@ -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]))

}
5 changes: 5 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 55ed568

Please sign in to comment.