la journalisation en scala

En Java, l'idiome standard pour la journalisation consiste à créer une variable statique pour un objet logger et à l'utiliser dans les différentes méthodes.

Dans Scala, il semble que l'idiome consiste à créer un trait de journalisation avec un membre logger et à mélanger le trait dans des classes concrètes. Cela signifie que chaque fois qu'un objet est créé, il appelle la journalisation pour obtenir un enregistreur et également l'objet est plus en raison de la référence supplémentaire.

Est-il une alternative qui permet la facilité d'utilisation de "avec la journalisation" tout en utilisant une instance de logger par classe?

EDIT: ma question ne concerne pas comment écrire un framework de journalisation dans Scala, mais plutôt comment utiliser un framework existant (log4j) sans encourir de surcharge de performances (obtenir une référence pour chaque instance) ou de complexité de code. Aussi, oui, je veux utiliser log4j, simplement parce que je vais utiliser des bibliothèques tierces écrites en Java qui sont susceptibles d'utiliser log4j.

29
demandé sur IttayD 2010-01-07 09:03:22

8 réponses

Je m'en tiendrais juste à l'approche "avec la journalisation". Clean design gagne à chaque fois-si vous obtenez le passe-partout, alors les chances sont que vous pouvez trouver des gains beaucoup plus utiles réalisables dans d'autres domaines.

Gardez à l'esprit que le framework de journalisation mettra en cache les enregistreurs, donc vous en avez toujours un par classe, même si chaque instance de cette classe contient une référence (peu coûteuse).

Sans preuve que les références d'enregistreur nuisent à votre tas, cela sent beaucoup prématuré optimisation... Détendez-vous et ne vous inquiétez pas à ce sujet, sauf si un profileur vous dit le contraire.

Sur une note sans rapport, vous pouvez également envisager d'utiliser slf4j et logback au lieu de log4j. slf4j a un design plus propre qui s'adapte mieux à la scala idiomatique.

19
répondu Kevin Wright 2010-01-07 19:57:49

J'ai utilisé log4j avec Scala en créant un trait et en ayant le logger par Per-instances pas par-classe. Avec de la magie Scala et des manifestes, vous pourriez être en mesure de changer l'enregistreur pour qu'il soit statique (objet interne), mais je ne suis pas sûr à 100%. Personnellement, je suis d'accord avec @KevinWright que rendre l'enregistreur statique est une optimisation prématurée.

Notez également que le code ci-dessous a les messages de journal comme par nom, ce qui signifie que vos appels de logger n'ont pas besoin d'être enveloppés dans `if (log.isDebugEnabled()); les messages de journal complexes créés via la concaténation de chaînes ne seront pas évalués à moins que le niveau de journal ne soit approprié. Voir ce lien pour plus d'informations: 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!")
    } 
  }
}
12
répondu davetron5000 2010-01-18 15:28:50

Si vous êtes vraiment préoccupé par la surcharge d'espace et / ou le temps supplémentaire dans les initialiseurs d'objets, une bonne stratégie peut être d'avoir un trait de journalisation qui laisse l'enregistreur Abstrait comme dans


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

Pour les classes qui doivent être aussi légères que possible, vous pouvez faire


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

Le JIT pourrait même être en ligne debug warn et logger dans ce cas.

, Vous pouvez également avoir un trait de mélanger dans les classes où la surcharge d'un champ supplémentaire n'est pas un problème


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

Un autre option est de laisser la déconnexion de la classe et le mettre dans un objet comme dans


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

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

Je suis d'accord avec Kevin cependant, n'ajoutez pas la complexité sauf si vous en avez besoin.

5
répondu Geoff Reedy 2010-01-18 17:20:47
object Log {
    def log(message: String) = {
        .....
    }
}

Non?

3
répondu F0RR 2010-01-07 09:43:58

Parfois, la journalisation au niveau du paquet est la bonne réponse. Scala rend cela plus facile que java car les objets peuvent être définis directement dans un package. Si vous avez défini un journal comme ceci:

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

Ce journal est disponible partout dans l'exemple de paquet. Pour obtenir une journalisation plus fine, vous pouvez disperser des définitions similaires dans la hiérarchie des paquets. Ou vous pouvez définir tous les enregistreurs de paquets ensemble comme ceci:

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

  object Log extends PackageLogger 

  package frobber {
    object Log extends PackageLogger 

    package undulater {
      object Log extends PackageLogger
    } 
  }
}

La classe PackageLogger peut être définie comme suit (en supposant 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)
}
2
répondu Arnold deVos 2010-04-17 04:30:28

Voici un hack rapide (que je n'ai pas réellement utilisé, honnête;@)

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

Espère que c'est de quelque utilité.

1
répondu Don Mackenzie 2010-01-07 11:02:00

L'API de journalisation typesafe ( https://github.com/typesafehub/scalalogging ) a un trait pour ajouter un logger val à une classe mais son utilisation est facultative. Il initialise la variable en utilisant getClass getName qui la moitié du temps sera sans valeur car souvent votre nom de classe réel sera gobbledygook.

Donc, si vous ne voulez pas que le trait ajoute la variable supplémentaire à chaque instance, vous n'avez certainement pas besoin de l'utiliser et vous pouvez simplement mettre le logger val dans votre objet compagnon et faire une importation dans la classe des membres d'objets compagnons afin que vous n'ayez pas besoin de la qualifier.

1
répondu 2013-05-14 02:32:14

Une façon est d'étendre l'Enregistreur à l'objet compagnon:

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 = ...
}

Vous pouvez également mettre en cache les instances de l'enregistreur:

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

C'est plus facile à utiliser mais aura des performances pires. Les performances seront vraiment mauvaises lorsque vous supprimerez la plupart des messages du journal (en raison de la configuration au niveau du journal).

0
répondu Thomas Jung 2010-01-07 15:22:12