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

izumi.fundamentals.platform.console.TrivialLogger.scala Maven / Gradle / Ivy

package izumi.fundamentals.platform.console

import izumi.fundamentals.platform.console.TrivialLogger.{Config, Level}
import izumi.fundamentals.platform.exceptions.IzThrowable._
import izumi.fundamentals.platform.strings.IzString._

import scala.annotation.nowarn
import scala.collection.mutable
import scala.reflect.{ClassTag, classTag}

trait TrivialLogger {
  def log(s: => String): Unit
  def log(s: => String, e: => Throwable): Unit

  def err(s: => String): Unit
  def err(s: => String, e: => Throwable): Unit

  def sub(): TrivialLogger = sub(1)
  def sub(delta: Int): TrivialLogger
}

trait AbstractStringTrivialSink {
  def flush(value: => String): Unit
  def flushError(value: => String): Unit
}

object AbstractStringTrivialSink {
  object Console extends AbstractStringTrivialSink {
    override def flush(value: => String): Unit = System.out.println(value)
    override def flushError(value: => String): Unit = System.err.println(value)
  }
}

final class TrivialLoggerImpl(
  config: Config,
  id: String,
  logMessages: Boolean,
  logErrors: Boolean,
  loggerLevel: Int,
) extends TrivialLogger {
  override def log(s: => String): Unit = {
    flush(Level.Info, format(s))
  }

  override def log(s: => String, e: => Throwable): Unit = {
    flush(Level.Info, formatError(s, e))
  }

  override def err(s: => String): Unit = {
    flush(Level.Error, format(s))
  }

  override def err(s: => String, e: => Throwable): Unit = {
    flush(Level.Error, formatError(s, e))
  }

  override def sub(delta: Int): TrivialLogger = {
    new TrivialLoggerImpl(config, id, logMessages, logErrors, loggerLevel + delta)
  }

  @inline private def format(s: => String): String = {
    s"$id: $s"
  }

  @inline private def formatError(s: => String, e: => Throwable): String = {
    s"$id: $s\n${e.stacktraceString}"
  }

  @inline private def flush(level: Level, s: => String): Unit = {
    level match {
      case Level.Info =>
        if (logMessages) {
          config.sink.flush(s.shift(loggerLevel * 2))
        }
      case Level.Error =>
        if (logErrors) {
          config.sink.flushError(s.shift(loggerLevel * 2))
        }
    }
  }
}

object TrivialLogger {
  sealed trait Level
  object Level {
    case object Info extends Level
    case object Error extends Level
  }

  final case class Config(
    sink: AbstractStringTrivialSink = AbstractStringTrivialSink.Console,
    forceLog: Boolean = false,
  )

  def make[T: ClassTag](sysProperty: String, config: Config = Config()): TrivialLogger = {
    val logMessages: Boolean = checkLog(sysProperty, config, default = false)
    val logErrors: Boolean = checkLog(sysProperty, config, default = true)
    new TrivialLoggerImpl(config, classTag[T].runtimeClass.getSimpleName, logMessages, logErrors, loggerLevel = 0)
  }

  private val enabled = new mutable.HashMap[String, Boolean]()

  @nowarn("msg=return statement uses an exception")
  private def checkLog(sysProperty: String, config: Config, default: Boolean): Boolean = enabled.synchronized {
    def check(): Boolean = {
      val parts = sysProperty.split('.')

      def cond(path: String): Boolean = {
        System.getProperty(path).asBoolean().getOrElse(default)
      }

      parts
        .foldLeft((false, "")) {
          case (done @ (true, _), _) => done
          case ((_, prev), p) =>
            val current = if (prev.isEmpty) p else s"$prev.$p"
            (cond(current), current)
        }._1
    }

    config.forceLog || enabled.getOrElseUpdate(sysProperty, check())
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy