Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZIO-2 logging (Part 1) #1038

Merged
merged 3 commits into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
}
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