All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.odin.extras.loggers.ConditionalLogger.scala Maven / Gradle / Ivy

The newest version!
package io.odin.extras.loggers

import cats.MonadError
import cats.effect.kernel.{Async, Clock, Resource}
import cats.effect.kernel.Resource.ExitCase
import cats.effect.std.Queue
import cats.syntax.applicativeError._
import cats.syntax.flatMap._
import cats.syntax.functor._
import io.odin.loggers.DefaultLogger
import io.odin.{Level, Logger, LoggerMessage}

final case class ConditionalLogger[F[_]: Clock] private (
    queue: Queue[F, LoggerMessage],
    inner: Logger[F],
    override val minLevel: Level
)(implicit F: MonadError[F, Throwable])
    extends DefaultLogger[F](minLevel) {

  def submit(msg: LoggerMessage): F[Unit] =
    queue.tryOffer(msg).void

  private def drain(exitCase: ExitCase): F[Unit] = {
    val level = exitCase match {
      case ExitCase.Succeeded => inner.minLevel
      case _                  => minLevel
    }

    drainAll
      .flatMap(msgs => inner.withMinimalLevel(level).log(msgs.toList))
      .attempt
      .void
  }

  def withMinimalLevel(level: Level): Logger[F] = copy(inner = inner.withMinimalLevel(level), minLevel = level)

  private def drainAll: F[Vector[LoggerMessage]] =
    F.tailRecM(Vector.empty[LoggerMessage]) { acc =>
      queue.tryTake.map {
        case Some(value) => Left(acc :+ value)
        case None        => Right(acc)
      }
    }

}

object ConditionalLogger {

  /**
    * Create ConditionalLogger that buffers messages and sends them to the inner logger when the resource is released.
    * If evaluation of the bracket completed with an error, the `fallbackLevel` is used as a `minLevel`.
    *
    * Example:
    * {{{
    *   consoleLogger[F](minLevel = Level.Info).withErrorLevel(Level.Debug) { logger =>
    *     logger.debug("debug message") >> trickyCode
    *   }
    * }}}
    *
    * If evaluation completed with an error, the messages with `level >= Level.Debug` will be sent to an inner logger.
    * If evaluation completed successfully, the messages with `level >= Level.Info` will be sent to an inner logger.
    *
    * '''Important:''' nothing is logged until the resource is released.
    * Example:
    * {{{
    * consoleLogger[F](minLevel = Level.Info).withErrorLevel(Level.Debug) { logger =>
    *   logger.info("info log") >> Timer[F].sleep(10.seconds) >> logger.debug("debug log")
    * }
    * }}}
    *
    * The message will be logged after 10 seconds. Thus use the logger with caution.
    *
    * @param inner logger that will receive messages from the buffer
    * @param minLevelOnError min level that will be used in case of error
    * @param maxBufferSize If `maxBufferSize` is set to some value and buffer size grows to that value,
    *                      any new events might be dropped until there is a space in the buffer.
    */
  def create[F[_]: Async](
      inner: Logger[F],
      minLevelOnError: Level,
      maxBufferSize: Option[Int]
  ): Resource[F, Logger[F]] = {

    val createQueue = maxBufferSize match {
      case Some(value) => Queue.bounded[F, LoggerMessage](value)
      case None        => Queue.unbounded[F, LoggerMessage]
    }

    def acquire: F[ConditionalLogger[F]] =
      for {
        queue <- createQueue
      } yield ConditionalLogger(queue, inner, minLevelOnError)

    def release(logger: ConditionalLogger[F], exitCase: ExitCase): F[Unit] =
      logger.drain(exitCase)

    Resource.makeCase(acquire)(release).widen
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy