web-dev-qa-db-fra.com

se connecter en scala

En Java, la norme pour la journalisation consiste à créer une variable statique pour un objet de journalisation 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 à le mélanger dans des classes concrètes. Cela signifie que chaque fois qu'un objet est créé, il appelle la structure de journalisation pour obtenir un enregistreur. Cet objet est également plus volumineux en raison de la référence supplémentaire.

Existe-t-il une alternative qui facilite l'utilisation de "avec journalisation" tout en utilisant une instance de consignateur par classe?

EDIT: Ma question ne concerne pas la façon dont on peut écrire un framework de journalisation dans Scala, mais plutôt comment utiliser un existant (log4j) sans générer de surcoût en performances (obtenir une référence pour chaque instance) ni de complexité du code. Aussi, oui, je veux utiliser log4j, simplement parce que j'utiliserai des bibliothèques tierces écrites en Java et susceptibles d'utiliser log4j.

29
IttayD

Je me contenterais de l'approche "avec journalisation". Un design épuré gagne à chaque fois - si vous obtenez le passe-partout, il y a de fortes chances que vous trouviez des gains bien plus utiles dans d'autres domaines.

Gardez à l'esprit que la structure de journalisation mettra les enregistreurs en cache, de sorte que vous en aurez 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 des enregistreurs nuisent à votre tas, cela sent beaucoup l'optimisation prématurée ... Détendez-vous et ne vous inquiétez pas, à moins que le profileur vous indique le contraire.

Sur une note indépendante, vous pouvez également envisager d’utiliser slf4j et logback au lieu de log4j. slf4j a un design plus propre qui correspond mieux à la scala idiomatique.

19
Kevin Wright

J'ai utilisé log4j avec Scala en créant un trait et en enregistrant l'enregistreur par instance et non par classe. Avec certaines magies et certains manifestes Scala, vous pourrez peut-être modifier l'enregistreur pour qu'il soit statique (objet interne), mais je ne suis pas sûr à 100%. Personnellement, je suis d’accord avec @KevinWright pour dire que rendre l’enregistreur statique est une optimisation prématurée.

Notez également que le code ci-dessous contient les messages de journal sous forme de nom, ce qui signifie que vos appels de journal ne doivent pas nécessairement être encapsulés dans `if (log.isDebugEnabled ()); Les messages de journal complexes créés via la concaténation de chaînes ne seront pas évalués, sauf si le niveau de journalisation est 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
davetron5000

Si vous êtes vraiment préoccupé par la surcharge d'espace et/ou le temps supplémentaire nécessaire aux initialiseurs d'objet, une bonne stratégie peut consister à avoir un trait de journalisation qui laisse le résumé de l'enregistreur 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
}

Dans le cas d'espèce, l'EJI pourrait même inclure debugwarn et logger.

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


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

Une autre option consiste à laisser la session en dehors de la classe et à la mettre complètement 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é à moins que vous n'en ayez besoin.

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

Non?

3
F0RR

Parfois, se connecter 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 package. Pour obtenir une journalisation plus fine, vous pouvez disperser des définitions similaires dans la hiérarchie des packages. 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 pourrait être définie comme suit (en supposant que 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
Arnold deVos

Voici un petit hack (que je n'ai pas 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)}
}

J'espère que c'est utile.

1
Don Mackenzie

L'API de journalisation typesafe ( https://github.com/typesafehub/scalalogging ) a la particularité d'ajouter un val de journal à une classe mais son utilisation est facultative. Il initialise la variable à l’aide de getClass getName, dont la moitié sera sans valeur, car le nom actuel de votre classe sera souvent 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 placer la valeur logger dans l'objet compagnon et effectuer une importation dans la classe des membres des objets pas besoin de le qualifier.

1
user486646

Une solution consiste à é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 du consignateur:

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

Ceci est plus facile à utiliser mais aura de moins bonnes performances. Les performances seront vraiment mauvaises si vous allez supprimer la plupart des messages de journal (en raison de la configuration du niveau de journalisation).

0
Thomas Jung