2010-01-07 26 views
28

En Java, la expresión estándar para el registro es crear una variable estática para un objeto logger y usar eso en los diversos métodos.iniciando sesión en scala

En Scala, parece que el modismo es crear un rasgo de registro con un miembro de registro y mezclar el rasgo en clases concretas. Esto significa que cada vez que se crea un objeto llama al marco de registro para obtener un registrador y también el objeto es más grande debido a la referencia adicional.

¿Existe una alternativa que permita la facilidad de uso de "con el registro" mientras se utiliza una instancia del registrador por clase?

EDITAR: Mi pregunta no es cómo se puede escribir un marco de trabajo en Scala, sino cómo usar uno existente (log4j) sin incurrir en una sobrecarga de rendimiento (obtener una referencia para cada instancia) o complejidad del código. Además, sí, quiero usar log4j, simplemente porque usaré bibliotecas de terceros escritas en Java que probablemente usen log4j.

+0

¿cuál es la ruta del registro? – Adrian

Respuesta

19

Me limitaría al enfoque "con registro". El diseño limpio gana todo el tiempo: si obtienes el texto estándar, es probable que puedas obtener ganancias mucho más útiles en otras áreas.

Tenga en cuenta que el marco de trabajo registrará registradores de caché, por lo que todavía tiene uno por clase, incluso si cada instancia de esa clase tiene una referencia (de bajo costo).

Sin pruebas de que las referencias del registrador están dañando su montón, esto huele mucho a una optimización prematura ... Solo relájese y no se preocupe, a menos que un generador de perfiles le indique lo contrario.

En una nota no relacionada, es posible que también desee examinar el uso de slf4j y logback en lugar de log4j. slf4j tiene un diseño más limpio que se adapta mejor a la scala idiomática.

+0

, entonces me parece que este es un caso en el que Java gana con Scala (especialmente si admite IDE donde el IDE puede generar el código de declinación de la variable de registro estático). Scala todavía está muy avanzado en la carrera, pero esperaba que alguien pudiera resolver este caso de uso. Y esta es una optimización prematura, pero toca cada bit de código de mi sistema. No veo cómo puedo optimizarlo más tarde. – IttayD

+1

De hecho, he visto este enfoque por instancia utilizado en Java, incluso lo he usado yo mismo. El principal beneficio es que puede especificar 'Logger.getLogger (this.class)' al construir el registrador, lo que evita problemas cuando se copia este tipo de código de preámbulo entre las definiciones de clase –

+3

Tengo este rasgo 'trait J2SELogging {protected val log = Logger.getLogger (getClass.getName)} ' – fommil

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

¿No?

+0

Creo que esta es la mejor solución, porque no creo que sea inteligente extender/implementar un rasgo cuando no hay una relación "es una". – paradigmatic

+1

Es una clase que usa soporte de registro. :-) Falta la clase/categoría de registro. Debería ser al menos 'Log.log (c: Class [_], message: String)' –

1

Aquí un corte rápido (que no he estado realmente usando, honesto; @)

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

espero que sea de alguna utilidad.

+0

¿Cómo responde esto mi pregunta? No pregunté cómo podrías escribir un marco de registro en Scala. Le pregunté cómo podría usar uno existente (por ejemplo, log4j) – IttayD

0

Una forma es el registrador se extiende al objeto compañera:

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

alternativa, se puede almacenar en caché los casos 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") 
} 

Esto es más fácil de usar, pero tendrá un peor rendimiento. El rendimiento será realmente malo cuando vaya a descartar la mayoría de los mensajes de registro (debido a la configuración de nivel de registro).

+0

sí, pero luego es tan engorroso como el equivalente de Java ... – IttayD

+0

observe también que el registrador se categorizará según la clase del objeto que en la JVM subyacente no es el mismo nombre de clase que la clase real (un '$' al final) que es confuso (alguien quiere configurar el nivel de depuración y tiene que recordar configurarlo para la clase del objeto complementario) – IttayD

+0

Sí, la versión del objeto no es buena. Agregué una solución alternativa que almacena en caché las instancias de Logger. Pero aún no es perfecto. –

11

Utilicé log4j con Scala creando un rasgo y teniendo el registrador por instancias no por clase. Con algunos conjuros y magia de Scala, es posible que puedas cambiar el registrador para que sea estático (objeto interno), pero no estoy 100% seguro. Personalmente, estoy de acuerdo con @KevinWright que hacer que el registrador sea estático es una optimización prematura.

También tenga en cuenta que el código siguiente tiene los mensajes de registro como un nombre, lo que significa que sus llamadas al registrador no necesitan ser incluidas en `if (log.isDebugEnabled()); los mensajes de registro complejos creados mediante la concatenación de cadenas no se evaluarán a menos que el nivel de registro sea el adecuado.Vea este enlace para más información: 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

Probé tu código en eclipse ... pero el mensaje de depuración no se imprimió en la consola ... necesito algo más, por favor guía – HDev007

5

Si está realmente preocupado por espacio superior y/o el tiempo extra en inicializadores de objeto, una buena estrategia puede ser tener un rasgo de registro que sale de la abstracta registrador como en

 

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

para las clases que han de ser lo más ligero posible, puede hacerlo

 

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

El JIT podría incluso alinear debugwarn y logger en este caso.

Usted también puede tener un rasgo de mezclar en las clases en las que no es un problema


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

Otra opción es dejar salir de la sesión de la clase y ponerlo por completo en una la sobrecarga de un campo adicional objeto como en


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

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

Estoy de acuerdo con Kevin, sin embargo, no añadir la complejidad a menos que lo necesite.

2

A veces, el inicio de sesión en el nivel del paquete es la respuesta correcta. Scala lo hace más fácil que java porque los objetos se pueden definir directamente en un paquete. Si definió un registro como este:

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

Este registro está disponible en todas partes en el ejemplo del paquete. Para obtener un registro más detallado, puede dispersar definiciones similares alrededor de la jerarquía del paquete. O puede definido todos los registradores de paquetes juntos de esta manera:

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 clase PackageLogger podría definirse de la siguiente manera (SLF4J asumiendo):

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

La API de registro typesafe (https://github.com/typesafehub/scalalogging) tiene un rasgo de agregando un logger val a una clase pero su uso es opcional. Inicializa la variable usando getClass getName, que la mitad del tiempo no tendrá valor porque con frecuencia su nombre de clase real será gobbledygook.

Así que si no desea que el rasgo agregue la variable adicional a cada instancia, ciertamente no necesita usarlo y puede simplemente poner el logger val en su objeto complementario y hacer una importación en la clase del acompañante objetos miembros por lo que no es necesario calificarlo.

Cuestiones relacionadas