繁体   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