вход в Скала - PullRequest
       43

вход в Скала

30 голосов
/ 07 января 2010

В Java стандартная идиома для ведения журналов заключается в создании статической переменной для объекта регистратора и использовании ее в различных методах.

В Scala похоже, что идиома состоит в том, чтобы создать черту Logging с участником logger и смешать черту в конкретных классах. Это означает, что каждый раз, когда объект создается, он вызывает каркас ведения журнала, чтобы получить регистратор, а также объект становится больше из-за дополнительной ссылки.

Есть ли альтернатива, которая позволяет с легкостью использовать «с ведением журнала» при использовании экземпляра средства ведения журнала для каждого класса?

РЕДАКТИРОВАТЬ: Мой вопрос не о том, как можно написать каркас для ведения журналов в Scala, а о том, как использовать существующий (log4j) без потери производительности (получения ссылки для каждого экземпляра) или сложности кода. Кроме того, да, я хочу использовать log4j просто потому, что я буду использовать сторонние библиотеки, написанные на Java, которые могут использовать log4j.

Ответы [ 8 ]

20 голосов
/ 07 января 2010

Я бы просто придерживался подхода «с ведением журнала». Чистый дизайн всегда побеждает - если вы получаете образец, то есть вероятность, что вы сможете найти гораздо более полезные преимущества, достижимые в других областях.

Имейте в виду, что каркас журналирования будет кэшировать логгеры, поэтому у вас по-прежнему есть один на класс, даже если каждый экземпляр этого класса содержит (недорогую) ссылку.

Без доказательства того, что ссылки логгера наносят вред вашей куче, это очень похоже на преждевременную оптимизацию ... Просто расслабьтесь и не беспокойтесь об этом, если профилировщик не скажет вам иначе.

В несвязанной заметке вы также можете захотеть использовать slf4j и logback вместо log4j. slf4j имеет более чистый дизайн, который лучше всего сочетается с идиоматическим scala.

12 голосов
/ 18 января 2010

Я использовал log4j со Scala, создав черту и ведя регистратор для каждого экземпляра, а не для класса. С некоторой магией и манифестами 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!")
    } 
  }
}
5 голосов
/ 18 января 2010

Если вы действительно беспокоитесь о нехватке места и / или дополнительном времени в инициализаторах объектов, хорошей стратегией может быть наличие признака ведения журнала, который оставляет абстрактное значение для регистратора, как в


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 warn и logger.

Вы также можете иметь черту, которую можно смешивать для классов, в которых дополнительные поля не являются проблемой


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

Я согласен с Кевином, не добавляйте сложность, если она вам не нужна.

3 голосов
/ 07 января 2010
object Log {
    def log(message: String) = {
        .....
    }
}

Нет

2 голосов
/ 17 апреля 2010

Иногда логирование на уровне пакета является правильным ответом. 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)
}
1 голос
/ 14 мая 2013

API безопасного ведения журнала (https://github.com/typesafehub/scalalogging) имеет свойство добавлять логгер val к ​​классу, но его использование не является обязательным. Он инициализирует переменную с помощью getClass getName, который половину времени будет бесполезен, поскольку часто ваш реальный класс имя будет gobbledygook.

Так что, если вы не хотите, чтобы черта добавляла дополнительную переменную к каждому экземпляру, вам определенно не нужно ее использовать, и вы можете просто поместить значение logger val в ваш объект-компаньон и выполнить импорт в класс объектов-компаньонов участники, так что вам не нужно квалифицировать его.

1 голос
/ 07 января 2010

Вот быстрый взлом (который я на самом деле не использовал, честно; @)

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

Надеюсь, это пригодится.

0 голосов
/ 07 января 2010

Одним из способов является расширение 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")
}

Это проще в использовании, но производительность будет хуже. Производительность будет очень плохой, если вы откажетесь от большинства сообщений журнала (из-за конфигурации уровня журнала).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...