Skip to content

Commit

Permalink
Use ZLayer and Has
Browse files Browse the repository at this point in the history
  • Loading branch information
mvv committed Mar 15, 2020
1 parent e79d2c4 commit 6008912
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
@@ -1,4 +1,4 @@
version = 2.2.2
version = 2.4.2
maxColumn = 120
align = most
align.openParenDefnSite = true
Expand Down
4 changes: 2 additions & 2 deletions build.sbt
Expand Up @@ -5,7 +5,7 @@ import xerial.sbt.Sonatype._
inThisBuild(
Seq(
organization := "com.github.mvv.zilog",
version := "0.1-M2",
version := "0.1-M3",
homepage := Some(url("https://github.com/mvv/zilog")),
scmInfo := Some(ScmInfo(url("https://github.com/mvv/zilog"), "scm:git@github.com:mvv/zilog.git")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
Expand Down Expand Up @@ -62,7 +62,7 @@ lazy val zilog = (project in file("."))
},
libraryDependencies ++=
Seq(
"dev.zio" %% "zio" % "1.0.0-RC16" % Provided,
"dev.zio" %% "zio" % "1.0.0-RC18-2" % Provided,
"org.slf4j" % "slf4j-api" % "1.7.29",
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.specs2" %% "specs2-core" % "4.8.1" % Test
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
@@ -1,3 +1,3 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.2")
addSbtPlugin("io.crashbox" % "sbt-gpg" % "0.2.0")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.8.1")
127 changes: 54 additions & 73 deletions src/main/scala/com/github/mvv/zilog/ImplicitArgsLogger.scala
@@ -1,55 +1,24 @@
package com.github.mvv.zilog

import org.slf4j.MDC
import zio.{FiberRef, UIO, ZIO}

trait ImplicitArgsLogger extends Logger {
override def logger: ImplicitArgsLogger.Service[Any]
}
import zio.{Has, Layer, UIO, URIO, ZIO, ZLayer}

object ImplicitArgsLogger {
trait Service[-R] extends Logger.Service[R] {
def withImplicitLogArgs[R1 <: R, E, A](args: (String, Any)*)(zio: ZIO[R1, E, A]): ZIO[R1, E, A]
}

sealed abstract private class FiberRefService[-R](fiberRef: FiberRef[Map[String, Any]]) extends Service[R] {
final override def withImplicitLogArgs[R1 <: R, E, A](args: (String, Any)*)(zio: ZIO[R1, E, A]): ZIO[R1, E, A] =
fiberRef.get.flatMap { current =>
fiberRef.locally(current ++ args)(zio)
}
def log(level: Level, format: String, explicitArgs: Array[Any], implicitArgs: Map[String, Any])(
implicit ctx: LoggerContext
): ZIO[R, Nothing, Unit]
final override def log(level: Level, format: String, args: Array[Any])(
implicit ctx: LoggerContext
): ZIO[R, Nothing, Unit] =
fiberRef.get.flatMap(log(level, format, args, _))
}

object Service {
def apply[R](
f: (LoggerContext, Level, String, Array[Any], Map[String, Any]) => ZIO[R, Nothing, Unit]
): UIO[Service[R]] =
FiberRef.make(Map.empty[String, Any], (first: Map[String, Any], _: Map[String, Any]) => first).map { fiberRef =>
new FiberRefService[R](fiberRef) {
override def log(level: Level, format: String, explicitArgs: Array[Any], implicitArgs: Map[String, Any])(
implicit ctx: LoggerContext
): ZIO[R, Nothing, Unit] =
f(ctx, level, format, explicitArgs, implicitArgs)
}
}
trait Service extends Logger.Service {
def withImplicitLogArgs(args: (String, Any)*): Service
}

val mdc: UIO[Service[Any]] = Service {
(ctx: LoggerContext, level: Level, format: String, explicitArgs: Array[Any], implicitArgs: Map[String, Any]) =>
ZIO.effectTotal {
private class MdcService(implicitArgs: Map[String, Any]) extends Service {
override def withImplicitLogArgs(args: (String, Any)*): Service = new MdcService(implicitArgs ++ args)
override def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): UIO[Unit] =
UIO.effectTotal {
val oldValues = implicitArgs.iterator.map {
case (key, value) =>
val oldValue = Option(MDC.get(key))
MDC.put(key, value.toString)
key -> oldValue
}.toSeq
ctx.log(level, format, explicitArgs)
ctx.log(level, format, args)
oldValues.foreach {
case (key, oldValue) =>
oldValue match {
Expand All @@ -62,45 +31,57 @@ object ImplicitArgsLogger {
}
}

private def countPlaceholdersIn(format: String): Int = {
var i = format.indexOf("{}")
var counter = 0
if (i == 0) {
counter += 1
i = format.indexOf("{}", 2)
}
while (i > 0) {
if (format.charAt(i - 1) != '\\') {
counter += 1
private class AppendToMessageService(underlying: Logger.Service, implicitArgs: Map[String, Any]) extends Service {
import AppendToMessageService._
override def withImplicitLogArgs(args: (String, Any)*): Service =
new AppendToMessageService(underlying, implicitArgs ++ args)
override def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): URIO[Any, Unit] = {
val (fullFormat, fullArgs) = if (implicitArgs.isEmpty) {
(format, args)
} else {
val formatSuffix = implicitArgs.keysIterator.map(key => s"$key = {}").mkString("; ")
val numFormatArgs = countPlaceholdersIn(format)
val numExplicitArgs = args.length
val fullArgs = numFormatArgs.compare(numExplicitArgs) match {
case -1 =>
val (formatArgs, extraArgs) = args.splitAt(numFormatArgs)
formatArgs ++ implicitArgs.values ++ extraArgs
case 1 =>
args ++ Seq.fill(numFormatArgs - numExplicitArgs)(null) ++ implicitArgs.values
case _ =>
args ++ implicitArgs.values
}
(s"$format; $formatSuffix", fullArgs)
}
i = format.indexOf("{}", i + 2)
underlying.log(level, fullFormat, fullArgs)
}
counter
}

val appendToMessage: UIO[Service[Any]] = Service {
(ctx: LoggerContext, level: Level, format: String, explicitArgs: Array[Any], implicitArgs: Map[String, Any]) =>
ZIO.effectTotal {
val (fullFormat, fullArgs) = if (implicitArgs.isEmpty) {
(format, explicitArgs)
} else {
val formatSuffix = implicitArgs.keysIterator.map { key =>
s"$key = {}"
}.mkString("; ")
val numFormatArgs = countPlaceholdersIn(format)
val numExplicitArgs = explicitArgs.length
val fullArgs = numFormatArgs.compare(numExplicitArgs) match {
case -1 =>
val (formatArgs, extraArgs) = explicitArgs.splitAt(numFormatArgs)
formatArgs ++ implicitArgs.values ++ extraArgs
case 1 =>
explicitArgs ++ Seq.fill(numFormatArgs - numExplicitArgs)(null) ++ implicitArgs.values
case _ =>
explicitArgs ++ implicitArgs.values
}
(s"$format; $formatSuffix", fullArgs)
object AppendToMessageService {
private def countPlaceholdersIn(format: String): Int = {
var i = format.indexOf("{}")
var counter = 0
if (i == 0) {
counter += 1
i = format.indexOf("{}", 2)
}
while (i > 0) {
if (format.charAt(i - 1) != '\\') {
counter += 1
}
ctx.log(level, fullFormat, fullArgs)
i = format.indexOf("{}", i + 2)
}
counter
}
}

object Service {
val mdc: Service = new MdcService(Map.empty)
def appendToMessage(service: Logger.Service): Service = new AppendToMessageService(service, Map.empty)
}

val any: ZLayer[ImplicitArgsLogger, Nothing, ImplicitArgsLogger] = ZLayer.requires[ImplicitArgsLogger]
val mdc: Layer[Nothing, ImplicitArgsLogger] = ZLayer.succeed(Service.mdc)
val appendToMessage: ZLayer[Logger, Nothing, ImplicitArgsLogger] =
ZLayer.requires[Logger].map(has => Has(Service.appendToMessage(has.get)))
}
51 changes: 28 additions & 23 deletions src/main/scala/com/github/mvv/zilog/Logger.scala
@@ -1,36 +1,41 @@
package com.github.mvv.zilog

import zio.ZIO
import zio.{Has, UIO, ULayer, URIO, ZLayer}

import scala.language.experimental.macros

trait Logger {
def logger: Logger.Service[Any]
}

object Logger {
trait Service[-R] {
def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): ZIO[R, Nothing, Unit]
final def error(message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logError
final def error(e: Throwable, message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logErrorWithThrowable
final def warn(message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logWarn
final def warn(e: Throwable, message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logWarnWithThrowable
final def info(message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logInfo
final def info(e: Throwable, message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logInfoWithThrowable
final def debug(message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logDebug
final def debug(e: Throwable, message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logDebugWithThrowable
final def trace(message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logTrace
final def trace(e: Throwable, message: String): ZIO[R, Nothing, Unit] = macro LoggerMacro.logTraceWithThrowable
trait Interface[-R] {
def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): URIO[R, Unit]
final def error(message: String): URIO[R, Unit] = macro LoggerMacro.logError
final def error(e: Throwable, message: String): URIO[R, Unit] = macro LoggerMacro.logErrorWithThrowable
final def warn(message: String): URIO[R, Unit] = macro LoggerMacro.logWarn
final def warn(e: Throwable, message: String): URIO[R, Unit] = macro LoggerMacro.logWarnWithThrowable
final def info(message: String): URIO[R, Unit] = macro LoggerMacro.logInfo
final def info(e: Throwable, message: String): URIO[R, Unit] = macro LoggerMacro.logInfoWithThrowable
final def debug(message: String): URIO[R, Unit] = macro LoggerMacro.logDebug
final def debug(e: Throwable, message: String): URIO[R, Unit] = macro LoggerMacro.logDebugWithThrowable
final def trace(message: String): URIO[R, Unit] = macro LoggerMacro.logTrace
final def trace(e: Throwable, message: String): URIO[R, Unit] = macro LoggerMacro.logTraceWithThrowable
}

object Default extends Logger {
override val logger: Service[Any] = new Service[Any] {
override def log(level: Level, format: String, args: Array[Any])(
implicit ctx: LoggerContext
): ZIO[Any, Nothing, Unit] =
ZIO.effectTotal(ctx.log(level, format, args))
trait Service extends Interface[Any]

object Service {
val live: Service = new Service {
override def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): UIO[Unit] =
UIO.effectTotal(ctx.log(level, format, args))
}
def prefix(prefix: String, service: Service): Service = new Service {
override def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): URIO[Any, Unit] =
service.log(level, s"$prefix$format", args)
}
}

val any: ZLayer[Logger, Nothing, Logger] = ZLayer.requires[Logger]
val live: ULayer[Logger] = ZLayer.succeed(Service.live)
def prefix(prefix: String): ZLayer[Logger, Nothing, Logger] =
ZLayer.requires[Logger].map(has => Has(Service.prefix(prefix, has.get)))

val NoArgs: Array[Any] = Array.empty[Any]
}
17 changes: 7 additions & 10 deletions src/main/scala/com/github/mvv/zilog/package.scala
@@ -1,14 +1,11 @@
package com.github.mvv

import zio.ZIO
import zio.{Has, URIO, ZIO}

package object zilog extends Logger.Service[Logger] with ImplicitArgsLogger.Service[ImplicitArgsLogger] {
override def log(level: Level, format: String, args: Array[Any])(
implicit ctx: LoggerContext
): ZIO[Logger, Nothing, Unit] =
ZIO.accessM[Logger](_.logger.log(level, format, args))
override def withImplicitLogArgs[R1 <: ImplicitArgsLogger, E, A](
args: (String, Any)*
)(zio: ZIO[R1, E, A]): ZIO[R1, E, A] =
ZIO.accessM[R1](_.logger.withImplicitLogArgs(args: _*)(zio))
package object zilog extends Logger.Interface[Has[Logger.Service]] {
type Logger = Has[Logger.Service]
type ImplicitArgsLogger = Has[ImplicitArgsLogger.Service]

override def log(level: Level, format: String, args: Array[Any])(implicit ctx: LoggerContext): URIO[Logger, Unit] =
ZIO.accessM[Logger](_.get.log(level, format, args))
}
34 changes: 11 additions & 23 deletions src/test/scala/com/github/mvv/zilog/test/LoggerSpec.scala
Expand Up @@ -3,37 +3,29 @@ package com.github.mvv.zilog.test
import com.github.mvv.zilog
import com.github.mvv.zilog.{Level, Logger, LoggerContext}
import org.specs2.mutable.Specification
import zio.{DefaultRuntime, ZIO}
import zio.BootstrapRuntime

class LoggerSpec extends Specification with DefaultRuntime {
class LoggerSpec extends Specification with BootstrapRuntime {
"Logger" >> {
"should log" >> {
val logger = new TestLogger(Level.Debug)
unsafeRunSync {
implicit val ctx = new LoggerContext(logger)
ZIO.provide(Logger.Default) {
zilog.info(s"Start ${1 + 2}${8} and ${"bar"} end")
}
zilog.info(s"Start ${1 + 2}${8} and ${"bar"} end").provideCustomLayer(Logger.live)
}
logger.entries must containTheSameElementsAs(
Seq(
(Level.Info, "Start {}{} and {} end", List(3, 8, "bar"))
)
logger.entries.toList mustEqual List(
(Level.Info, "Start {}{} and {} end", List(3, 8, "bar"))
)
}

"should log messages without arguments" >> {
val logger = new TestLogger(Level.Debug)
unsafeRunSync {
implicit val ctx = new LoggerContext(logger)
ZIO.provide(Logger.Default) {
zilog.warn("Message")
}
zilog.warn("Message").provideCustomLayer(Logger.live)
}
logger.entries must containTheSameElementsAs(
Seq(
(Level.Warn, "Message", Nil)
)
logger.entries.toList mustEqual List(
(Level.Warn, "Message", Nil)
)
}

Expand All @@ -42,14 +34,10 @@ class LoggerSpec extends Specification with DefaultRuntime {
val logger = new TestLogger(Level.Debug)
unsafeRunSync {
implicit val ctx = new LoggerContext(logger)
ZIO.provide(Logger.Default) {
zilog.error(e, s"${1} plus ${2}")
}
zilog.error(e, s"${1} plus ${2}").provideCustomLayer(Logger.live)
}
logger.entries must containTheSameElementsAs(
Seq(
(Level.Error, "{} plus {}", List(1, 2, e))
)
logger.entries.toList mustEqual List(
(Level.Error, "{} plus {}", List(1, 2, e))
)
}
}
Expand Down

0 comments on commit 6008912

Please sign in to comment.