Skip to content

Commit

Permalink
Merge pull request #248 from evolution-gaming/lazy_log_mdc_ce2
Browse files Browse the repository at this point in the history
Implement lazy MDC
  • Loading branch information
DeviLab authored Oct 9, 2023
2 parents 4e376b8 + 46ce3aa commit 0b6de64
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 29 deletions.
86 changes: 74 additions & 12 deletions core/src/main/scala/com/evolutiongaming/catshelper/Log.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cats.effect.Sync
import cats.{Applicative, Semigroup, ~>}
import org.slf4j.{Logger, MDC}

import scala.annotation.tailrec
import scala.collection.immutable.SortedMap

trait Log[F[_]] {
Expand Down Expand Up @@ -44,30 +45,91 @@ object Log {
object Mdc {

private object Empty extends Mdc
private final case class Context(values: NonEmptyMap[String, String]) extends Mdc {
private final case class EagerContext(values: NonEmptyMap[String, String]) extends Mdc {
override def toString: String = s"MDC(${values.toSortedMap.mkString(", ")})"
}
private final class LazyContext(val getMdc: () => Mdc) extends Mdc {

override def toString: String = getMdc().toString

override def hashCode(): Int = getMdc().hashCode()

override def equals(obj: Any): Boolean = obj match {
case that: LazyContext => this.getMdc().equals(that.getMdc())
case _ => false
}
}
private object LazyContext {
def apply(mdc: => Mdc): LazyContext = new LazyContext(() => mdc)
}

val empty: Mdc = Empty

def apply(head: (String, String), tail: (String, String)*): Mdc = Context(NonEmptyMap.of(head, tail: _*))
type Record = (String, String)

@deprecated("Use Mdc.Lazy.apply or Mdc.Eager.apply", "3.9.0")
def apply(head: Record, tail: Record*): Mdc = Eager(head, tail:_*)

@deprecated("Use Mdc.Lazy.fromSeq or Mdc.Eager.fromSeq", "3.9.0")
def fromSeq(seq: Seq[Record]): Mdc = Eager.fromSeq(seq)

def fromSeq(seq: Seq[(String, String)]): Mdc =
NonEmptyMap.fromMap(SortedMap(seq: _*)).fold(empty){ nem => Context(nem) }
@deprecated("Use Mdc.Lazy.fromMap or Mdc.Eager.fromMap", "3.9.0")
def fromMap(map: Map[String, String]): Mdc = Eager.fromMap(map)

def fromMap(map: Map[String, String]): Mdc = fromSeq(map.toSeq)
object Eager {
def apply(head: Record, tail: Record*): Mdc = EagerContext(NonEmptyMap.of(head, tail: _*))

implicit final val mdcSemigroup: Semigroup[Mdc] = Semigroup.instance {
case (Empty, right) => right
case (left, Empty) => left
case (Context(v1), Context(v2)) => Context(v1 ++ v2)
def fromSeq(seq: Seq[Record]): Mdc = NonEmptyMap.fromMap(SortedMap(seq: _*)).fold(empty){ nem => EagerContext(nem) }

def fromMap(map: Map[String, String]): Mdc = fromSeq(map.toSeq)
}

object Lazy {
def apply(v1: => Record): Mdc = LazyContext(Eager(v1))
def apply(v1: => Record, v2: => Record): Mdc = LazyContext(Eager(v1, v2))
def apply(v1: => Record, v2: => Record, v3: => Record): Mdc = LazyContext(Eager(v1, v2, v3))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record): Mdc = LazyContext(Eager(v1, v2, v3, v4))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record, v5: => Record): Mdc =
LazyContext(Eager(v1, v2, v3, v4, v5))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record, v5: => Record, v6: => Record): Mdc =
LazyContext(Eager(v1, v2, v3, v4, v5, v6))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record, v5: => Record, v6: => Record, v7: => Record): Mdc =
LazyContext(Eager(v1, v2, v3, v4, v5, v6, v7))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record, v5: => Record, v6: => Record, v7: => Record, v8: => Record): Mdc =
LazyContext(Eager(v1, v2, v3, v4, v5, v6, v7, v8))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record, v5: => Record, v6: => Record, v7: => Record, v8: => Record, v9: => Record): Mdc =
LazyContext(Eager(v1, v2, v3, v4, v5, v6, v7, v8, v9))
def apply(v1: => Record, v2: => Record, v3: => Record, v4: => Record, v5: => Record, v6: => Record, v7: => Record, v8: => Record, v9: => Record, v10: => Record): Mdc =
LazyContext(Eager(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10))

def fromSeq(seq: => Seq[Record]): Mdc = LazyContext(Eager.fromSeq(seq))

def fromMap(map: => Map[String, String]): Mdc = LazyContext(Eager.fromMap(map))
}

implicit final val mdcSemigroup: Semigroup[Mdc] = {
@tailrec def joinContexts(c1: Mdc, c2: Mdc): Mdc = (c1, c2) match {
case (Empty, right) => right
case (left, Empty) => left
case (EagerContext(v1), EagerContext(v2)) => EagerContext(v1 ++ v2)
case (c1: LazyContext, c2: LazyContext) => joinContexts(c1.getMdc(), c2.getMdc())
case (c1: LazyContext, c2: EagerContext) => joinContexts(c1.getMdc(), c2)
case (c1: EagerContext, c2: LazyContext) => joinContexts(c1, c2.getMdc())
}

Semigroup.instance(joinContexts)
}

implicit final class MdcOps(val mdc: Mdc) extends AnyVal {

def context: Option[NonEmptyMap[String, String]] = mdc match {
case Empty => None
case Context(values) => Some(values)
def context: Option[NonEmptyMap[String, String]] = {
@tailrec def contextInner(mdc: Mdc): Option[NonEmptyMap[String, String]] = mdc match {
case Empty => None
case EagerContext(values) => Some(values)
case lc: LazyContext => contextInner(lc.getMdc())
}

contextInner(mdc)
}
}
}
Expand Down
30 changes: 15 additions & 15 deletions core/src/test/scala/com/evolutiongaming/catshelper/LogSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,25 @@ class LogSpec extends AnyFunSuite with Matchers {
val stateT = for {
log0 <- logOf("source")
log = log0.prefixed(">").mapK(FunctionK.id)
_ <- log.trace("trace", Log.Mdc(mdc))
_ <- log.debug("debug", Log.Mdc(mdc))
_ <- log.info("info", Log.Mdc(mdc))
_ <- log.warn("warn", Log.Mdc(mdc))
_ <- log.warn("warn", Error, Log.Mdc(mdc))
_ <- log.error("error", Log.Mdc(mdc))
_ <- log.error("error", Error, Log.Mdc(mdc))
_ <- log.trace("trace", Log.Mdc.Lazy(mdc))
_ <- log.debug("debug", Log.Mdc.Lazy(mdc))
_ <- log.info("info", Log.Mdc.Lazy(mdc))
_ <- log.warn("warn", Log.Mdc.Lazy(mdc))
_ <- log.warn("warn", Error, Log.Mdc.Lazy(mdc))
_ <- log.error("error", Log.Mdc.Lazy(mdc))
_ <- log.error("error", Error, Log.Mdc.Lazy(mdc))
} yield {}


val (state, _) = stateT.run(State(Nil))
state shouldEqual State(List(
Action.Error1("> error", Error, Log.Mdc(mdc)),
Action.Error0("> error", Log.Mdc(mdc)),
Action.Warn1("> warn", Error, Log.Mdc(mdc)),
Action.Warn0("> warn", Log.Mdc(mdc)),
Action.Info("> info", Log.Mdc(mdc)),
Action.Debug("> debug", Log.Mdc(mdc)),
Action.Trace("> trace", Log.Mdc(mdc)),
Action.Error1("> error", Error, Log.Mdc.Lazy(mdc)),
Action.Error0("> error", Log.Mdc.Lazy(mdc)),
Action.Warn1("> warn", Error, Log.Mdc.Lazy(mdc)),
Action.Warn0("> warn", Log.Mdc.Lazy(mdc)),
Action.Info("> info", Log.Mdc.Lazy(mdc)),
Action.Debug("> debug", Log.Mdc.Lazy(mdc)),
Action.Trace("> trace", Log.Mdc.Lazy(mdc)),
Action.OfStr("source")))
}

Expand All @@ -73,7 +73,7 @@ class LogSpec extends AnyFunSuite with Matchers {
val io = for {
logOf <- LogOf.slf4j[IO]
log <- logOf(getClass)
_ <- log.info("whatever", Log.Mdc("k" -> "v"))
_ <- log.info("whatever", Log.Mdc.Lazy("k" -> "v"))
} yield org.slf4j.MDC.getCopyOfContextMap

io.unsafeRunSync() shouldEqual null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class LogOfFromLogbackSpec extends AnyFunSuite with Matchers {
val io = for {
logOf <- LogOfFromLogback[IO]
log <- logOf(getClass)
_ <- log.info("hello from logback", Log.Mdc("k" -> "test value for K"))
_ <- log.info("hello from logback", Log.Mdc.Lazy("k" -> "test value for K"))
} yield ()

io.unsafeRunSync()
Expand Down
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ThisBuild / version := "2.14.1-SNAPSHOT"
ThisBuild / version := "2.15.0-SNAPSHOT"

0 comments on commit 0b6de64

Please sign in to comment.