簡體   English   中英

登錄scala

[英]logging in scala

在Java中,日志記錄的標准習慣用法是為記錄器對象創建一個靜態變量,並在各種方法中使用它。

在Scala中,看起來成語是用記錄器成員創建Logging特征並在具體類中混合特征。 這意味着每次創建對象時,它都會調用日志框架來獲取記錄器,並且由於附加引用,對象也會變大。

是否有一種替代方案可以在仍然使用每類記錄器實例時輕松使用“with Logging”?

編輯:我的問題不是關於如何在Scala中編寫日志框架,而是如何使用現有的(log4j)而不會產生性能開銷(獲取每個實例的引用)或代碼復雜性。 另外,是的,我想使用log4j,因為我將使用可能使用log4j的Java編寫的第三方庫。

我只是堅持“使用Logging”的方法。 干凈的設計每次都會獲勝 - 如果你得到了樣板,那么很有可能你可以在其他領域找到更有用的增益。

請記住,日志記錄框架將緩存記錄器,因此每個類仍然有一個,即使該類的每個實例碰巧都有(廉價)引用。

沒有證據證明記錄器引用會損害你的堆,這就像過早的優化一樣......只是放松並且不用擔心,除非探查器告訴你。

在一個不相關的說明中,您可能還想研究使用slf4j和logback而不是log4j。 slf4j有一個更干凈的設計,更適合慣用的scala。

我通過創建一個特征並使用每個實例而不是每個類的記錄器來使用帶有Scala的log4j。 使用一些Scala魔法和清單,您可以將記錄器更改為靜態(內部對象),但我不是100%肯定。 就個人而言,我同意@KevinWright認為使記錄器靜態是一個不成熟的優化。

另請注意,下面的代碼按名稱顯示日志消息,這意味着您的記錄器調用不需要包含在`if(log.isDebugEnabled()); 除非日志級別合適,否則不會評估通過字符串連接創建的復雜日志消息。 有關詳細信息,請參閱此鏈接: http//www.naildrivin5.com/scalatour/wiki_pages/TypeDependentClosures

http://github.com/davetron5000/shorty/blob/master/src/main/scala/shorty/Logs.scala

package shorty

import org.apache.log4j._

trait Logs {
  private[this] val logger = Logger.getLogger(getClass().getName());

  import org.apache.log4j.Level._

  def debug(message: => String) = if (logger.isEnabledFor(DEBUG)) logger.debug(message)
  def debug(message: => String, ex:Throwable) = if (logger.isEnabledFor(DEBUG)) logger.debug(message,ex)
  def debugValue[T](valueName: String, value: => T):T = {
    val result:T = value
    debug(valueName + " == " + result.toString)
    result
  }

  def info(message: => String) = if (logger.isEnabledFor(INFO)) logger.info(message)
  def info(message: => String, ex:Throwable) = if (logger.isEnabledFor(INFO)) logger.info(message,ex)

  def warn(message: => String) = if (logger.isEnabledFor(WARN)) logger.warn(message)
  def warn(message: => String, ex:Throwable) = if (logger.isEnabledFor(WARN)) logger.warn(message,ex)

  def error(ex:Throwable) = if (logger.isEnabledFor(ERROR)) logger.error(ex.toString,ex)
  def error(message: => String) = if (logger.isEnabledFor(ERROR)) logger.error(message)
  def error(message: => String, ex:Throwable) = if (logger.isEnabledFor(ERROR)) logger.error(message,ex)

  def fatal(ex:Throwable) = if (logger.isEnabledFor(FATAL)) logger.fatal(ex.toString,ex)
  def fatal(message: => String) = if (logger.isEnabledFor(FATAL)) logger.fatal(message)
  def fatal(message: => String, ex:Throwable) = if (logger.isEnabledFor(FATAL)) logger.fatal(message,ex)
}

class Foo extends SomeBaseClass with Logs {
  def doit(s:Option[String]) = {
    debug("Got param " + s)
    s match {
      case Some(string) => info(string)
      case None => error("Expected something!")
    } 
  }
}

如果您真的擔心對象初始化器中的空間開銷和/或額外時間,那么一個好的策略就是有一個日志記錄特征,使得記錄器抽象如同


trait Logging {
  def logger: Logger
  def debug(message: String) { logger.debug(message) }
  def warn(message: String) { logger.warn(message) }
}

對於需要盡可能輕量級的類,您可以這樣做


object MustBeLightweight {
  val logger = Logger.getLog(classOf[MustBeLightweight])
}
class MustBeLightWeight extends Logging {
  final def logger = MustBeLightweight.logger
}

在這種情況下,JIT甚至可以內聯debug warnlogger

您還可以將特性混合到類中,其中附加字段的開銷不是問題


trait PerInstanceLog {
  val logger = Logger.getLog(this.getClass())
}

另一個選擇是將日志記錄保留在類之外,並將其完全放在對象中,如同


object Foo {
  object log extends Logging {
    override val logger = Logger.getLogger(classOf[Foo])
  } 
}

class Foo {
  import Foo.log._
  def someMethod() = { warn("xyz") }
}

我同意凱文,除非你需要,否則不要增加復雜性。

object Log {
    def log(message: String) = {
        .....
    }
}

沒有?

有時在包級別登錄是正確的答案。 Scala比java更容易,因為對象可以直接在包中定義。 如果您定義了這樣的日志:

package example 
object Log extends au.com.langdale.util.PackageLogger 

此日志在包示例中隨處可用。 要獲得更細粒度的日志記錄,可以在包層次結構周圍分散類似的定義。 或者您可以像這樣一起定義所有包記錄器:

package example {
  import au.com.langdale.util.PackageLogger

  object Log extends PackageLogger 

  package frobber {
    object Log extends PackageLogger 

    package undulater {
      object Log extends PackageLogger
    } 
  }
}

PackageLogger類可以定義如下(假設SLF4J):

package au.com.langdale.util
import org.slf4j.LoggerFactory

class PackageLogger {
  val name = { val c = getClass.getName; c.substring(0, c.lastIndexOf('.')) }
  val inner = LoggerFactory.getLogger(name)

  // various convenient logging methods follow....
  def apply( mesg: => Any ) = inner.info(mesg.toString)
  def info( mesg: String ) = inner.info(mesg)
  def warn( mesg: String ) = inner.warn(mesg)
  def error( mesg: String ) = inner.error(mesg)
}

類型安全日志記錄API( https://github.com/typesafehub/scalalogging )具有向類添加記錄器val的特性,但其使用是可選的。 它使用getClass getName初始化變量,其中一半的時間將毫無價值,因為通常你的實際類名將是gobbledygook。

因此,如果您不希望特征向每個實例添加額外變量,您當然不需要使用它,只需將logger val放入伴隨對象並在伴隨對象成員的類中進行導入即可不需要限定它。

這是一個快速的黑客(我實際上沒有使用,誠實; @)

object LogLevel extends Enumeration {
  val Error   = Value(" ERROR   ")
  val Warning = Value(" WARNING ")                                                                                                      
  val Info    = Value(" INFO    ")
  val Debug   = Value(" DEBUG   ")
}

trait Identity {
  val id: String
}

trait Logging extends Identity {
  import LogLevel._

  abstract class LogWriter {
    protected val writer: Actor
    protected val tstampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ")

    def tstamp = tstampFormat.format(new Date)

    def log(id: String, logLevel: LogLevel.Value, msg: String) {
      writer ! (tstamp + id + logLevel + msg)
    }
  }

  object NullLogWriter extends LogWriter {
    protected val writer = actor{loop {react{case msg: String =>}}}
  }

  object ConsoleWriter extends LogWriter {
    protected val writer = actor{loop {react {case msg: String => Console.out.println(msg); Console.flush case _ =>}}}
  }

  class FileWriter(val file: File) extends LogWriter {
    require(file != null)
    require(file.canWrite)

    protected val writer = actor{loop {react {case msg: String => destFile.println(msg); destFile.flush case _ =>}}}

    private val destFile = {
      try {new PrintStream(new FileOutputStream(file))}
      catch {case e => ConsoleWriter.log("FileWriter", LogLevel.Error, "Unable to create FileWriter for file " + file +
                                         " exception was: " + e); Console.out}
    }
  }

  protected var logWriter: LogWriter = ConsoleWriter
  protected var logLevel             = Info

  def setLoggingLevel(level: LogLevel.Value) {logLevel = level}

  def setLogWriter(lw: LogWriter) {if (lw != null) logWriter = lw}

  def logError(msg: => String) {if (logLevel <= Error) logWriter.log(id, Error, msg)}

  def logWarning(msg: => String) {if (logLevel <= Warning) logWriter.log(id, Warning, msg)}

  def logInfo(msg: => String) {if (logLevel <= Info) logWriter.log(id, Info, msg)}

  def logDebug(msg: => String) {if (logLevel <= Debug) logWriter.log(id, Debug, msg)}
}

希望它有一些用處。

一種方法是將Logger擴展到伴侶對象:

object A extends LoggerSupport

class A {
    import A._
    log("hi")
}

trait LoggerSupport{
    val logger = LoggerFactory.getLogger(this.getClass)
    def log(msg : String)= logger.log(msg)
}

//classes of the logging framework
trait Logger{
    def log(msg : String) : Unit
}

object LoggerFactory{
    def getLogger(classOfLogger : Class[_]) : Logger = ...
}

或者,您可以緩存記錄器實例:

import collection.mutable.Map
object LoggerCache{
    var cache : Map[Class[_], Logger] = Map()
    def logger(c : Class[_]) = cache.getOrElseUpdate(c, LoggerFactory.getLogger(c))
}

trait LoggerSupport{
    def log(msg : String) = LoggerCache.logger(this.getClass).log(msg)
}

class A extends LoggerSupport{
    log("hi")
}

這更容易使用,但性能會更差。 當您要丟棄大部分日志消息時(由於日志級別配置),性能將非常糟糕。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM