From 366d80e166b2bb95a78b06d498a31c858e666983 Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Fri, 31 May 2024 13:13:45 -0700 Subject: [PATCH] Fold deferred log messages into existing log message class --- .../log4cats/extras/DeferredLogger.scala | 90 ++++------ .../extras/DeferredLoggerFactory.scala | 34 ++-- .../DeferredSelfAwareStructuredLogger.scala | 154 ++++++++-------- .../extras/DeferredStructuredLogger.scala | 166 +++++++----------- .../typelevel/log4cats/extras/LogLevel.scala | 22 ++- .../log4cats/extras/LogMessage.scala | 88 +++++++++- .../extras/DeferredLoggerFactoryTest.scala | 52 +++--- 7 files changed, 311 insertions(+), 295 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala index 93a06346..b14d805a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala @@ -21,7 +21,6 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import org.typelevel.log4cats.Logger -import org.typelevel.log4cats.extras.DeferredLogger.DeferredLogMessage /** * `Logger` that does not immediately log. @@ -49,13 +48,13 @@ import org.typelevel.log4cats.extras.DeferredLogger.DeferredLogMessage * >>> WARNING: READ BEFORE USAGE! <<< */ trait DeferredLogger[F[_]] extends Logger[F] { + /** * View the logs in the buffer. * - * This is primarily useful for testing, and will not effect the behavior - * of calls to `log` + * This is primarily useful for testing, and will not effect the behavior of calls to `log` */ - def inspect: F[Chain[DeferredLogMessage]] + def inspect: F[Chain[LogMessage]] /** * Log the deferred messages @@ -67,70 +66,47 @@ trait DeferredLogger[F[_]] extends Logger[F] { object DeferredLogger { def apply[F[_]](logger: Logger[F])(implicit F: Concurrent[F]): Resource[F, DeferredLogger[F]] = Resource - .makeCase(Ref.empty[F, Chain[DeferredLogMessage]]) { (ref, exitCase) => + .makeCase(Ref.empty[F, Chain[LogMessage]]) { (ref, exitCase) => exitCase match { case ExitCase.Succeeded => F.unit - case _ => ref.get.flatMap(_.traverse_(_.log(logger))) + case _ => ref.get.flatMap(_.traverse_(LogMessage.log(_, logger))) } } .map { ref => new DeferredLogger[F] { - private def save(lm: DeferredLogMessage): F[Unit] = ref.update(_.append(lm)) + private def save(lm: LogMessage): F[Unit] = ref.update(_.append(lm)) - override def trace(t: Throwable)(msg: => String): F[Unit] = save(Trace(() => msg, t.some)) - override def debug(t: Throwable)(msg: => String): F[Unit] = save(Debug(() => msg, t.some)) - override def info(t: Throwable)(msg: => String): F[Unit] = save(Info(() => msg, t.some)) - override def warn(t: Throwable)(msg: => String): F[Unit] = save(Warn(() => msg, t.some)) - override def error(t: Throwable)(msg: => String): F[Unit] = save(Error(() => msg, t.some)) + private def trace(msg: => String, tOpt: Option[Throwable]): F[Unit] = + save(LogMessage(LogLevel.Trace, msg, tOpt, Map.empty)) - override def trace(msg: => String): F[Unit] = save(Trace(() => msg, none)) - override def debug(msg: => String): F[Unit] = save(Debug(() => msg, none)) - override def info(msg: => String): F[Unit] = save(Info(() => msg, none)) - override def warn(msg: => String): F[Unit] = save(Warn(() => msg, none)) - override def error(msg: => String): F[Unit] = save(Error(() => msg, none)) + private def debug(msg: => String, tOpt: Option[Throwable]): F[Unit] = + save(LogMessage(LogLevel.Debug, msg, tOpt, Map.empty)) - override def inspect: F[Chain[DeferredLogMessage]] = ref.get + private def info(msg: => String, tOpt: Option[Throwable]): F[Unit] = + save(LogMessage(LogLevel.Info, msg, tOpt, Map.empty)) - override def log: F[Unit] = ref.getAndSet(Chain.empty).flatMap(_.traverse_(_.log(logger))) - } - } + private def warn(msg: => String, tOpt: Option[Throwable]): F[Unit] = + save(LogMessage(LogLevel.Warn, msg, tOpt, Map.empty)) + + private def error(msg: => String, tOpt: Option[Throwable]): F[Unit] = + save(LogMessage(LogLevel.Error, msg, tOpt, Map.empty)) - sealed trait DeferredLogMessage { - def message: () => String - def throwOpt: Option[Throwable] + override def trace(t: Throwable)(msg: => String): F[Unit] = trace(msg, t.some) + override def debug(t: Throwable)(msg: => String): F[Unit] = debug(msg, t.some) + override def info(t: Throwable)(msg: => String): F[Unit] = info(msg, t.some) + override def warn(t: Throwable)(msg: => String): F[Unit] = warn(msg, t.some) + override def error(t: Throwable)(msg: => String): F[Unit] = error(msg, t.some) - def log[F[_]](logger: Logger[F]): F[Unit] = this match { - case Trace(message, Some(e)) => logger.trace(e)(message()) - case Trace(message, None) => logger.trace(message()) - case Debug(message, Some(e)) => logger.debug(e)(message()) - case Debug(message, None) => logger.debug(message()) - case Info(message, Some(e)) => logger.info(e)(message()) - case Info(message, None) => logger.info(message()) - case Warn(message, Some(e)) => logger.warn(e)(message()) - case Warn(message, None) => logger.warn(message()) - case Error(message, Some(e)) => logger.error(e)(message()) - case Error(message, None) => logger.error(message()) - } - } + override def trace(msg: => String): F[Unit] = trace(msg, none) + override def debug(msg: => String): F[Unit] = debug(msg, none) + override def info(msg: => String): F[Unit] = info(msg, none) + override def warn(msg: => String): F[Unit] = warn(msg, none) + override def error(msg: => String): F[Unit] = error(msg, none) - final case class Trace( - message: () => String, - throwOpt: Option[Throwable] - ) extends DeferredLogMessage - final case class Debug( - message: () => String, - throwOpt: Option[Throwable] - ) extends DeferredLogMessage - final case class Info( - message: () => String, - throwOpt: Option[Throwable] - ) extends DeferredLogMessage - final case class Warn( - message: () => String, - throwOpt: Option[Throwable] - ) extends DeferredLogMessage - final case class Error( - message: () => String, - throwOpt: Option[Throwable] - ) extends DeferredLogMessage + override def inspect: F[Chain[LogMessage]] = ref.get + + override def log: F[Unit] = + ref.getAndSet(Chain.empty).flatMap(_.traverse_(LogMessage.log(_, logger))) + } + } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLoggerFactory.scala index 37161b24..85f37dc7 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLoggerFactory.scala @@ -21,30 +21,29 @@ import cats.data.Chain import cats.effect.kernel.{Concurrent, Resource} import cats.syntax.all.* import cats.{~>, Functor} -import org.typelevel.log4cats.extras.DeferredStructuredLogger.DeferredStructuredLogMessage import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} /** * A `LoggerFactory` that does not immediately log. * - * Effectively a `LoggerFactory` equivalent to `DeferredSelfAwareStructuredLogger`. - * As an implementation note, the `LoggerFactory` trait is constrained in such a way that - * this will produce `SelfAwareStructuredLogger`, rather than `DeferredSelfAwareStructuredLogger`, - * so if logging is desired it needs to be triggered using the `DeferredLoggerFactory`, rather than - * being able to trigger it from any of the produced loggers. + * Effectively a `LoggerFactory` equivalent to `DeferredSelfAwareStructuredLogger`. As an + * implementation note, the `LoggerFactory` trait is constrained in such a way that this will + * produce `SelfAwareStructuredLogger`, rather than `DeferredSelfAwareStructuredLogger`, so if + * logging is desired it needs to be triggered using the `DeferredLoggerFactory`, rather than being + * able to trigger it from any of the produced loggers. * * >>> WARNING: READ BEFORE USAGE! <<< * https://github.com/typelevel/log4cats/blob/main/core/shared/src/main/scala/org/typelevel/log4cats/extras/README.md * >>> WARNING: READ BEFORE USAGE! <<< */ trait DeferredLoggerFactory[F[_]] extends LoggerFactory[F] { + /** * View the logs in the buffer. * - * This is primarily useful for testing, and will not effect the behavior - * of calls to `log` + * This is primarily useful for testing, and will not effect the behavior of calls to `log` */ - def inspect: F[Chain[DeferredStructuredLogMessage]] + def inspect: F[Chain[LogMessage]] /** * Log the deferred messages @@ -80,14 +79,12 @@ object DeferredLoggerFactory { ): Resource[F, DeferredLoggerFactory[F]] = DeferredSelfAwareStructuredLogger.makeCache[F].map { cache => new DeferredLoggerFactory[F] { - override def inspect: F[Chain[DeferredStructuredLogMessage]] = cache.get.map(_._1F) + override def inspect: F[Chain[LogMessage]] = cache.get.map(_._1F) override def log: F[Unit] = { cache .getAndSet(Chain.empty) - .flatMap(_.traverse_ { case (msg, logger) => - msg.log(logger) - }) + .flatMap(_.traverse_((LogMessage.logStructured[F] _).tupled)) } override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = @@ -102,9 +99,8 @@ object DeferredLoggerFactory { fk: F ~> G )(lf: DeferredLoggerFactory[F]): DeferredLoggerFactory[G] = new DeferredLoggerFactory[G] { - override def inspect: G[Chain[DeferredStructuredLogger.DeferredStructuredLogMessage]] = fk( - lf.inspect - ) + override def inspect: G[Chain[LogMessage]] = fk(lf.inspect) + override def log: G[Unit] = fk(lf.log) override def getLoggerFromName(name: String): SelfAwareStructuredLogger[G] = @@ -120,8 +116,7 @@ object DeferredLoggerFactory { ctx: Map[String, String] ): DeferredLoggerFactory[F] = new DeferredLoggerFactory[F] { - override def inspect: F[Chain[DeferredStructuredLogger.DeferredStructuredLogMessage]] = - lf.inspect + override def inspect: F[Chain[LogMessage]] = lf.inspect override def log: F[Unit] = lf.log override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = @@ -136,8 +131,7 @@ object DeferredLoggerFactory { f: String => String ): DeferredLoggerFactory[F] = new DeferredLoggerFactory[F] { - override def inspect: F[Chain[DeferredStructuredLogger.DeferredStructuredLogMessage]] = - lf.inspect + override def inspect: F[Chain[LogMessage]] = lf.inspect override def log: F[Unit] = lf.log override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala index f705bf40..77fb1d9f 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala @@ -22,14 +22,6 @@ import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.{~>, Show} import org.typelevel.log4cats.SelfAwareStructuredLogger -import org.typelevel.log4cats.extras.DeferredStructuredLogger.{ - Debug, - DeferredStructuredLogMessage, - Error, - Info, - Trace, - Warn -} /** * Similar to `DeferredStructuredLogger`, for `SelfAwareStructuredLogger` @@ -39,13 +31,13 @@ import org.typelevel.log4cats.extras.DeferredStructuredLogger.{ * >>> WARNING: READ BEFORE USAGE! <<< */ trait DeferredSelfAwareStructuredLogger[F[_]] extends SelfAwareStructuredLogger[F] { + /** * View the logs in the buffer. * - * This is primarily useful for testing, and will not effect the behavior - * of calls to `log` + * This is primarily useful for testing, and will not effect the behavior of calls to `log` */ - def inspect: F[Chain[DeferredStructuredLogMessage]] + def inspect: F[Chain[LogMessage]] /** * Log the deferred messages @@ -76,10 +68,10 @@ object DeferredSelfAwareStructuredLogger { def apply[F[_]: Concurrent]( logger: SelfAwareStructuredLogger[F], - stash: Ref[F, Chain[(DeferredStructuredLogMessage, SelfAwareStructuredLogger[F])]] + stash: Ref[F, Chain[(LogMessage, SelfAwareStructuredLogger[F])]] ): DeferredSelfAwareStructuredLogger[F] = new DeferredSelfAwareStructuredLogger[F] { - private def save(lm: DeferredStructuredLogMessage): F[Unit] = + private def save(lm: LogMessage): F[Unit] = stash.update(_.append(lm -> logger)) override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled @@ -88,84 +80,86 @@ object DeferredSelfAwareStructuredLogger { override def isWarnEnabled: F[Boolean] = logger.isWarnEnabled override def isErrorEnabled: F[Boolean] = logger.isErrorEnabled - private def saveTrace(lm: Trace): F[Unit] = isTraceEnabled.flatMap(save(lm).whenA(_)) - private def saveDebug(lm: Debug): F[Unit] = isDebugEnabled.flatMap(save(lm).whenA(_)) - private def saveInfo(lm: Info): F[Unit] = isInfoEnabled.flatMap(save(lm).whenA(_)) - private def saveWarn(lm: Warn): F[Unit] = isWarnEnabled.flatMap(save(lm).whenA(_)) - private def saveError(lm: Error): F[Unit] = isErrorEnabled.flatMap(save(lm).whenA(_)) + private def trace( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + isTraceEnabled.flatMap(save(LogMessage(LogLevel.Trace, msg, tOpt, context)).whenA(_)) + + private def debug( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + isDebugEnabled.flatMap(save(LogMessage(LogLevel.Debug, msg, tOpt, context)).whenA(_)) + + private def info( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + isInfoEnabled.flatMap(save(LogMessage(LogLevel.Info, msg, tOpt, context)).whenA(_)) + + private def warn( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + isWarnEnabled.flatMap(save(LogMessage(LogLevel.Warn, msg, tOpt, context)).whenA(_)) + + private def error( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + isErrorEnabled.flatMap(save(LogMessage(LogLevel.Error, msg, tOpt, context)).whenA(_)) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = trace(msg, none, ctx) + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = debug(msg, none, ctx) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = info(msg, none, ctx) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = warn(msg, none, ctx) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = error(msg, none, ctx) - override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = saveTrace( - Trace(() => msg, none, ctx) - ) - override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = saveDebug( - Debug(() => msg, none, ctx) - ) - override def info(ctx: Map[String, String])(msg: => String): F[Unit] = saveInfo( - Info(() => msg, none, ctx) - ) - override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = saveWarn( - Warn(() => msg, none, ctx) - ) - override def error(ctx: Map[String, String])(msg: => String): F[Unit] = saveError( - Error(() => msg, none, ctx) - ) - - override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = saveTrace( - Trace(() => msg, t.some, ctx) - ) - override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = saveDebug( - Debug(() => msg, t.some, ctx) - ) - override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = saveInfo( - Info(() => msg, t.some, ctx) - ) - override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = saveWarn( - Warn(() => msg, t.some, ctx) - ) - override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = saveError( - Error(() => msg, t.some, ctx) - ) + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + trace(msg, t.some, ctx) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + debug(msg, t.some, ctx) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + info(msg, t.some, ctx) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + warn(msg, t.some, ctx) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + error(msg, t.some, ctx) - override def trace(t: Throwable)(msg: => String): F[Unit] = saveTrace( - Trace(() => msg, t.some, Map.empty) - ) - override def debug(t: Throwable)(msg: => String): F[Unit] = saveDebug( - Debug(() => msg, t.some, Map.empty) - ) - override def info(t: Throwable)(msg: => String): F[Unit] = saveInfo( - Info(() => msg, t.some, Map.empty) - ) - override def warn(t: Throwable)(msg: => String): F[Unit] = saveWarn( - Warn(() => msg, t.some, Map.empty) - ) - override def error(t: Throwable)(msg: => String): F[Unit] = saveError( - Error(() => msg, t.some, Map.empty) - ) + override def trace(t: Throwable)(msg: => String): F[Unit] = trace(msg, t.some, Map.empty) + override def debug(t: Throwable)(msg: => String): F[Unit] = debug(msg, t.some, Map.empty) + override def info(t: Throwable)(msg: => String): F[Unit] = info(msg, t.some, Map.empty) + override def warn(t: Throwable)(msg: => String): F[Unit] = warn(msg, t.some, Map.empty) + override def error(t: Throwable)(msg: => String): F[Unit] = error(msg, t.some, Map.empty) - override def trace(msg: => String): F[Unit] = saveTrace(Trace(() => msg, none, Map.empty)) - override def debug(msg: => String): F[Unit] = saveDebug(Debug(() => msg, none, Map.empty)) - override def info(msg: => String): F[Unit] = saveInfo(Info(() => msg, none, Map.empty)) - override def warn(msg: => String): F[Unit] = saveWarn(Warn(() => msg, none, Map.empty)) - override def error(msg: => String): F[Unit] = saveError(Error(() => msg, none, Map.empty)) + override def trace(msg: => String): F[Unit] = trace(msg, none, Map.empty) + override def debug(msg: => String): F[Unit] = debug(msg, none, Map.empty) + override def info(msg: => String): F[Unit] = info(msg, none, Map.empty) + override def warn(msg: => String): F[Unit] = warn(msg, none, Map.empty) + override def error(msg: => String): F[Unit] = error(msg, none, Map.empty) - override def inspect: F[Chain[DeferredStructuredLogMessage]] = stash.get.map(_._1F) + override def inspect: F[Chain[LogMessage]] = stash.get.map(_._1F) override def log: F[Unit] = stash .getAndSet(Chain.empty) - .flatMap(_.traverse_ { case (msg, logger) => - msg.log(logger) - }) + .flatMap(_.traverse_((LogMessage.logStructured[F] _).tupled)) } private[extras] def makeCache[F[_]](implicit F: Concurrent[F] - ): Resource[F, Ref[F, Chain[(DeferredStructuredLogMessage, SelfAwareStructuredLogger[F])]]] = + ): Resource[F, Ref[F, Chain[(LogMessage, SelfAwareStructuredLogger[F])]]] = Resource - .makeCase(Ref.empty[F, Chain[(DeferredStructuredLogMessage, SelfAwareStructuredLogger[F])]]) { + .makeCase(Ref.empty[F, Chain[(LogMessage, SelfAwareStructuredLogger[F])]]) { (ref, exitCase) => exitCase match { case ExitCase.Succeeded => F.unit - case _ => ref.get.flatMap(_.traverse_ { case (msg, logger) => msg.log(logger) }) + case _ => ref.get.flatMap(_.traverse_((LogMessage.logStructured[F] _).tupled)) } } @@ -174,7 +168,7 @@ object DeferredSelfAwareStructuredLogger { fk: F ~> G ): DeferredSelfAwareStructuredLogger[G] = new DeferredSelfAwareStructuredLogger[G] { - override def inspect: G[Chain[DeferredStructuredLogger.DeferredStructuredLogMessage]] = fk( + override def inspect: G[Chain[LogMessage]] = fk( logger.inspect ) override def log: G[Unit] = fk(logger.log) @@ -235,8 +229,7 @@ object DeferredSelfAwareStructuredLogger { new DeferredSelfAwareStructuredLogger[F] { private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx - override def inspect: F[Chain[DeferredStructuredLogger.DeferredStructuredLogMessage]] = - logger.inspect + override def inspect: F[Chain[LogMessage]] = logger.inspect override def log: F[Unit] = logger.log override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled @@ -289,8 +282,7 @@ object DeferredSelfAwareStructuredLogger { f: String => String ): DeferredSelfAwareStructuredLogger[F] = new DeferredSelfAwareStructuredLogger[F] { - override def inspect: F[Chain[DeferredStructuredLogger.DeferredStructuredLogMessage]] = - logger.inspect + override def inspect: F[Chain[LogMessage]] = logger.inspect override def log: F[Unit] = logger.log override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala index b34bc73c..1b44ff1f 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala @@ -19,10 +19,8 @@ package org.typelevel.log4cats.extras import cats.data.Chain import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} -import cats.kernel.Hash import cats.syntax.all.* import org.typelevel.log4cats.StructuredLogger -import org.typelevel.log4cats.extras.DeferredStructuredLogger.DeferredStructuredLogMessage /** * `StructuredLogger` that does not immediately log. @@ -50,13 +48,13 @@ import org.typelevel.log4cats.extras.DeferredStructuredLogger.DeferredStructured * >>> WARNING: READ BEFORE USAGE! <<< */ trait DeferredStructuredLogger[F[_]] extends StructuredLogger[F] { + /** * View the logs in the buffer. * - * This is primarily useful for testing, and will not effect the behavior - * of calls to `log` + * This is primarily useful for testing, and will not effect the behavior of calls to `log` */ - def inspect: F[Chain[DeferredStructuredLogMessage]] + def inspect: F[Chain[LogMessage]] /** * Log the deferred messages @@ -70,117 +68,89 @@ object DeferredStructuredLogger { logger: StructuredLogger[F] )(implicit F: Concurrent[F]): Resource[F, DeferredStructuredLogger[F]] = Resource - .makeCase(Ref.empty[F, Chain[DeferredStructuredLogMessage]]) { (ref, exitCase) => + .makeCase(Ref.empty[F, Chain[LogMessage]]) { (ref, exitCase) => exitCase match { case ExitCase.Succeeded => F.unit - case _ => ref.get.flatMap(_.traverse_(_.log(logger))) + case _ => ref.get.flatMap(_.traverse_(LogMessage.logStructured(_, logger))) } } .map { ref => new DeferredStructuredLogger[F] { - private def save(lm: DeferredStructuredLogMessage): F[Unit] = ref.update(_.append(lm)) + private def save(lm: LogMessage): F[Unit] = ref.update(_.append(lm)) + + private def trace( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + save(LogMessage(LogLevel.Trace, msg, tOpt, context)) + + private def debug( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + save(LogMessage(LogLevel.Debug, msg, tOpt, context)) + + private def info( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + save(LogMessage(LogLevel.Info, msg, tOpt, context)) + + private def warn( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + save(LogMessage(LogLevel.Warn, msg, tOpt, context)) + + private def error( + msg: => String, + tOpt: Option[Throwable], + context: Map[String, String] + ): F[Unit] = + save(LogMessage(LogLevel.Error, msg, tOpt, context)) override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - save(Trace(() => msg, none, ctx)) + trace(msg, none, ctx) override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - save(Debug(() => msg, none, ctx)) + debug(msg, none, ctx) override def info(ctx: Map[String, String])(msg: => String): F[Unit] = - save(Info(() => msg, none, ctx)) + info(msg, none, ctx) override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - save(Warn(() => msg, none, ctx)) + warn(msg, none, ctx) override def error(ctx: Map[String, String])(msg: => String): F[Unit] = - save(Error(() => msg, none, ctx)) + error(msg, none, ctx) override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - save(Trace(() => msg, t.some, ctx)) + trace(msg, t.some, ctx) override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - save(Debug(() => msg, t.some, ctx)) + debug(msg, t.some, ctx) override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - save(Info(() => msg, t.some, ctx)) + info(msg, t.some, ctx) override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - save(Warn(() => msg, t.some, ctx)) + warn(msg, t.some, ctx) override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - save(Error(() => msg, t.some, ctx)) - - override def trace(t: Throwable)(msg: => String): F[Unit] = - save(Trace(() => msg, t.some, Map.empty)) - override def debug(t: Throwable)(msg: => String): F[Unit] = - save(Debug(() => msg, t.some, Map.empty)) - override def info(t: Throwable)(msg: => String): F[Unit] = - save(Info(() => msg, t.some, Map.empty)) - override def warn(t: Throwable)(msg: => String): F[Unit] = - save(Warn(() => msg, t.some, Map.empty)) - override def error(t: Throwable)(msg: => String): F[Unit] = - save(Error(() => msg, t.some, Map.empty)) - - override def trace(msg: => String): F[Unit] = save(Trace(() => msg, none, Map.empty)) - override def debug(msg: => String): F[Unit] = save(Debug(() => msg, none, Map.empty)) - override def info(msg: => String): F[Unit] = save(Info(() => msg, none, Map.empty)) - override def warn(msg: => String): F[Unit] = save(Warn(() => msg, none, Map.empty)) - override def error(msg: => String): F[Unit] = save(Error(() => msg, none, Map.empty)) - - override def inspect: F[Chain[DeferredStructuredLogMessage]] = ref.get - - override def log: F[Unit] = ref.getAndSet(Chain.empty).flatMap(_.traverse_(_.log(logger))) + error(msg, t.some, ctx) + + override def trace(t: Throwable)(msg: => String): F[Unit] = trace(msg, t.some, Map.empty) + override def debug(t: Throwable)(msg: => String): F[Unit] = debug(msg, t.some, Map.empty) + override def info(t: Throwable)(msg: => String): F[Unit] = info(msg, t.some, Map.empty) + override def warn(t: Throwable)(msg: => String): F[Unit] = warn(msg, t.some, Map.empty) + override def error(t: Throwable)(msg: => String): F[Unit] = error(msg, t.some, Map.empty) + + override def trace(msg: => String): F[Unit] = trace(msg, none, Map.empty) + override def debug(msg: => String): F[Unit] = debug(msg, none, Map.empty) + override def info(msg: => String): F[Unit] = info(msg, none, Map.empty) + override def warn(msg: => String): F[Unit] = warn(msg, none, Map.empty) + override def error(msg: => String): F[Unit] = error(msg, none, Map.empty) + + override def inspect: F[Chain[LogMessage]] = ref.get + + override def log: F[Unit] = + ref.getAndSet(Chain.empty).flatMap(_.traverse_(LogMessage.logStructured(_, logger))) } } - - sealed trait DeferredStructuredLogMessage { - def ctx: Map[String, String] - def message: () => String - def throwOpt: Option[Throwable] - - def log[F[_]](logger: StructuredLogger[F]): F[Unit] = this match { - case Trace(message, Some(e), ctx) => logger.trace(ctx, e)(message()) - case Trace(message, None, ctx) => logger.trace(ctx)(message()) - case Debug(message, Some(e), ctx) => logger.debug(ctx, e)(message()) - case Debug(message, None, ctx) => logger.debug(ctx)(message()) - case Info(message, Some(e), ctx) => logger.info(ctx, e)(message()) - case Info(message, None, ctx) => logger.info(ctx)(message()) - case Warn(message, Some(e), ctx) => logger.warn(ctx, e)(message()) - case Warn(message, None, ctx) => logger.warn(ctx)(message()) - case Error(message, Some(e), ctx) => logger.error(ctx, e)(message()) - case Error(message, None, ctx) => logger.error(ctx)(message()) - } - - override def equals(obj: Any): Boolean = obj match { - case other: DeferredStructuredLogMessage => deferredStructuredLogMessageHash.eqv(this, other) - } - - override def hashCode(): Int = deferredStructuredLogMessageHash.hash(this) - } - - final case class Trace( - message: () => String, - throwOpt: Option[Throwable], - ctx: Map[String, String] - ) extends DeferredStructuredLogMessage - final case class Debug( - message: () => String, - throwOpt: Option[Throwable], - ctx: Map[String, String] - ) extends DeferredStructuredLogMessage - final case class Info( - message: () => String, - throwOpt: Option[Throwable], - ctx: Map[String, String] - ) extends DeferredStructuredLogMessage - final case class Warn( - message: () => String, - throwOpt: Option[Throwable], - ctx: Map[String, String] - ) extends DeferredStructuredLogMessage - final case class Error( - message: () => String, - throwOpt: Option[Throwable], - ctx: Map[String, String] - ) extends DeferredStructuredLogMessage - - implicit val deferredStructuredLogMessageHash: Hash[DeferredStructuredLogMessage] = Hash.by { - case Trace(message, throwOpt, ctx) => (0, message(), throwOpt.map(t => (t.getClass.getTypeName, t.getMessage)), ctx) - case Debug(message, throwOpt, ctx) => (1, message(), throwOpt.map(t => (t.getClass.getTypeName, t.getMessage)), ctx) - case Info(message, throwOpt, ctx) => (2, message(), throwOpt.map(t => (t.getClass.getTypeName, t.getMessage)), ctx) - case Warn(message, throwOpt, ctx) => (3, message(), throwOpt.map(t => (t.getClass.getTypeName, t.getMessage)), ctx) - case Error(message, throwOpt, ctx) => (4, message(), throwOpt.map(t => (t.getClass.getTypeName, t.getMessage)), ctx) - } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala index dd2ed730..7e5d9fe4 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala @@ -19,7 +19,11 @@ package org.typelevel.log4cats.extras import cats._ sealed trait LogLevel -object LogLevel { +trait LogLevelInstances { + implicit val logLevelHash: Hash[LogLevel] = + Hash.by(LogLevel.indexOf) +} +object LogLevel extends LogLevelInstances { case object Error extends LogLevel case object Warn extends LogLevel case object Info extends LogLevel @@ -48,12 +52,14 @@ object LogLevel { case Trace => "LogLevel.Trace" } + private[extras] def indexOf(logLevel: LogLevel): Int = logLevel match { + case Error => 5 + case Warn => 4 + case Info => 3 + case Debug => 2 + case Trace => 1 + } + implicit final val logLevelOrder: Order[LogLevel] = - Order.by[LogLevel, Int] { - case Error => 5 - case Warn => 4 - case Info => 3 - case Debug => 2 - case Trace => 1 - } + Order.by[LogLevel, Int](indexOf) } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala index 9d549766..3a4d601e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala @@ -16,14 +16,78 @@ package org.typelevel.log4cats.extras -import cats._ -import cats.syntax.all._ -import org.typelevel.log4cats.Logger +import cats.* +import cats.kernel.Hash +import cats.syntax.all.* +import org.typelevel.log4cats.{Logger, StructuredLogger} -final case class LogMessage(level: LogLevel, t: Option[Throwable], message: String) +sealed trait LogMessage { + def level: LogLevel + + /** + * The message is lazy to preserve the semantics of the methods on `Logger` + */ + def lazyMessage: () => String + + @deprecated("Deprecated in favor of lazyMessage", since = "2.1.2") + def message: String + + @deprecated("Deprecated in favor of error", since = "2.1.2") + def t: Option[Throwable] + def error: Option[Throwable] + + def context: Map[String, String] + + override def equals(obj: Any): Boolean = obj match { + case other: LogMessage => LogMessage.logMessageHash.eqv(this, other) + } + + override def hashCode(): Int = LogMessage.logMessageHash.hash(this) + + override def toString: String = LogMessage.logMessageShow.show(this) +} object LogMessage { + + def apply(level: LogLevel, t: Option[Throwable], message: String): LogMessage = + LogMessageImpl(level, t, () => message, Map.empty) + + def apply( + level: LogLevel, + message: => String, + t: Option[Throwable], + context: Map[String, String] + ): LogMessage = + LogMessageImpl(level, t, () => message, context) + + def unapply(lm: LogMessage): Some[(LogLevel, Option[Throwable], String)] = Some( + (lm.level, lm.t, lm.message) + ) + + private final case class LogMessageImpl( + level: LogLevel, + error: Option[Throwable], + // Lazy to keep the semantics of by-name messages in Logger + lazyMessage: () => String, + context: Map[String, String] + ) extends LogMessage { + def message: String = lazyMessage() + + def t: Option[Throwable] = error + } + implicit val logMessageShow: Show[LogMessage] = - Show.show[LogMessage](l => show"LogMessage(${l.level},${l.t.map(_.getMessage)},${l.message})") + Show.show[LogMessage](l => + show"LogMessage(${l.level},${l.t.map(_.getMessage)},${l.lazyMessage()})" + ) + + implicit val logMessageHash: Hash[LogMessage] = Hash.by { lm => + ( + lm.level, + lm.lazyMessage(), + lm.error.map(t => (t.getClass.getTypeName, t.getMessage)), + lm.context + ) + } def log[F[_]](sm: LogMessage, l: Logger[F]): F[Unit] = sm match { case LogMessage(LogLevel.Trace, Some(t), m) => l.trace(t)(m) @@ -41,4 +105,18 @@ object LogMessage { case LogMessage(LogLevel.Error, Some(t), m) => l.error(t)(m) case LogMessage(LogLevel.Error, None, m) => l.error(m) } + + def logStructured[F[_]](lm: LogMessage, logger: StructuredLogger[F]): F[Unit] = + (lm.level, lm.error) match { + case (LogLevel.Trace, Some(e)) => logger.trace(lm.context, e)(lm.lazyMessage()) + case (LogLevel.Trace, None) => logger.trace(lm.context)(lm.lazyMessage()) + case (LogLevel.Debug, Some(e)) => logger.debug(lm.context, e)(lm.lazyMessage()) + case (LogLevel.Debug, None) => logger.debug(lm.context)(lm.lazyMessage()) + case (LogLevel.Info, Some(e)) => logger.info(lm.context, e)(lm.lazyMessage()) + case (LogLevel.Info, None) => logger.info(lm.context)(lm.lazyMessage()) + case (LogLevel.Warn, Some(e)) => logger.warn(lm.context, e)(lm.lazyMessage()) + case (LogLevel.Warn, None) => logger.warn(lm.context)(lm.lazyMessage()) + case (LogLevel.Error, Some(e)) => logger.error(lm.context, e)(lm.lazyMessage()) + case (LogLevel.Error, None) => logger.error(lm.context)(lm.lazyMessage()) + } } diff --git a/testing/shared/src/test/scala/org/typelevel/log4cats/extras/DeferredLoggerFactoryTest.scala b/testing/shared/src/test/scala/org/typelevel/log4cats/extras/DeferredLoggerFactoryTest.scala index 0f06f0b1..ec89bebf 100644 --- a/testing/shared/src/test/scala/org/typelevel/log4cats/extras/DeferredLoggerFactoryTest.scala +++ b/testing/shared/src/test/scala/org/typelevel/log4cats/extras/DeferredLoggerFactoryTest.scala @@ -301,35 +301,35 @@ class DeferredLoggerFactoryTest extends munit.CatsEffectSuite { val testLoggerFactory = TestingLoggerFactory.atomic[IO]( debugEnabled = false ) - DeferredLoggerFactory(testLoggerFactory) - .use { loggerFactory => - val loggerName = "Test Logger" - val logger = loggerFactory.getLoggerFromName(loggerName) - for { - _ <- logger.trace("Test Message 0") - _ <- logger.debug("Test Message 1") - _ <- logger.info("Test Message 2") - _ <- testLoggerFactory.logged.assertEquals( - Vector.empty, - clue("Checking that logging is deferred") - ) - _ <- loggerFactory.inspect.map(_.toVector).assertEquals( + DeferredLoggerFactory(testLoggerFactory).use { loggerFactory => + val loggerName = "Test Logger" + val logger = loggerFactory.getLoggerFromName(loggerName) + for { + _ <- logger.trace("Test Message 0") + _ <- logger.debug("Test Message 1") + _ <- logger.info("Test Message 2") + _ <- testLoggerFactory.logged.assertEquals( + Vector.empty, + clue("Checking that logging is deferred") + ) + _ <- loggerFactory.inspect + .map(_.toVector) + .assertEquals( Vector( - DeferredStructuredLogger.Trace(() => "Test Message 0", none, Map.empty), - DeferredStructuredLogger.Info(() => "Test Message 2", none, Map.empty) + LogMessage(LogLevel.Trace, "Test Message 0", none, Map.empty), + LogMessage(LogLevel.Info, "Test Message 2", none, Map.empty) ), clue("Checking that the debug message was not buffered") ) - _ <- loggerFactory.log - _ <- testLoggerFactory.logged.assertEquals( - Vector( - Trace(loggerName, "Test Message 0", none), - Info(loggerName, "Test Message 2", none) - ), - clue("Checking that logs were sent to test logger") - ) - } yield () - } - .assert + _ <- loggerFactory.log + _ <- testLoggerFactory.logged.assertEquals( + Vector( + Trace(loggerName, "Test Message 0", none), + Info(loggerName, "Test Message 2", none) + ), + clue("Checking that logs were sent to test logger") + ) + } yield () + }.assert } }