logging - Iniciando sesión en Scala
(13)
envoltorios slf4j
La mayoría de las bibliotecas de registro de Scala han sido algunos envoltorios alrededor de un marco de registro de Java (slf4j, log4j, etc.), pero a partir de marzo de 2015, las bibliotecas de registro supervivientes son todas slf4j. Estas bibliotecas de registro proporcionan algún tipo de objeto de log
al que puede llamar info(...)
, debug(...)
, etc. No soy un gran admirador de slf4j, pero ahora parece ser el marco de registro predominante . Aquí está la descripción de SLF4J :
Simple Logging Facade para Java o (SLF4J) sirve como una simple fachada o abstracción para varios marcos de registro, por ejemplo, java.util.logging, log4j y logback, lo que permite al usuario final conectar el marco de trabajo de registro deseado en el momento del despliegue.
La capacidad de cambiar la biblioteca de registro subyacente en el momento del despliegue proporciona una característica única a toda la familia de registradores slf4j, que debe tener en cuenta:
- classpath como enfoque de configuración . La forma en que slf4j sabe qué biblioteca de registro subyacente está utilizando es cargando una clase por algún nombre. He tenido problemas en los que slf4j no reconoció mi registrador cuando se personalizó el cargador de clases.
- Debido a que la fachada simple intenta ser el denominador común, se limita solo a las llamadas de registro reales. En otras palabras, la configuración no se puede hacer a través del código.
En un proyecto grande, en realidad podría ser conveniente poder controlar el comportamiento de registro de dependencias transitivas si todos usaran slf4j.
Registro de Scala
Scala Logging está escrito por Heiko Seeberger como sucesor de su slf4s . Utiliza macro para expandir las llamadas a la expresión if para evitar una llamada de registro potencialmente costosa.
Scala Logging es una biblioteca de registro conveniente y de rendimiento que abarca bibliotecas de registro como SLF4J y potencialmente otras.
Registradores históricos
¿Cuál es una buena forma de iniciar sesión en una aplicación Scala? Algo que es consistente con la filosofía del lenguaje, no satura el código, es de bajo mantenimiento y discreto. Aquí hay una lista de requisitos básicos:
- sencillo
- no satura el código Scala es genial por su brevedad. No quiero que la mitad de mi código sea una declaración de registro
- el formato de registro se puede cambiar para adaptarse al resto de mis registros de empresa y software de supervisión
- admite niveles de registro (es decir, depuración, rastreo, error)
- puede iniciar sesión en el disco, así como otros destinos (es decir, socket, consola, etc.)
- configuración mínima, si hay alguna
- funciona en contenedores (es decir, servidor web)
- (opcional, pero agradable de tener) viene como parte del lenguaje o como un artefacto experto, así que no tengo que hackear mis compilaciones para usarlo
Sé que puedo usar las soluciones de registro de Java existentes, pero fallan en al menos dos de las anteriores, a saber, desorden y configuración.
Gracias por tus respuestas.
No use Logula
De hecho, he seguido la recomendación de Eugene y lo intenté y descubrí que tiene una configuración torpe y está sujeto a errores que no se corrigen (como este ). No parece estar bien mantenido y no es compatible con Scala 2.10 .
Utilice slf4s + slf4j-simple
Beneficios clave:
- Admite la última versión de Scala 2.10 (hasta la fecha es M7)
- La configuración es versátil pero no podría ser más simple. Se hace con las propiedades del sistema , que puede establecer añadiendo algo así como
-Dorg.slf4j.simplelogger.defaultlog=trace
al comando de ejecución o hardcode en su script:System.setProperty("org.slf4j.simplelogger.defaultlog", "trace")
. ¡No es necesario administrar los archivos de configuración basura! - Encaja muy bien con IDEs. Por ejemplo, para establecer el nivel de registro en "rastreo" en una configuración de ejecución específica en IDEA, vaya a
Run/Debug Configurations
y agregue-Dorg.slf4j.simplelogger.defaultlog=trace
a lasVM options
. - Fácil configuración: solo ingrese las dependencias desde la parte inferior de esta respuesta
Esto es lo que necesita para ejecutarlo con Maven:
<dependency>
<groupId>com.weiglewilczek.slf4s</groupId>
<artifactId>slf4s_2.9.1</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.6</version>
</dependency>
Así es como conseguí que Scala Logging funcionara para mí:
Pon esto en tu build.sbt
:
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2",
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
Luego, después de hacer una sbt update
, esto imprime un mensaje de registro amigable:
import com.typesafe.scalalogging._
object MyApp extends App with LazyLogging {
logger.info("Hello there")
}
Si está usando Play, puede simplemente import play.api.Logger
para escribir mensajes de registro: Logger.debug("Hi")
.
Ver los docs para más información.
Con Scala 2.10+ Considere ScalaLogging by Typesafe. Utiliza macros para entregar una API muy limpia
https://github.com/typesafehub/scala-logging
Citando de su wiki:
Afortunadamente, las macros de Scala se pueden usar para facilitar nuestras vidas: ScalaLogging ofrece a la clase
Logger
con métodos de registro livianos que se ampliarán al idioma anterior. Entonces todo lo que tenemos que escribir es:
logger.debug(s"Some ${expensiveExpression} message!")
Después de que se haya aplicado la macro, el código se habrá transformado en la expresión idiomática descrita anteriormente.
Además, ScalaLogging ofrece el Logging
características que proporciona de manera conveniente una instancia de Logger
inicializada con el nombre de la clase mezclada en:
import com.typesafe.scalalogging.slf4j.LazyLogging
class MyClass extends LazyLogging {
logger.debug("This is very convenient ;-)")
}
Debería echar un vistazo a la biblioteca scalax: http://scalax.scalaforge.org/ En esta biblioteca, hay un rasgo de registro, usando sl4j como back-end. Al usar este rasgo, puede iniciar sesión con bastante facilidad (solo use el campo de registro en la clase que hereda el rasgo).
Después de usar slf4s y Logula por un tiempo, escribí loglady
, un rasgo de registro simple que envuelve slf4j.
Ofrece una API similar a la de la biblioteca de registro de Python, lo que hace que los casos comunes (cadena básica, formateo simple) sean triviales y evite dar formato a la repetición.
Encuentro muy conveniente el uso de algún tipo de logger java, sl4j por ejemplo, con scala wrapper simple, que me trae esa sintaxis
val #! = new Logger(..) // somewhere deep in dsl.logging.
object User with dsl.logging {
#! ! "info message"
#! dbg "debug message"
#! trace "var a=true"
}
En mi opinión, es una combinación muy útil de marcos de registro de Java comprobados y la sintaxis de Scala.
Formas rápidas y fáciles.
Scala 2.10 y anterior:
import com.typesafe.scalalogging.slf4j.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")
Y build.sbt:
libraryDependencies += "com.typesafe" %% "scalalogging-slf4j" % "1.1.0"
Scala 2.11+ y más nuevo:
import import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")
Y build.sbt:
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0"
Saqué un poco de trabajo del rasgo de Logging
de scalax
y creé un rasgo que también integraba una biblioteca MessageFormat-based
.
Entonces algo así se ve así:
class Foo extends Loggable {
info( "Dude, I''m an {0} with {1,number,#}", "Log message", 1234 )
}
Nos gusta el enfoque hasta ahora.
Implementación:
trait Loggable {
val logger:Logger = Logging.getLogger(this)
def checkFormat(msg:String, refs:Seq[Any]):String =
if (refs.size > 0) msgfmtSeq(msg, refs) else msg
def trace(msg:String, refs:Any*) = logger trace checkFormat(msg, refs)
def trace(t:Throwable, msg:String, refs:Any*) = logger trace (checkFormat(msg, refs), t)
def info(msg:String, refs:Any*) = logger info checkFormat(msg, refs)
def info(t:Throwable, msg:String, refs:Any*) = logger info (checkFormat(msg, refs), t)
def warn(msg:String, refs:Any*) = logger warn checkFormat(msg, refs)
def warn(t:Throwable, msg:String, refs:Any*) = logger warn (checkFormat(msg, refs), t)
def critical(msg:String, refs:Any*) = logger error checkFormat(msg, refs)
def critical(t:Throwable, msg:String, refs:Any*) = logger error (checkFormat(msg, refs), t)
}
/**
* Note: implementation taken from scalax.logging API
*/
object Logging {
def loggerNameForClass(className: String) = {
if (className endsWith "$") className.substring(0, className.length - 1)
else className
}
def getLogger(logging: AnyRef) = LoggerFactory.getLogger(loggerNameForClass(logging.getClass.getName))
}
Todavía no lo he probado, pero Configgy parece prometedor tanto para la configuración como para el registro:
Usar slf4j y una envoltura es bueno, pero el uso de la interpolación construida se rompe cuando tienes más de dos valores para interpolar, ya que entonces necesitas crear una matriz de valores para interpolar.
Una solución más similar a Scala es usar un procesador o clúster para retrasar la concatenación del mensaje de error. Un buen ejemplo de esto es el registrador de Lift
Que se ve así:
class Log4JLogger(val logger: Logger) extends LiftLogger {
override def trace(msg: => AnyRef) = if (isTraceEnabled) logger.trace(msg)
}
Tenga en cuenta que msg es una llamada por nombre y no se evaluará a menos que isTraceEnabled sea verdadero, por lo que no hay costo en la generación de una buena cadena de mensajes. Esto funciona alrededor del mecanismo de interpolación del slf4j que requiere analizar el mensaje de error. Con este modelo, puede interpolar cualquier cantidad de valores en el mensaje de error.
Si tiene un rasgo separado que mezcla este Log4JLogger en su clase, entonces puede hacer
trace("The foobar from " + a + " doesn''t match the foobar from " +
b + " and you should reset the baz from " + c")
en lugar de
info("The foobar from {0} doesn''t match the foobar from {1} and you should reset the baz from {c},
Array(a, b, c))
Uso el SLF4J + Logback classic y lo utilizo así:
trait Logging {
lazy val logger = LoggerFactory.getLogger(getClass)
implicit def logging2Logger(anything: Logging): Logger = anything.logger
}
Entonces puede usarlo lo que mejor se adapte a su estilo:
class X with Logging {
logger.debug("foo")
debug("bar")
}
pero este enfoque, por supuesto, usa una instancia de registrador por instancia de clase.
Writer
, Monoid
y una implementación de Monad
.