简体   繁体   English

登录scala

[英]logging in scala

In Java, the standard idiom for logging is to create a static variable for a logger object and use that in the various methods. 在Java中,日志记录的标准习惯用法是为记录器对象创建一个静态变量,并在各种方法中使用它。

In Scala, it looks like the idiom is to create a Logging trait with a logger member and mixin the trait in concrete classes. 在Scala中,看起来成语是用记录器成员创建Logging特征并在具体类中混合特征。 This means that each time an object is created it calls the logging framework to get a logger and also the object is bigger due to the additional reference. 这意味着每次创建对象时,它都会调用日志框架来获取记录器,并且由于附加引用,对象也会变大。

Is there an alternative that allows the ease of use of "with Logging" while still using a per-class logger instance? 是否有一种替代方案可以在仍然使用每类记录器实例时轻松使用“with Logging”?

EDIT: My question is not about how one can write a logging framework in Scala, but rather how to use an existing one (log4j) without incurring an overhead of performance (getting a reference for each instance) or code complexity. 编辑:我的问题不是关于如何在Scala中编写日志框架,而是如何使用现有的(log4j)而不会产生性能开销(获取每个实例的引用)或代码复杂性。 Also, yes, I want to use log4j, simply because I'll use 3rd party libraries written in Java that are likely to use log4j. 另外,是的,我想使用log4j,因为我将使用可能使用log4j的Java编写的第三方库。

I'd just stick to the "with Logging" approach. 我只是坚持“使用Logging”的方法。 Clean design wins every time - if you get the boilerplate out the way then chances are that you can find far more useful gains achievable in other areas. 干净的设计每次都会获胜 - 如果你得到了样板,那么很有可能你可以在其他领域找到更有用的增益。

Keep in mind that the logging framework will cache loggers, so you still have one per class, even if every instance of that class happens to hold a (inexpensive) reference. 请记住,日志记录框架将缓存记录器,因此每个类仍然有一个,即使该类的每个实例碰巧都有(廉价)引用。

Without proof that logger references are harming your heap, this smells a lot like premature optimization... Just relax and don't worry about it, unless a profiler tells you otherwise. 没有证据证明记录器引用会损害你的堆,这就像过早的优化一样......只是放松并且不用担心,除非探查器告诉你。

On an unrelated note, you might also want to look into using slf4j and logback instead of log4j. 在一个不相关的说明中,您可能还想研究使用slf4j和logback而不是log4j。 slf4j has a cleaner design that fits better with idiomatic scala. slf4j有一个更干净的设计,更适合惯用的scala。

I used log4j with Scala by creating a trait and having the logger by per-instances not per-class. 我通过创建一个特征并使用每个实例而不是每个类的记录器来使用带有Scala的log4j。 With some Scala magic and manifests, you might be able to change the logger to be static (internal object), but I'm not 100% sure. 使用一些Scala魔法和清单,您可以将记录器更改为静态(内部对象),但我不是100%肯定。 Personally, I agree with @KevinWright that making the logger static is a premature optimization. 就个人而言,我同意@KevinWright认为使记录器静态是一个不成熟的优化。

Also note that the code below has the log messages as by-name, meaning that your logger calls don't need to be wrapped in `if (log.isDebugEnabled()); 另请注意,下面的代码按名称显示日志消息,这意味着您的记录器调用不需要包含在`if(log.isDebugEnabled()); complex log messages created via string concatenation won't be evaluated unless the log level is appropriate. 除非日志级别合适,否则不会评估通过字符串连接创建的复杂日志消息。 See this link for more info: http://www.naildrivin5.com/scalatour/wiki_pages/TypeDependentClosures 有关详细信息,请参阅此链接: http//www.naildrivin5.com/scalatour/wiki_pages/TypeDependentClosures

http://github.com/davetron5000/shorty/blob/master/src/main/scala/shorty/Logs.scala 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!")
    } 
  }
}

If you are truly concerned about space overhead and/or extra time in object initializers, a good strategy can be to have a logging trait that leaves the logger abstract as in 如果您真的担心对象初始化器中的空间开销和/或额外时间,那么一个好的策略就是有一个日志记录特征,使得记录器抽象如同


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

For classes which need to be as lightweight as possible then you can do 对于需要尽可能轻量级的类,您可以这样做


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

The JIT might even inline debug warn and logger in this case. 在这种情况下,JIT甚至可以内联debug warnlogger

You can also have a trait to mix in for classes where the overhead of an additional field is not a problem 您还可以将特性混合到类中,其中附加字段的开销不是问题


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

A further option is to leave logging out of the class and put it completely in an object as in 另一个选择是将日志记录保留在类之外,并将其完全放在对象中,如同


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

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

I agree with Kevin though, don't add the complexity unless you need it. 我同意凯文,除非你需要,否则不要增加复杂性。

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

No? 没有?

Sometimes logging at the package level is the right answer. 有时在包级别登录是正确的答案。 Scala makes this easier than java because objects can be defined directly in a package. Scala比java更容易,因为对象可以直接在包中定义。 If you defined a Log like this: 如果您定义了这样的日志:

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

This Log is available everywhere in package example. 此日志在包示例中随处可用。 To get more fine-grained logging, you can scatter similar definitions around the package hierarchy. 要获得更细粒度的日志记录,可以在包层次结构周围分散类似的定义。 Or you can defined all the package loggers together like this: 或者您可以像这样一起定义所有包记录器:

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

  object Log extends PackageLogger 

  package frobber {
    object Log extends PackageLogger 

    package undulater {
      object Log extends PackageLogger
    } 
  }
}

The PackageLogger class could be defined as follows (assuming SLF4J): 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)
}

The typesafe logging API ( https://github.com/typesafehub/scalalogging ) has a trait for adding a logger val to a class but its use is optional. 类型安全日志记录API( https://github.com/typesafehub/scalalogging )具有向类添加记录器val的特性,但其使用是可选的。 It initializes the variable using getClass getName which half the time will be worthless because frequently your actual class name will be gobbledygook. 它使用getClass getName初始化变量,其中一半的时间将毫无价值,因为通常你的实际类名将是gobbledygook。

So if you don't want the trait adding the extra variable to each instance you certainly don't need to use it and can simply put the logger val in your companion object and do an import in the class of the companion objects members so you don't need to qualify it. 因此,如果您不希望特征向每个实例添加额外变量,您当然不需要使用它,只需将logger val放入伴随对象并在伴随对象成员的类中进行导入即可不需要限定它。

Here's a quick hack (which I haven't actually been using, honest ;@) 这是一个快速的黑客(我实际上没有使用,诚实; @)

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)}
}

Hope it's of some use. 希望它有一些用处。

One way is to extends the Logger to the companion object: 一种方法是将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 = ...
}

Alternatively you can cache the logger instances: 或者,您可以缓存记录器实例:

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")
}

This is easier to use but will have worse performance. 这更容易使用,但性能会更差。 Performance will be really bad when you're go to discard most of the log messages (because of the log level configuration). 当您要丢弃大部分日志消息时(由于日志级别配置),性能将非常糟糕。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM