From 0da55faf2ce5f20c61e796f82dd3d7476183d763 Mon Sep 17 00:00:00 2001 From: Anton Voitsishevskii Date: Tue, 16 Nov 2021 11:08:34 +0200 Subject: [PATCH 1/2] add cached Log and a simple log factory for it; --- .../com/evolutiongaming/catshelper/Log.scala | 123 ++++++++++++++++-- .../evolutiongaming/catshelper/LogOf.scala | 30 ++++- 2 files changed, 140 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/com/evolutiongaming/catshelper/Log.scala b/core/src/main/scala/com/evolutiongaming/catshelper/Log.scala index 4dc0cebd..5320f118 100644 --- a/core/src/main/scala/com/evolutiongaming/catshelper/Log.scala +++ b/core/src/main/scala/com/evolutiongaming/catshelper/Log.scala @@ -3,7 +3,7 @@ package com.evolutiongaming.catshelper import cats.data.NonEmptyMap import cats.effect.Sync import cats.{Applicative, Semigroup, ~>} -import org.slf4j.{Logger, MDC} +import org.slf4j.{ILoggerFactory, Logger, MDC} import scala.collection.immutable.SortedMap @@ -41,9 +41,11 @@ trait Log[F[_]] { object Log { sealed trait Mdc + object Mdc { private object Empty extends Mdc + private final case class Context(values: NonEmptyMap[String, String]) extends Mdc { override def toString: String = s"MDC(${values.toSortedMap.mkString(", ")})" } @@ -53,7 +55,7 @@ object Log { def apply(head: (String, String), tail: (String, String)*): Mdc = Context(NonEmptyMap.of(head, tail: _*)) def fromSeq(seq: Seq[(String, String)]): Mdc = - NonEmptyMap.fromMap(SortedMap(seq: _*)).fold(empty){ nem => Context(nem) } + NonEmptyMap.fromMap(SortedMap(seq: _*)).fold(empty) { nem => Context(nem) } def fromMap(map: Map[String, String]): Mdc = fromSeq(map.toSeq) @@ -76,7 +78,92 @@ object Log { def summon[F[_]](implicit F: Log[F]): Log[F] = F - def apply[F[_]: Sync](logger: Logger): Log[F] = new Log[F] { + def cached[F[_] : Sync](source: String, factory: ILoggerFactory): Log[F] = new Log[F] { + + def withMDC(mdc: Log.Mdc)(log: => Unit): Unit = { + import Mdc.MdcOps + mdc.context match { + case None => log + case Some(mdc) => + val backup = MDC.getCopyOfContextMap + MDC.clear() + mdc.toSortedMap foreach { case (k, v) => MDC.put(k, v) } + log + if (backup == null) MDC.clear() else MDC.setContextMap(backup) + } + } + + def trace(msg: => String, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + if (logger.isTraceEnabled) withMDC(mdc) { + logger.trace(msg) + } + } + } + + def debug(msg: => String, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + + if (logger.isDebugEnabled) withMDC(mdc) { + logger.debug(msg) + } + } + } + + def info(msg: => String, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + + if (logger.isInfoEnabled) withMDC(mdc) { + logger.info(msg) + } + } + } + + def warn(msg: => String, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + + if (logger.isWarnEnabled) withMDC(mdc) { + logger.warn(msg) + } + } + } + + def warn(msg: => String, cause: Throwable, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + + if (logger.isWarnEnabled) withMDC(mdc) { + logger.warn(msg, cause) + } + } + } + + def error(msg: => String, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + + if (logger.isErrorEnabled) withMDC(mdc) { + logger.error(msg) + } + } + } + + def error(msg: => String, cause: Throwable, mdc: Log.Mdc) = { + Sync[F].delay { + val logger = factory.getLogger(source) + + if (logger.isErrorEnabled) withMDC(mdc) { + logger.error(msg, cause) + } + } + } + } + + def apply[F[_] : Sync](logger: Logger): Log[F] = new Log[F] { def withMDC(mdc: Log.Mdc)(log: => Unit): Unit = { import Mdc.MdcOps @@ -93,43 +180,57 @@ object Log { def trace(msg: => String, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isTraceEnabled) withMDC(mdc) { logger.trace(msg) } + if (logger.isTraceEnabled) withMDC(mdc) { + logger.trace(msg) + } } } def debug(msg: => String, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isDebugEnabled) withMDC(mdc) { logger.debug(msg) } + if (logger.isDebugEnabled) withMDC(mdc) { + logger.debug(msg) + } } } def info(msg: => String, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isInfoEnabled) withMDC(mdc) { logger.info(msg) } + if (logger.isInfoEnabled) withMDC(mdc) { + logger.info(msg) + } } } def warn(msg: => String, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isWarnEnabled) withMDC(mdc) { logger.warn(msg) } + if (logger.isWarnEnabled) withMDC(mdc) { + logger.warn(msg) + } } } def warn(msg: => String, cause: Throwable, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isWarnEnabled) withMDC(mdc) { logger.warn(msg, cause) } + if (logger.isWarnEnabled) withMDC(mdc) { + logger.warn(msg, cause) + } } } def error(msg: => String, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isErrorEnabled) withMDC(mdc) { logger.error(msg) } + if (logger.isErrorEnabled) withMDC(mdc) { + logger.error(msg) + } } } def error(msg: => String, cause: Throwable, mdc: Log.Mdc) = { Sync[F].delay { - if (logger.isErrorEnabled) withMDC(mdc) { logger.error(msg, cause) } + if (logger.isErrorEnabled) withMDC(mdc) { + logger.error(msg, cause) + } } } } @@ -183,7 +284,7 @@ object Log { def error(msg: => String, cause: Throwable, mdc: Log.Mdc) = unit } - def empty[F[_]: Applicative]: Log[F] = const(Applicative[F].unit) + def empty[F[_] : Applicative]: Log[F] = const(Applicative[F].unit) implicit class LogOps[F[_]](val self: Log[F]) extends AnyVal { diff --git a/core/src/main/scala/com/evolutiongaming/catshelper/LogOf.scala b/core/src/main/scala/com/evolutiongaming/catshelper/LogOf.scala index d019969d..86b9fe04 100644 --- a/core/src/main/scala/com/evolutiongaming/catshelper/LogOf.scala +++ b/core/src/main/scala/com/evolutiongaming/catshelper/LogOf.scala @@ -6,6 +6,8 @@ import cats.{Applicative, Functor, ~>} import ch.qos.logback.classic.util.ContextInitializer import org.slf4j.{ILoggerFactory, LoggerFactory} +import scala.reflect.ClassTag + trait LogOf[F[_]] { def apply(source: String): F[Log[F]] @@ -15,6 +17,25 @@ trait LogOf[F[_]] { object LogOf { + + trait Safe[F[_]] { + def apply(source: String): Log[F] + + def apply[Source: ClassTag]: Log[F] + } + + def slf4jSafe[F[_] : Sync]: F[LogOf.Safe[F]] = { + for { + factory <- Sync[F].delay { + LoggerFactory.getILoggerFactory + } + } yield new Safe[F] { + override def apply(source: String): Log[F] = Log.cached(source, factory) + + override def apply[Source: ClassTag]: Log[F] = Log.cached(implicitly[ClassTag[Source]].runtimeClass.getName.stripSuffix("$"), factory) + } + } + def apply[F[_]](implicit F: LogOf[F]): LogOf[F] = F def summon[F[_]](implicit F: LogOf[F]): LogOf[F] = F @@ -24,11 +45,14 @@ object LogOf { def apply(source: String) = { for { - log <- Sync[F].delay { factory.getLogger(source) } + log <- Sync[F].delay { + factory.getLogger(source) + } } yield { Log[F](log) } } + def apply(source: Class[_]) = apply(source.getName.stripSuffix("$")) } @@ -38,7 +62,9 @@ object LogOf { def slf4j[F[_] : Sync]: F[LogOf[F]] = { for { - factory <- Sync[F].delay { LoggerFactory.getILoggerFactory } + factory <- Sync[F].delay { + LoggerFactory.getILoggerFactory + } } yield { apply(factory) } From 3ff3a89ff62393ee8832e451717a8b4ac8c5a3d0 Mon Sep 17 00:00:00 2001 From: Anton Voitsishevskii Date: Tue, 16 Nov 2021 11:27:02 +0200 Subject: [PATCH 2/2] add test with POC; --- .../catshelper/LogOfSafeSpec.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/src/test/scala/com/evolutiongaming/catshelper/LogOfSafeSpec.scala diff --git a/core/src/test/scala/com/evolutiongaming/catshelper/LogOfSafeSpec.scala b/core/src/test/scala/com/evolutiongaming/catshelper/LogOfSafeSpec.scala new file mode 100644 index 00000000..ad605d97 --- /dev/null +++ b/core/src/test/scala/com/evolutiongaming/catshelper/LogOfSafeSpec.scala @@ -0,0 +1,21 @@ +package com.evolutiongaming.catshelper + +import cats.effect.IO +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +class LogOfSafeSpec extends AnyFunSuite with Matchers { + class SomeService[F[_] : LogOf.Safe] { + val log: Log[F] = implicitly[LogOf.Safe[F]].apply[SomeService[F]] + + def logIt(arg: String): F[Unit] = log.info("this it message: " + arg) + } + + test("POC") { + implicit val logOfSafe: LogOf.Safe[IO] = LogOf.slf4jSafe[IO].unsafeRunSync() + + new SomeService[IO].logIt("Wow such simple no F[Log[F]]") + + } + +}