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

scribe.Logger.scala Maven / Gradle / Ivy

The newest version!
package scribe

import scribe.mdc.MDC
import scribe.format.Formatter
import scribe.handler.{LogHandle, LogHandler, SynchronousLogHandle}
import scribe.jul.JULHandler
import scribe.message.LoggableMessage
import scribe.modify.{LevelFilter, LogBooster, LogModifier}
import scribe.output.format.OutputFormat
import scribe.util.Time
import scribe.writer.{ConsoleWriter, Writer}
import sourcecode.{FileName, Line, Name, Pkg}

import java.io.PrintStream
import scala.reflect._
import scala.util.Try

case class Logger(parentId: Option[LoggerId] = Some(Logger.RootId),
                  modifiers: List[LogModifier] = Nil,
                  handlers: List[LogHandler] = Nil,
                  overrideClassName: Option[String] = None,
                  data: Map[String, () => Any] = Map.empty,
                  id: LoggerId = LoggerId()) extends LoggerSupport[Unit] {
  private var lastUpdate = Logger.lastChange
  private var includeStatus = Map.empty[Level, Boolean]

  lazy val isEmpty: Boolean = modifiers.isEmpty && handlers.isEmpty

  def reset(): Logger = copy(parentId = Some(Logger.RootId), Nil, Nil, None)

  def orphan(): Logger = copy(parentId = None)

  def withParent(name: String): Logger = copy(parentId = Some(Logger(name).id))

  def withParent(logger: Logger): Logger = copy(parentId = Some(logger.id))

  def withParent(id: LoggerId): Logger = copy(parentId = Some(id))

  def withHandler(handler: LogHandler): Logger = copy(handlers = handlers ::: List(handler))

  def withHandler(formatter: Formatter = Formatter.default,
                  writer: Writer = ConsoleWriter,
                  minimumLevel: Option[Level] = None,
                  modifiers: List[LogModifier] = Nil,
                  outputFormat: OutputFormat = OutputFormat.default,
                  handle: LogHandle = SynchronousLogHandle): Logger = {
    withHandler(LogHandler(formatter, writer, minimumLevel, modifiers, outputFormat, handle))
  }

  def withoutHandler(handler: LogHandler): Logger = copy(handlers = handlers.filterNot(_ == handler))

  def clearHandlers(): Logger = copy(handlers = Nil)

  def withClassNameOverride(className: String): Logger = copy(overrideClassName = Option(className))

  def setModifiers(modifiers: List[LogModifier]): Logger = copy(modifiers = modifiers.sorted)

  def clearModifiers(): Logger = setModifiers(Nil)

  def set(key: String, value: => Any): Logger = copy(data = this.data + (key -> (() => value)))

  def get(key: String): Option[Any] = data.get(key).map(_ ())

  final def withModifier(modifier: LogModifier): Logger = setModifiers(modifiers.filterNot(m => m.id.nonEmpty && m.id == modifier.id) ::: List(modifier))

  final def withoutModifier(modifier: LogModifier): Logger = setModifiers(modifiers.filterNot(m => m.id.nonEmpty && m.id == modifier.id))

  override def log(level: Level, mdc: MDC, features: LogFeature*)
                  (implicit pkg: Pkg, fileName: FileName, name: Name, line: Line): Unit = {
    if (includes(level)) {
      super.log(level, mdc, features: _*)
    }
  }

  def includes(level: Level): Boolean = {
    if (lastUpdate != Logger.lastChange) {
      includeStatus = Map.empty
      lastUpdate = Logger.lastChange
    }
    includeStatus.get(level) match {
      case Some(b) =>
        b
      case None =>
        val b = shouldLog(LogRecord.simple("", "", "", level = level))
        synchronized {
          includeStatus += level -> b
        }
        b
    }
  }

  def modifierById[M <: LogModifier](id: String, recursive: Boolean): Option[M] = {
    modifiers.find(m => m.id.nonEmpty && m.id == id).orElse {
      parentId match {
        case _ if !recursive => None
        case None => None
        case Some(pId) => Logger(pId).modifierById(id, recursive)
      }
    }.map(_.asInstanceOf[M])
  }

  def withMinimumLevel(level: Level): Logger = withModifier(LevelFilter >= level)

  def withBoost(booster: Double => Double, priority: Priority = Priority.Normal): Logger = {
    withModifier(new LogBooster(booster, priority))
  }

  def withBoostOneLevel(): Logger = withBoost(_ + 100.0)

  def withBoosted(minimumLevel: Level, destinationLevel: Level): Logger = {
    withBoost(d => if (d >= minimumLevel.value && d <= destinationLevel.value) {
      destinationLevel.value
    } else {
      d
    })
  }

  override final def log(record: LogRecord): Unit = {
    val r = if (data.nonEmpty) {
      record.copy(data = data ++ record.data)
    } else {
      record
    }
    r.modify(modifiers).foreach { r =>
      handlers.foreach(_.log(r))
      parentId.map(Logger.apply).foreach(_.log(r))
    }
  }

  protected def shouldLog(record: LogRecord): Boolean = record.modify(modifiers) match {
    case Some(_) if handlers.nonEmpty => true
    case Some(r) => parentId.map(Logger.apply).exists(p => p.shouldLog(r))
    case None => false
  }

  def replace(name: Option[String] = None): Logger = name match {
    case Some(n) => Logger.replaceByName(n, this)
    case None => Logger.replace(this)
  }

  def remove(): Unit = Logger.remove(this)

  def logDirect(level: Level,
                messages: List[LoggableMessage] = Nil,
                fileName: String = "",
                className: String = "",
                methodName: Option[String] = None,
                line: Option[Int] = None,
                column: Option[Int] = None,
                thread: Thread = Thread.currentThread(),
                timeStamp: Long = Time()): Unit = {
    log(LogRecord(
      level = level,
      levelValue = level.value,
      messages = messages,
      fileName = fileName,
      className = overrideClassName.getOrElse(className),
      methodName = methodName,
      line = line,
      column = column,
      thread = thread,
      timeStamp = timeStamp
    ))
  }
}

object Logger {
  // Keep a reference to the print streams just in case we need to redirect later
  private val systemOut = System.out
  private val systemErr = System.err

  lazy val DefaultRootMinimumLevel: Level = Option(System.getenv("SCRIBE_MINIMUM_LEVEL")).flatMap(Level.get).getOrElse(Level.Info)

  /**
   * Functionality for system output stream management
   */
  object system {
    /**
     * The standard system out (set upon initialization to represent the original, non-redirected, System.out)
     */
    def out: PrintStream = systemOut

    /**
     * The standard system err (set upon initialization to represent the original, non-redirected, System.err)
     */
    def err: PrintStream = systemErr

    /**
     * Redirects system output to Scribe's logging
     *
     * @param outLevel if set, defines the level to log System.out to (defaults to Some(Level.Info))
     * @param errLevel if set, defines the level to log System.err to (defaults to Some(Level.Error))
     * @param loggerId the loggerId to determine what logger to use when logging (defaults to Logger.RootId)
     */
    def redirect(outLevel: Option[Level] = Some(Level.Info),
                 errLevel: Option[Level] = Some(Level.Error),
                 loggerId: LoggerId = RootId): Unit = {
      if (System.out.toString != "Scribe Printer") {
        outLevel.foreach { level =>
          val os = new LoggingOutputStream(loggerId, level, className = "System", methodName = Some("out"))
          val ps = new PrintStream(os) {
            override def toString: String = "Scribe Printer"
          }
          System.setOut(ps)
        }
      } else {
        scribe.warn("System.out is already redirected")
      }
      if (System.err.toString != "Scribe Printer") {
        errLevel.foreach { level =>
          val os = new LoggingOutputStream(loggerId, level, className = "System", methodName = Some("err"))
          val ps = new PrintStream(os) {
            override def toString: String = "Scribe Printer"
          }
          System.setErr(ps)
        }
      } else {
        scribe.warn("System.err is already redirected")
      }
    }

    /**
     * Resets the System.out and System.err to the original state
     *
     * @param out if true, resets System.out (defaults to true)
     * @param err if true, resets System.err (defaults to true)
     */
    def reset(out: Boolean = true, err: Boolean = true): Unit = {
      if (out) {
        System.setOut(systemOut)
      }
      if (err) {
        System.setErr(systemErr)
      }
    }

    /**
     * Attempts to install Scribe as a java.util.logging base handler.
     *
     * @param removeExistingHandlers removes all existing JUL handlers if true (defaults to true)
     */
    def installJUL(removeExistingHandlers: Boolean = true): Unit = Try {
      val logger = java.util.logging.LogManager.getLogManager.getLogger("")
      if (removeExistingHandlers) {
        logger.getHandlers.foreach(logger.removeHandler)
      }
      logger.addHandler(JULHandler)
    }.failed.foreach { t =>
      scribe.warn(s"Failed to install java.util.logging integration: ${t.getMessage}")
    }
  }

  val RootId: LoggerId = LoggerId(0L)

  private var lastChange: Long = 0L
  private var id2Logger: Map[LoggerId, Logger] = Map.empty
  private var name2Id: Map[String, LoggerId] = Map.empty

  resetRoot()

  // Initialize Platform-specific functionality
  Platform.init()

  def empty: Logger = Logger()

  def root: Logger = apply(RootId)

  def loggersByName: Map[String, Logger] = name2Id.map {
    case (name, id) => name -> id2Logger(id)
  }

  /**
   * Resets the global state of Scribe
   */
  def reset(): Unit = {
    id2Logger = Map.empty
    name2Id = Map.empty
    resetRoot()
  }

  def apply(name: String): Logger = get(name) match {
    case Some(logger) => logger
    case None => synchronized {
      val n = fixName(name)
      val dotIndex = n.lastIndexOf('.')
      val parentId = if (dotIndex > 0) {
        val parentName = n.substring(0, dotIndex)
        val parent = apply(parentName)
        parent.id
      } else {
        RootId
      }
      val logger = Logger(parentId = Some(parentId))
      id2Logger += logger.id -> logger
      name2Id += n -> logger.id
      lastChange = System.currentTimeMillis()
      logger
    }
  }

  def apply(id: LoggerId): Logger = get(id) match {
    case Some(logger) => logger
    case None => synchronized {
      val logger = new Logger(id = id)
      id2Logger += logger.id -> logger
      lastChange = System.currentTimeMillis()
      logger
    }
  }

  def minimumLevels(minimums: MinimumLevel*): Unit = minimums.foreach { m =>
    m.logger.withMinimumLevel(m.minimumLevel).replace()
  }

  def apply[T](implicit t: ClassTag[T]): Logger = apply(t.runtimeClass.getName)

  def get(name: String): Option[Logger] = name2Id.get(fixName(name)).flatMap(id => id2Logger.get(id))

  def get(id: LoggerId): Option[Logger] = id2Logger.get(id)

  def get[T](implicit t: ClassTag[T]): Option[Logger] = get(t.runtimeClass.getName)

  /**
    * Replaces this logger and all references to it in the global state
    */
  def replace(logger: Logger): Logger = synchronized {
    id2Logger += logger.id -> logger
    lastChange = System.currentTimeMillis()
    logger
  }

  def replaceByName(name: String, logger: Logger): Logger = synchronized {
    replace(logger)
    name2Id += fixName(name) -> logger.id
    logger
  }

  /**
    * Removes this logger from the global state and all references to it.
    */
  def remove(logger: Logger): Unit = synchronized {
    id2Logger -= logger.id
    lastChange = System.currentTimeMillis()
    val names = name2Id.collect {
      case (name, id) if logger.id == id => name
    }
    name2Id --= names
  }

  def namesFor(loggerId: LoggerId): Set[String] = name2Id.collect {
    case (name, id) if id == loggerId => name
  }.toSet

  def resetRoot(): Unit = {
    // Configure the root logger to filter anything under SCRIBE_MINIMUM_LEVEL (or INFO if not specified) and write to the console
    root
      .orphan()
      .clearModifiers()
      .withMinimumLevel(DefaultRootMinimumLevel)
      .clearHandlers()
      .withHandler()
      .replace(Some("root"))
  }

  private def fixName(name: String): String = name.replace("$", "")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy