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 1 commit
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))
vagroz marked this conversation as resolved.
Show resolved Hide resolved
} yield ()
)

override def run =
ZIO
.collectAllParDiscard(tasks)
.provide(
FooService.layer,
ZLogging.Make.layerPlainWithContext,
SimpleContext.layer
)
}
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,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any other way to get implement this method other than that? It feels like any implementation of ContextProvider will be doing something.something.map(x => /*implicit conversion*/ x). Perhaps it's better to make it ContextProvider[A: Loggable]?

Copy link
Collaborator Author

@vagroz vagroz Nov 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ZLogs.layerPlainWithContext is pretty now: we can provide to it any implementation of ContextProvider.
If we make this trait as ContextProvider[A: Loggable], the layer will also require type-parameter which can be huge enough. For example, next PR will bring ContextProvider with data A = Map[ZLogAnnotation[_], Any].

As a user, I don't want write THIS type A, ideally I should not know anything about it. I just write two things

zioProgram.provide(
  ZLogs.layerPlainWithContext,  // dear tofu, give me a logger factory using ContextProvider I point next to
  TofuDefaultContext.layer         // I'd like to log this context, don't carry how this collected
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be doing something.something.map(x => /implicit conversion/ x)

suggest abstract class ValueContextProvider which do this itself

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest abstract class ValueContextProvider which do this itself

yep, that'll work


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

object SimpleContext {
val layer: ULayer[SimpleContext] = ZLayer.scoped(
FiberRef
.make[String]("")
vagroz marked this conversation as resolved.
Show resolved Hide resolved
.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._

class ZContextLogging[R, Ctx: Loggable](logger: => Logger, ctxLog: URIO[R, Ctx]) extends ZLogging[R] {
vagroz marked this conversation as resolved.
Show resolved Hide resolved
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,8 @@
package tofu.logging.zlogs

import tofu.logging.LoggedValue
import zio.UIO

trait ContextProvider {
def getCtx: UIO[LoggedValue]
}
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