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

net.model3.logging.logback.LogbackConfigurator.scala Maven / Gradle / Ivy

The newest version!
package net.model3.logging.logback

import a8.common.logging.{LoggingBootstrapConfig, LoggingBootstrapConfigServiceLoader}
import ch.qos.logback.classic.joran.JoranConfigurator
import ch.qos.logback.classic.jul.LevelChangePropagator
import ch.qos.logback.classic.{Level, Logger, LoggerContext}
import ch.qos.logback.classic.layout.TTLLLayout
import ch.qos.logback.classic.spi.Configurator.ExecutionStatus
import ch.qos.logback.classic.spi.{Configurator, ILoggingEvent, LoggerContextListener}
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.encoder.LayoutWrappingEncoder
import ch.qos.logback.core.spi.ContextAwareBase
import ch.qos.logback.core.status.{InfoStatus, Status, StatusBase, StatusListener, StatusUtil}
import ch.qos.logback.core.util.StatusPrinter
import org.slf4j.bridge.SLF4JBridgeHandler
import zio.ZLayer

import java.io.{File, FileOutputStream}
import java.nio.file.Paths
import java.util
import scala.jdk.CollectionConverters.*

object LogbackConfigurator {

  val configureLoggingZ: zio.ZIO[LoggingBootstrapConfig & LoggerContext, Throwable, Unit] = {
    for {
      context <- zio.ZIO.service[LoggerContext]
      config <- zio.ZIO.service[LoggingBootstrapConfig]
      _ <-
        zio.ZIO.attempt {
          context.reset()
          val configurator = new LogbackConfigurator
          configurator.setContext(context)
          configurator.configure(context, config)
        }
    } yield ()
  }

  def statusMessages(): (a8.common.logging.Level,String) = {
    val status =
      org.slf4j.LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
        .getStatusManager
        .getCopyOfStatusList
        .asScala
    highestLevel(status) -> statusMessages(status)
  }

  def statusMessages(loggerContext: LoggerContext): String = {
    val sb = new java.lang.StringBuilder()
    loggerContext
      .getStatusManager
      .getCopyOfStatusList()
      .asScala
      .foreach(s => StatusPrinter.buildStr(sb, "", s))
    sb.toString
  }

  def statusMessages(status: Iterable[Status]): String = {
    val sb = new java.lang.StringBuilder()
    status
      .foreach(s => StatusPrinter.buildStr(sb, "", s))
    sb.toString
  }
  def statusMessage(status: Status): (a8.common.logging.Level,String) = {
    val sb = new java.lang.StringBuilder()
    StatusPrinter.buildStr(sb, "", status)
    highestLevel(status) -> sb.toString
  }

  def highestLevel(status: Status): a8.common.logging.Level =
    impl.statusLevel(impl.highestLevel(status))

  def highestLevel(status: Iterable[Status]): a8.common.logging.Level =
    impl.statusLevel(impl.highestLevel(status.iterator))

  object impl {

    def highestLevel(status: Status): Int = {
      val iter = status.iterator()
      if (iter != null) {
        Math.max(status.getLevel, highestLevel(iter.asScala))
      } else {
        status.getLevel
      }
    }
    def statusLevel(statusLevelInt: Int): a8.common.logging.Level = {
      statusLevelInt match {
        case 0 =>
          a8.common.logging.Level.Info
        case 1 =>
          a8.common.logging.Level.Warn
        case 2 =>
          a8.common.logging.Level.Error
        case _ =>
          a8.common.logging.Level.Fatal
      }
    }

    def highestLevel(status: Iterator[Status]): Int = {
      status
        .foldLeft(0) { (level, status) =>
          Math.max(level, highestLevel(status))
        }
    }
  }

}

class LogbackConfigurator extends ContextAwareBase with Configurator { outer =>

  lazy val configDirectory: java.io.File =
    new File(System.getProperty("config.dir"), "./config")

  lazy val logsDirectory: java.io.File =
    new File(System.getProperty("logs.dir"), "./logs")

  lazy val archivesDirectory: java.io.File =
    new File(System.getProperty("archives.dir"), new File(logsDirectory, "archives").getAbsolutePath)

  override def configure(loggerContext: LoggerContext): Configurator.ExecutionStatus = {
    val bootstrapConfig = LoggingBootstrapConfigServiceLoader.loggingBootstrapConfig
    configure(loggerContext, bootstrapConfig)
  }

  def configure(loggerContext: LoggerContext, bootstrapConfig: LoggingBootstrapConfig): Configurator.ExecutionStatus = {

    addInfo(s"""using bootstrapConfig ${bootstrapConfig.asProperties("").mkString("  ")}""")

    val joranConfigurator = new JoranConfigurator
    joranConfigurator.setContext(loggerContext)

    val bootstrapConfigProperties = bootstrapConfig.asProperties("bootstrap.")

    bootstrapConfigProperties
      .foreach(t => System.setProperty(t._1, t._2))

    // propagate logging level changes to java.util.logging
    loggerContext.addListener(new LevelChangePropagator() {
      setContext(loggerContext)
      override def isResetResistant: Boolean = true
    })

    val configFile = new File(configDirectory, "logback.xml")

    if ( configFile.exists() ) {
      addInfo(s"configuring logging using ${configFile.getAbsolutePath}")
      joranConfigurator.doConfigure(configFile)
    } else {
      val input = getClass.getResourceAsStream("/logback-default.xml")
      joranConfigurator.doConfigure(input)
      input.close()
      addInfo(s"logging configured using default file from classpath /logback-default.xml")
    }

    if ( configDirectory.exists() ) {
      configDirectory.mkdirs(): @scala.annotation.nowarn
      val sampleFile = configDirectory.toPath.resolve("logback-sample.xml").toFile
      val input = getClass.getResourceAsStream("/logback-sample.xml")
      val output = new FileOutputStream(sampleFile)
      output.write(input.readAllBytes())
      output.close()
    }

    // dummy status listener to avoid logging logback status messages to console
    context.getStatusManager.add(
      new StatusListener:
        override def addStatusEvent(status: Status): Unit = ()
    )

//    to test error logging
//    addError("boom")

    SLF4JBridgeHandler.removeHandlersForRootLogger // (since SLF4J 1.6.5)

    // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during
    // the initialization phase of your application
    SLF4JBridgeHandler.install

    ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;

  }

  def postConfig(loggerContext: LoggerContext) = {

    val (level, statusStr) = LogbackConfigurator.statusMessages()
    val indentedStatusStr = statusStr.linesIterator.map("        " + _).mkString("\n")
    a8.common.logging.Logger.logger(getClass).log(level, s"logging config results\n${indentedStatusStr}")

    LogbackLoggerFactory.loggingConfiguredPromise.success(()): @scala.annotation.nowarn

    loggerContext.getStatusManager.add(
      new StatusListener {
        lazy val logger = a8.common.logging.Logger.logger("net.model3.logging.logback.LogbackLoggerFactory")

        override def addStatusEvent(status: Status): Unit = {
          val t = LogbackConfigurator.statusMessage(status)
          logger.log(t._1, t._2.trim)
        }

        override def isResetResistant: Boolean = true
      }
    )

  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy