2010-01-07 18 views
28

In Java besteht das Standard-Idiom für die Protokollierung darin, eine statische Variable für ein Loggerobjekt zu erstellen und diese in den verschiedenen Methoden zu verwenden.Anmelden in scala

In Scala sieht es so aus, als ob das Idiom ist, eine Logging-Eigenschaft mit einem Logger-Mitglied zu erstellen und das Merkmal in konkrete Klassen zu mixen. Dies bedeutet, dass jedes Mal, wenn ein Objekt erstellt wird, das Logging-Framework aufgerufen wird, um einen Logger zu erhalten, und das Objekt aufgrund der zusätzlichen Referenz ebenfalls größer ist.

Gibt es eine Alternative, die die Benutzerfreundlichkeit von "mit Protokollierung" ermöglicht, während immer noch eine Logger-Instanz pro Klasse verwendet wird?

EDIT: Meine Frage geht nicht darum, wie man ein Logging-Framework in Scala schreiben kann, sondern wie man ein existierendes (log4j) verwendet, ohne einen Overhead der Leistung (eine Referenz für jede Instanz zu bekommen) oder Komplexität des Codes. Außerdem, ja, ich möchte log4j verwenden, einfach weil ich Bibliotheken von Drittanbietern verwenden werde, die in Java geschrieben sind und wahrscheinlich log4j verwenden.

+0

Was ist der Protokollpfad? – Adrian

Antwort

19

Ich würde nur bei der "mit Protokollierung" -Ansatz bleiben. Sauberes Design gewinnt jedes Mal - wenn Sie das BoilerPlate aus dem Weg bekommen, dann sind die Chancen, dass Sie viel nützlichere Gewinne in anderen Bereichen finden können.

Denken Sie daran, dass das Logging-Framework Logger zwischenspeichert, so dass Sie immer noch einen pro Klasse haben, selbst wenn jede Instanz dieser Klasse eine (preiswerte) Referenz enthält.

Ohne Beweis, dass Logger-Referenzen Ihren Heap schädigen, riecht das sehr nach voreiliger Optimierung ... Entspannen Sie sich einfach und machen Sie sich keine Sorgen, es sei denn, ein Profiler sagt Ihnen etwas anderes.

In einem nicht verwandten Hinweis, möchten Sie vielleicht auch mit slf4j und logback statt log4j suchen. slf4j hat ein sauberes Design, das besser zu idiomatischen Scala passt.

+0

dann sieht es für mich so aus, dass Java Scala übertrifft (vor allem, wenn man IDE-Unterstützung nimmt, wo der statische Log-Deklarationscode von der IDE erzeugt werden kann). Scala ist im Rennen immer noch weit vorne, aber ich hatte gehofft, dass dieser Anwendungsfall von jemandem gelöst werden könnte. Und das ist in der Tat vorzeitige Optimierung, aber es berührt jedes bisschen Code meines Systems. Ich sehe nicht, wie ich später optimieren kann. – IttayD

+1

Ich habe diesen in Java verwendeten proinstanzellen Ansatz tatsächlich gesehen, ich habe ihn sogar selbst verwendet. Der Hauptvorteil ist, dass Sie 'Logger.getLogger (this.class)' angeben können, wenn Sie den Logger erstellen - wodurch Probleme vermieden werden, wenn diese Art von Präambel-Code zwischen Klassendefinitionen kopiert wird –

+3

Ich habe dieses Merkmal 'Merkmal J2SELogging {protected val log = Logger.getLogger (getClass.getName)} ' – fommil

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

Nein?

+0

Ich denke, das ist die beste Lösung, denn ich denke nicht, dass es schlau ist, ein Merkmal zu erweitern/zu implementieren, wenn es keine Beziehung "ist ein" gibt. – paradigmatic

+1

Es ist eine Klasse, die Protokollierungsunterstützung verwendet. :-) Die Klasse/Logging-Kategorie fehlt. Es sollte mindestens 'Log.log (c: Class [_], Nachricht: String)' –

1

Hier ist eine schnelle Hack (was ich habe nicht wirklich gewesen verwendet, ehrlich; @)

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

Hoffe, dass es von Nutzen ist.

+0

Wie beantwortet das meine Frage? Ich habe nicht gefragt, wie man ein Logging-Framework in Scala schreiben könnte. Ich fragte, wie Sie ein vorhandenes verwenden könnten (sagen log4j) – IttayD

0

Eine Möglichkeit ist, den Logger zum Begleiter-Objekt erweitert:

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

Alternativ können Sie die Logger-Instanzen cachen:

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

Dies ist einfacher zu bedienen ist, aber schlechtere Leistung haben. Die Leistung ist sehr schlecht, wenn Sie die meisten Protokollmeldungen verwerfen (wegen der Konfiguration der Protokollebene).

+0

ja, aber dann ist es so umständlich wie das Java-Äquivalent ... – IttayD

+0

Beachten Sie auch, dass der Logger nach der Klasse des Objekts kategorisiert wird, die in der zugrunde liegenden JVM nicht den gleichen Klassennamen wie die eigentliche Klasse (ein '$' am Ende) hat, was verwirrend ist (jemand möchte Debug-Level einrichten und muss daran denken, es für die Klasse des Companion-Objekts zu setzen) – IttayD

+0

Ja, die Objektversion ist nicht nett. Ich habe eine alternative Lösung hinzugefügt, die die Logger-Instanzen zwischenspeichert. Aber es ist immer noch nicht perfekt. –

11

Ich habe log4j mit Scala verwendet, indem ich ein Merkmal erstellt habe und den Logger pro Instanz nicht pro Klasse hat. Mit einigen Scala-Zaubern und Manifesten könntest du vielleicht den Logger als statisch (internes Objekt) verändern, aber ich bin mir nicht 100% sicher. Persönlich stimme ich @KevinWright zu, dass es eine vorzeitige Optimierung ist, den Logger statisch zu machen.

Beachten Sie auch, dass der folgende Code die Protokollmeldungen als Name enthält, was bedeutet, dass Ihre Logger-Aufrufe nicht in `if (log.isDebugEnabled()) eingebunden werden müssen; Komplexe Protokollnachrichten, die über die Verkettung von Zeichenketten erstellt wurden, werden nur ausgewertet, wenn die Protokollierungsstufe angemessen ist.Siehe diesen Link für weitere Informationen: 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!") 
    } 
    } 
} 
+0

Ich habe versucht, Ihren Code in Eclipse..aber die Debug-Nachricht druckte nicht auf Konsole..do Ich brauche etwas anderes bitte führen – HDev007

5

Wenn Sie wirklich besorgt über Raum sind Overhead und/oder zusätzliche Zeit in Objektinitialisierer kann eine gute Strategie sein, um eine Protokollierung Eigenschaft zu haben, dass die Blätter Logger abstrakt wie in

 

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

Für Klassen, die so leicht wie möglich sein müssen, dann können Sie tun

 

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

Die JIT könnte sogar Inline debugwarn und in diesem Fall.

können Sie haben auch ein Merkmal für die Klassen in mischen, wo der Aufwand für ein zusätzliches Feld ist kein Problem


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

Eine weitere Möglichkeit der Abmeldung von der Klasse zu verlassen, und es vollständig in einem setzen Objekt wie in

Ich stimme mit Kevin obwohl, fügen Sie nicht die Komplexität, es sei denn, Sie brauchen es.

2

Manchmal ist die Protokollierung auf Paketebene die richtige Antwort. Scala macht dies einfacher als Java, weil Objekte direkt in einem Paket definiert werden können. Wenn Sie ein Protokoll wie folgt definiert haben:

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

Dieses Protokoll ist überall im Paketbeispiel verfügbar. Um eine detailliertere Protokollierung zu erhalten, können Sie ähnliche Definitionen um die Pakethierarchie herum verteilen. Oder Sie können definiert alle Paket-Logger zusammen wie folgt aus:

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

    object Log extends PackageLogger 

    package frobber { 
    object Log extends PackageLogger 

    package undulater { 
     object Log extends PackageLogger 
    } 
    } 
} 

Die PackageLogger Klasse definiert werden könnte wie folgt (unter der Annahme 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

Die typsichere Protokollierung API (https://github.com/typesafehub/scalalogging) ein Merkmal für Hinzufügen eines Logger-Wertes zu einer Klasse, aber ihre Verwendung ist optional. Es initialisiert die Variable mit getClass getName, wobei die Hälfte der Zeit wertlos ist, da häufig Ihr tatsächlicher Klassenname ein Kauderwelsch ist.

Wenn Sie also nicht möchten, dass die Eigenschaft die zusätzliche Variable zu jeder Instanz hinzufügt, brauchen Sie sie nicht zu verwenden und können den Logger einfach in Ihr Begleitobjekt einfügen und einen Import in die Klasse des Begleiters durchführen Objekte Mitglieder, so dass Sie es nicht qualifizieren müssen.