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

com.twitter.server.handler.logback.classic.LoggingHandler.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.server.handler.logback.classic

import ch.qos.logback.classic.{Level, Logger, LoggerContext}
import com.twitter.finagle.http._
import com.twitter.io.Buf
import com.twitter.server.Admin.Grouping
import com.twitter.server.util.HtmlUtils.escapeHtml
import com.twitter.server.util.HttpUtils._
import com.twitter.util.Future
import com.twitter.util.logging.Logging
import java.net.URLEncoder
import java.util.{logging => javalog}
import org.slf4j.LoggerFactory
import scala.annotation.tailrec
import scala.collection.JavaConverters._

private class LoggingHandler extends com.twitter.server.handler.LoggingHandler with Logging {

  /** Implementation name */
  override val name: String = "logback-classic"

  implicit val loggerOrder: Ordering[Logger] = Ordering.by(_.getName)
  implicit val levelOrder: Ordering[Level] = Ordering.by(_.levelInt)

  private[this] val levels =
    Seq(Level.OFF, Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE, Level.ALL)
      .sorted(levelOrder)

  private[this] implicit val julLevelOrder: Ordering[javalog.Level] = Ordering.by(_.intValue)
  private[this] val julLogManager = javalog.LogManager.getLogManager
  private[this] val julLoggers = julLogManager.getLoggerNames.asScala.toSeq
    .map(julLogManager.getLogger)

  private[this] val julLevels =
    Seq(
      javalog.Level.OFF,
      javalog.Level.SEVERE,
      javalog.Level.WARNING,
      javalog.Level.INFO,
      javalog.Level.CONFIG,
      javalog.Level.FINE,
      javalog.Level.FINER,
      javalog.Level.FINEST,
      javalog.Level.ALL
    ).sorted(julLevelOrder)

  /** Exposed for testing */
  private[classic] def loggers =
    LoggerFactory.getILoggerFactory
      .asInstanceOf[LoggerContext]
      .getLoggerList
      .asScala
      .sorted(loggerOrder)

  val pattern = "/admin/logging"
  override def route: Route =
    Route(
      pattern = this.pattern,
      handler = this,
      index = Some(
        RouteIndex(alias = "Logging", group = Grouping.Utilities, path = Some(this.pattern))
      )
    )

  override def apply(request: Request): Future[Response] = {
    request.method match {
      case Method.Get | Method.Post =>
        respond(request)
      case _ =>
        newResponse(
          status = Status.MethodNotAllowed,
          contentType = "text/plain;charset=UTF-8",
          content = Buf.Utf8("Method Not Allowed")
        )
    }
  }

  /* Private */

  private def respond(request: Request): Future[Response] = {
    try {
      val toSetLoggerOption = Option(request.getParam("logger"))
      val message = toSetLoggerOption match {
        case Some(loggerString) =>
          try {
            val toSetLevel = request.getParam("level")
            val isJul = request.getBooleanParam("isJul", false)
            if (toSetLevel == null || toSetLevel.isEmpty) {
              throw new IllegalArgumentException(
                s"Unable to set log level for $loggerString -- undefined logging level!"
              )
            } else if (toSetLevel == "null") {
              val logger = LoggerFactory.getLogger(loggerString).asInstanceOf[Logger]
              logger.setLevel(null)
              val message = s"Removed level override for ${logger.getName}"
              info(message)
              escapeHtml(message)
            } else {
              val message = if (!isJul) {
                val logger = LoggerFactory.getLogger(loggerString).asInstanceOf[Logger]
                logger.setLevel(Level.valueOf(toSetLevel))
                s"""Changed ${logger.getName} to Level.$toSetLevel"""
              } else {
                val julLogger = julLogManager.getLogger(loggerString)
                julLogger.setLevel(javalog.Level.parse(toSetLevel))
                s"""Changed ${julLogger.getName} to Level.$toSetLevel"""
              }

              info(message)
              escapeHtml(message)
            }
          } catch {
            case e: Exception =>
              warn(e.getMessage)
              escapeHtml(e.getMessage)
          }
        case _ => ""
      }

      val showOverriddenOnly = request.getBooleanParam("overridden", false)
      val filteredLoggers = if (showOverriddenOnly) {
        loggers
          .filter(_.getLevel != null)
          .filter(_.getName != org.slf4j.Logger.ROOT_LOGGER_NAME)
      } else loggers
      val html = renderHtml(filteredLoggers.toSeq, julLoggers, message, showOverriddenOnly)

      newResponse(
        contentType = "text/html;charset=UTF-8",
        content = Buf.Utf8(html)
      )
    } catch {
      case e: Throwable =>
        newResponse(contentType = "text/html;charset=UTF-8", content = Buf.Utf8(e.getMessage))
    }
  }

  private def renderHtml(
    renderLoggers: Seq[Logger],
    julLoggers: Seq[javalog.Logger],
    updateMsg: String,
    showOverriddenOnly: Boolean
  ): String = {
    s"""

Logback Loggers

${renderFilterButtons(showOverriddenOnly)} ${(for (logger <- renderLoggers) yield { val loggerName = if (logger.getName == "") org.slf4j.Logger.ROOT_LOGGER_NAME else logger.getName val inheritsLevel = logger.getLevel == null val filterQueryParams = if (showOverriddenOnly) { "?overridden=true&" } else "?" val buttons = for (level <- levels) yield { val isActive = logger.getEffectiveLevel == level val activeCss = if (!isActive) "btn-default" else if (!inheritsLevel) "btn-primary active disabled" else "btn-primary active" val queryParams = if (isActive && logger.getLevel == logger.getEffectiveLevel) "" else { s"""logger=${URLEncoder.encode(loggerName, "UTF-8")}&level=${level.toString}""" } s"""${level.toString}""" } val resetButton = if (!inheritsLevel && loggerName != org.slf4j.Logger.ROOT_LOGGER_NAME) { val queryParams = s"""logger=${URLEncoder.encode(loggerName, "UTF-8")}&level=null""" s"""RESET""" } else "" s"""""" }).mkString("\n")}
${escapeHtml(updateMsg)}
ch.qos.logback.classic.Logger ch.qos.logback.classic.Level
${escapeHtml(loggerName)}
${buttons.mkString("\n")} $resetButton

java.util.Logging Loggers

${ val filterQueryParams = if (showOverriddenOnly) { "?overridden=true&" } else "?" (for (logger <- julLoggers) yield { val loggerName = getLoggerDisplayName(logger) val buttons = (for (level <- julLevels) yield { val isActive = getLevel(logger) == level val activeCss = if (!isActive) "btn-default" else { "btn-primary active disabled" } val queryParams = if (isActive) "" else { s"""logger=${URLEncoder .encode(loggerName, "UTF-8")}&level=${level.toString}&isJul=true""" } s"""${level.toString}""" }).mkString("\n") s"""""" }).mkString("\n") }
java.util.logging.Logger java.util.logging.Level
${escapeHtml(loggerName)}
$buttons
""" } private def getLoggerDisplayName(logger: javalog.Logger): String = logger.getName match { case "" => "root" case name => name } def getLevel(logger: javalog.Logger): javalog.Level = { @tailrec def go(l: javalog.Logger): javalog.Level = { if (l.getLevel != null) l.getLevel else if (l.getParent != null) go(l.getParent) else javalog.Level.OFF // root has no level set } go(javalog.Logger.getLogger(logger.getName)) } private def renderFilterButtons(showOverriddenOnly: Boolean): String = { def buttonClass(inject: String): String = s"""class="btn btn-primary$inject"""" val disabledBtn = buttonClass(" active disabled") val overrideBtn = buttonClass("""" href="?overridden=true""") val (showAllBtn, overriddenBtn) = if (showOverriddenOnly) (buttonClass("""" href="?"""), disabledBtn) else (disabledBtn, overrideBtn) s"""Show all loggers Show overridden loggers only""" } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy