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.
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.
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!")
}
}
}
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 debug
warn
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.
object Log {
def log(message: String) = {
.....
}
}
Non?
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)
}
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.
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.
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).