Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 097d5fc
Showing
13 changed files
with
515 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.* | ||
*.swp | ||
*~ | ||
*.log | ||
target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
version = 2.2.2 | ||
maxColumn = 120 | ||
align = most | ||
align.openParenDefnSite = true | ||
align.openParenCallSite = true | ||
includeCurlyBraceInSelectChains = false | ||
rewrite.rules = [SortImports, SortModifiers, RedundantBraces] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import sbt._ | ||
import Keys._ | ||
import xerial.sbt.Sonatype._ | ||
|
||
inThisBuild( | ||
Seq( | ||
organization := "com.github.mvv.zilog", | ||
version := "0.1-SNAPSHOT", | ||
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")), | ||
developers := List( | ||
Developer(id = "mvv", | ||
name = "Mikhail Vorozhtsov", | ||
email = "mikhail.vorozhtsov@gmail.com", | ||
url = url("https://github.com/mvv")) | ||
), | ||
sonatypeProjectHosting := Some(GitHubHosting("mvv", "zilog", "mikhail.vorozhtsov@gmail.com")) | ||
) | ||
) | ||
|
||
ThisBuild / publishTo := sonatypePublishToBundle.value | ||
ThisBuild / publishMavenStyle := true | ||
|
||
inThisBuild( | ||
Seq( | ||
crossScalaVersions := Seq("2.13.1", "2.12.10", "2.11.12"), | ||
scalaVersion := crossScalaVersions.value.head, | ||
scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked", "-Xfatal-warnings") | ||
) | ||
) | ||
|
||
def isPriorTo2_13(version: String): Boolean = | ||
CrossVersion.partialVersion(version) match { | ||
case Some((2, minor)) => minor < 13 | ||
case _ => false | ||
} | ||
|
||
lazy val zilog = (project in file(".")) | ||
.settings( | ||
name := "zilog", | ||
sonatypeProfileName := "com.github.mvv", | ||
sonatypeSessionName := s"Zilog_${version.value}", | ||
scalacOptions ++= { | ||
if (isPriorTo2_13(scalaVersion.value)) { | ||
Nil | ||
} else { | ||
Seq("-Ymacro-annotations") | ||
} | ||
}, | ||
libraryDependencies ++= | ||
Seq( | ||
"dev.zio" %% "zio" % "1.0.0-RC16" % Provided, | ||
"org.slf4j" % "slf4j-api" % "1.7.29", | ||
"org.scala-lang" % "scala-reflect" % scalaVersion.value, | ||
"org.specs2" %% "specs2-core" % "4.8.1" % Test | ||
), | ||
libraryDependencies ++= { | ||
if (isPriorTo2_13(scalaVersion.value)) { | ||
Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)) | ||
} else { | ||
Nil | ||
} | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=1.3.3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1") | ||
addSbtPlugin("io.crashbox" % "sbt-gpg" % "0.2.0") | ||
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.8") |
106 changes: 106 additions & 0 deletions
106
src/main/scala/com/github/mvv/zilog/ImplicitArgsLogger.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package com.github.mvv.zilog | ||
|
||
import org.slf4j.MDC | ||
import zio.{FiberRef, UIO, ZIO} | ||
|
||
trait ImplicitArgsLogger extends Logger { | ||
override def logger: ImplicitArgsLogger.Service[Any] | ||
} | ||
|
||
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) | ||
} | ||
} | ||
} | ||
|
||
val mdc: UIO[Service[Any]] = Service { | ||
(ctx: LoggerContext, level: Level, format: String, explicitArgs: Array[Any], implicitArgs: Map[String, Any]) => | ||
ZIO.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) | ||
oldValues.foreach { | ||
case (key, oldValue) => | ||
oldValue match { | ||
case Some(value) => | ||
MDC.put(key, value) | ||
case None => | ||
MDC.remove(key) | ||
} | ||
} | ||
} | ||
} | ||
|
||
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 | ||
} | ||
i = format.indexOf("{}", i + 2) | ||
} | ||
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) | ||
} | ||
ctx.log(level, fullFormat, fullArgs) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.github.mvv.zilog | ||
|
||
sealed trait Level { | ||
protected val code: Int | ||
} | ||
|
||
object Level { | ||
case object Error extends Level { | ||
override protected val code = 4 | ||
} | ||
case object Warn extends Level { | ||
override protected val code = 3 | ||
} | ||
case object Info extends Level { | ||
override protected val code = 2 | ||
} | ||
case object Debug extends Level { | ||
override protected val code = 1 | ||
} | ||
case object Trace extends Level { | ||
override protected val code = 0 | ||
} | ||
|
||
implicit val levelOrdering: Ordering[Level] = new Ordering[Level] { | ||
override def compare(x: Level, y: Level): Int = x.code.compare(y.code) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.github.mvv.zilog | ||
|
||
import zio.ZIO | ||
|
||
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 | ||
} | ||
|
||
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)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.github.mvv.zilog | ||
|
||
import org.slf4j.LoggerFactory | ||
|
||
import scala.reflect.{classTag, ClassTag} | ||
|
||
final class LoggerContext(val underlying: org.slf4j.Logger) extends AnyVal { | ||
def isLevelEnabled(level: Level): Boolean = | ||
level match { | ||
case Level.Error => underlying.isErrorEnabled | ||
case Level.Warn => underlying.isWarnEnabled | ||
case Level.Info => underlying.isInfoEnabled | ||
case Level.Debug => underlying.isDebugEnabled | ||
case Level.Trace => underlying.isTraceEnabled | ||
} | ||
def log(level: Level, format: String, args: Array[Any]): Unit = | ||
level match { | ||
case Level.Error => underlying.error(format, args.asInstanceOf[Array[AnyRef]]: _*) | ||
case Level.Warn => underlying.warn(format, args.asInstanceOf[Array[AnyRef]]: _*) | ||
case Level.Info => underlying.info(format, args.asInstanceOf[Array[AnyRef]]: _*) | ||
case Level.Debug => underlying.debug(format, args.asInstanceOf[Array[AnyRef]]: _*) | ||
case Level.Trace => underlying.trace(format, args.asInstanceOf[Array[AnyRef]]: _*) | ||
} | ||
} | ||
|
||
object LoggerContext { | ||
def apply(name: String): LoggerContext = new LoggerContext(LoggerFactory.getLogger(name)) | ||
def apply[A: ClassTag]: LoggerContext = new LoggerContext(LoggerFactory.getLogger(classTag[A].runtimeClass)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package com.github.mvv.zilog | ||
|
||
import scala.annotation.tailrec | ||
import scala.reflect.macros.blackbox | ||
|
||
class LoggerMacro(val c: blackbox.Context) { | ||
import c.universe._ | ||
|
||
private val Error = c.Expr(q"_root_.com.github.mvv.zilog.Level.Error") | ||
private val Warn = c.Expr(q"_root_.com.github.mvv.zilog.Level.Warn") | ||
private val Info = c.Expr(q"_root_.com.github.mvv.zilog.Level.Info") | ||
private val Debug = c.Expr(q"_root_.com.github.mvv.zilog.Level.Debug") | ||
private val Trace = c.Expr(q"_root_.com.github.mvv.zilog.Level.Trace") | ||
|
||
def logError(message: c.Expr[String]): Tree = | ||
log(Error, None, message) | ||
def logErrorWithThrowable(e: c.Expr[Throwable], message: c.Expr[String]): Tree = | ||
log(Error, Some(e), message) | ||
def logWarn(message: c.Expr[String]): Tree = | ||
log(Warn, None, message) | ||
def logWarnWithThrowable(e: c.Expr[Throwable], message: c.Expr[String]): Tree = | ||
log(Warn, Some(e), message) | ||
def logInfo(message: c.Expr[String]): Tree = | ||
log(Info, None, message) | ||
def logInfoWithThrowable(e: c.Expr[Throwable], message: c.Expr[String]): Tree = | ||
log(Info, Some(e), message) | ||
def logDebug(message: c.Expr[String]): Tree = | ||
log(Debug, None, message) | ||
def logDebugWithThrowable(e: c.Expr[Throwable], message: c.Expr[String]): Tree = | ||
log(Debug, Some(e), message) | ||
def logTrace(message: c.Expr[String]): Tree = | ||
log(Trace, None, message) | ||
def logTraceWithThrowable(e: c.Expr[Throwable], message: c.Expr[String]): Tree = | ||
log(Trace, Some(e), message) | ||
|
||
private object ListOfStrings { | ||
def unapply(list: List[Tree]): Option[List[String]] = { | ||
@tailrec | ||
def loop(acc: List[String], left: List[Tree]): Option[List[String]] = | ||
left.headOption match { | ||
case Some(Literal(Constant(s: String))) => | ||
loop(s :: acc, left.tail) | ||
case Some(_) => | ||
None | ||
case None => | ||
Some(acc.reverse) | ||
} | ||
loop(Nil, list) | ||
} | ||
} | ||
|
||
def log(level: c.Expr[Level], error: Option[c.Expr[Throwable]], message: c.Expr[String]): Tree = { | ||
val (format, args) = message.tree match { | ||
// 2.11 and 2.12 | ||
case Apply(Select(Apply(Select(prefix, TermName("apply")), ListOfStrings(formatPieces)), TermName("s")), args) | ||
if prefix.tpe.typeSymbol == typeOf[StringContext.type].typeSymbol && formatPieces.size == args.size + 1 => | ||
(Literal(Constant(formatPieces.mkString("{}"))), args) | ||
// 2.13+ | ||
case Typed(expr, _) => | ||
@tailrec | ||
def linearize(acc: List[Tree], lhs: Tree): List[Tree] = | ||
lhs match { | ||
case Apply(Select(next, TermName("$plus")), List(arg)) | ||
if next.tpe.typeSymbol == typeOf[String].typeSymbol => | ||
linearize(arg :: acc, next) | ||
case other => | ||
other :: acc | ||
} | ||
@tailrec | ||
def loop(format: String, args: Seq[Tree], rhs: Seq[Tree], isArg: Boolean): (Tree, Seq[Tree]) = | ||
rhs.headOption match { | ||
case Some(next) if isArg => | ||
loop(s"$format{}", args :+ next, rhs.tail, false) | ||
case Some(Literal(Constant(s: String))) => | ||
loop(s"$format$s", args, rhs.tail, true) | ||
case Some(next) => | ||
loop(s"$format{}", args :+ next, rhs.tail, false) | ||
case None => | ||
(Literal(Constant(format)), args) | ||
} | ||
loop("", Vector.empty, linearize(Nil, expr), false) | ||
case _ => | ||
(message.tree, Seq.empty) | ||
} | ||
val argsWithError = error.fold(args)(e => args :+ e.tree) | ||
val ctx = TermName(c.freshName("ctx")) | ||
q"""{ | ||
val $ctx = implicitly[_root_.com.github.mvv.zilog.LoggerContext] | ||
if ($ctx.isLevelEnabled($level)) { | ||
${c.prefix}.log($level, $format, ${if (argsWithError.isEmpty) { | ||
q"_root_.scala.collection.immutable.Nil" | ||
} else { | ||
q"_root_.scala.Array[_root_.scala.Any](..$argsWithError)" | ||
}}) | ||
} else { | ||
_root_.zio.ZIO.unit | ||
} | ||
}""" | ||
} | ||
} |
Oops, something went wrong.