Iniciar sesión en Scala

169

¿Cuál es una buena manera 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 abarrota el código. Scala es genial por su brevedad. No quiero que la mitad de mi código sean declaraciones de registro
  • el formato de registro se puede cambiar para adaptarse al resto de los registros de mi empresa y el software de monitoreo
  • admite niveles de registro (es decir, depuración, rastreo, error)
  • puede iniciar sesión en el disco, así como en otros destinos (es decir, socket, consola, etc.)
  • configuración mínima, si la hay
  • funciona en contenedores (es decir, servidor web)
  • (opcional, pero agradable de tener) viene como parte del lenguaje o como un artefacto experto, por lo 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, el desorden y la configuración.

Gracias por sus respuestas

Jorge
fuente

Respuestas:

119

envoltorios slf4j

La mayoría de las bibliotecas de registro de Scala han sido algunas envolturas alrededor de un marco de registro de Java (slf4j, log4j, etc.), pero a partir de marzo de 2015, las bibliotecas de registro sobrevivientes son todas slf4j. Estas bibliotecas de registros proporcionan algún tipo de logobjeto 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 registro deseado en el momento de la implementación.

La capacidad de cambiar la biblioteca de registros subyacente en el momento de la implementación aporta una característica única a toda la familia de registradores slf4j, que debe tener en cuenta:

  1. classpath como enfoque de configuración . La forma en que slf4j sabe qué biblioteca de registro subyacente está utilizando es cargando una clase con algún nombre. He tenido problemas en los que slf4j no reconoce mi registrador cuando se ha personalizado el cargador de clases.
  2. 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 las dependencias transitivas si todos usaran slf4j.

Scala Logging

Scala Logging está escrito por Heiko Seeberger como sucesor de su slf4s . Utiliza macro para expandir llamadas en expresión if para evitar llamadas de registro potencialmente costosas.

Scala Logging es una biblioteca de registro conveniente y eficiente que incluye bibliotecas de registro como SLF4J y potencialmente otras.

Registradores históricos

  • Logula , un contenedor Log4J escrito por Coda Hale. Solía ​​gustarme este, pero ahora está abandonado.
  • configgy , un contenedor java.util.logging que solía ser popular en los primeros días de Scala. Ahora abandonado.
Eugene Yokota
fuente
1
Debo decir que amo a Logula ... finalmente un registrador que no me da ganas de apuñalar el destornillador por la oreja. Sin xml ni BS, solo una configuración scala en el inicio
Arg
El SLF4S de Heiko Seeberger ya no parece mantenerse. Hice mi propio objetivo Scala 2.10 y el último SLF4J. http://slf4s.org/
Matt Roberts
3
Logula es abandonada según su LÉAME en Github.
Erik Kaplun
Oncue Journal es similar en concepto a Scala Logging, pero los mensajes de registro se envían a un actor que llama a SLF4J desde un grupo de subprocesos dedicado. Cambia el tiempo de CPU por latencia (mucho mejor) github.com/oncue/journal
Gary Coady
64

Con Scala 2.10+ Considere ScalaLogging de 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 hacernos la vida más fácil: ScalaLogging ofrece a la clase Loggermé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 el idioma descrito anteriormente.

Además, ScalaLogging ofrece el rasgo Loggingque convenientemente proporciona una Loggerinstancia inicializada con el nombre de la clase mezclado en:

import com.typesafe.scalalogging.slf4j.LazyLogging

class MyClass extends LazyLogging {
  logger.debug("This is very convenient ;-)")
}
fracca
fuente
12
Uso class MyClass with Logging.
Andrzej Jozwik
1
Tenga en cuenta que el uso de las características de inicio de sesión hace que la característica tenga el mismo nombre para el registrador que la clase en la que se mezcla. Creo que puede obtener el registrador manualmente en lugar de la conveniencia de macro para proporcionar el nombre del registrador manualmente, si es necesario. Pero por favor, corrígeme si me equivoco.
mkko
1
¿Significa esto que MyClass tiene métodos públicos para depuración, advertencia de información, etc.? Si es así, prefiero hacer el trabajo manual adicional de instanciar un registrador privado para la clase. Los métodos de registro no deben filtrarse en una interfaz de clases.
ChucK
2
obtienes un campo de registrador, que puedes usar a tu gusto. Ver github.com/typesafehub/scalalogging/blob/master/… para más detalles
fracca
2
2.x requiere Scala 2.11; ojalá no haya mucha diferencia práctica; para Scala 2.10.x, hay "com.typesafe" %% "scalalogging-slf4j" % "1.1.0".
Erik Kaplun
14

Usar slf4j y un contenedor es bueno, pero el uso de su interpolación integrada se descompone cuando tiene que interpolar más de dos valores, desde entonces necesita crear una Matriz de valores para interpolar.

Una solución más similar a Scala es usar un thunk o cluster para retrasar la concatenación del mensaje de error. Un buen ejemplo de esto es el registrador de Lift

Log.scala Slf4jLog.scala

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 ningún costo en generar una buena cadena de mensaje. Esto funciona alrededor del mecanismo de interpolación de 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 combina 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 vez 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))
Blair Zajac
fuente
2
¿Es posible obtener números de línea precisos para las declaraciones de registro en las clases scala?
jpswain
13

No uses Logula

De hecho, he seguido la recomendación de Eugene y lo probé y descubrí que tiene una configuración torpe y está sujeta 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. Está hecho con las propiedades del sistema , que se pueden establecer ya sea añadiendo algo parecido -Dorg.slf4j.simplelogger.defaultlog=traceal comando de ejecución o hardcode en el script: System.setProperty("org.slf4j.simplelogger.defaultlog", "trace"). ¡No es necesario administrar archivos de configuración basura!
  • Se adapta muy bien con IDEs. Por ejemplo, puede establecer el nivel de registro "huella" en una configuración específica de ejecución de IDEA sólo tiene que ir a Run/Debug Configurationsy añadir -Dorg.slf4j.simplelogger.defaultlog=tracea VM options.
  • Configuración fácil: simplemente coloque 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>
Nikita Volkov
fuente
¿Dónde encuentras slf4s que soporta 2.10? Estoy mirando github.com/weiglewilczek/slf4s y veo "Las versiones de Scala compatibles son 2.8.0, 2.8.1, 2.9.0-1 y 2.9.1". ¿Estoy buscando en el lugar equivocado?
nairbv
@Brian Use el 2.9.1 como se sugiere en la respuesta. Lo probé para que funcione bien con 2.10.0-M7
Nikita Volkov
Estoy usando 2.9.1, tenía curiosidad sobre el "beneficio clave" resaltado y lo que eso significa.
nairbv
Después de buscar cuidadosamente en las respuestas los pros y los contras de las bibliotecas de registro, elijo la que me dice qué dependencia importar. Gracias Señor.
teo
11

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 un 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á utilizando la reproducción, puede, por supuesto, simplemente import play.api.Loggerpara escribir mensajes de registro: Logger.debug("Hi").

Vea los documentos para más información.

Matthias Braun
fuente
Solo funcionó para mí después de crear log4j.propertiesdentro de la carpeta de recursos del proyecto.
Nadjib Mami
7

Saqué un poco de trabajo del Loggingrasgo de scalax, y creé un rasgo que también integraba una MessageFormat-basedbiblioteca.

Entonces las cosas se ven 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))  
}
Tristan Juricek
fuente
Lindo, aunque tengo algunos problemas para hacer que esto funcione, específicamente, el resultado (en la configuración predeterminada de slf4j) siempre se parece a lo siguiente, en lugar de que la clase realmente extienda Loggable: 22 de julio de 2011 3:02:17 AM redacted.util.Loggable $ class info INFORMACIÓN: Algún mensaje ... ¿Alguna posibilidad de que te hayas encontrado con esto?
Tomer Gabel
7

Uso SLF4J + Logback classic y lo aplico así:

trait Logging {
  lazy val logger = LoggerFactory.getLogger(getClass)

  implicit def logging2Logger(anything: Logging): Logger = anything.logger
}

Luego puede usarlo según 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.

Kristof Jozsa
fuente
4

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 registrador en la clase que hereda el rasgo).


fuente
3

Formas rápidas y fáciles.

Scala 2.10 y mayores:

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 reciente:

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"
xgMz
fuente
2

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, que hace que los casos comunes (cadena básica, formato simple) sean triviales y evita el formato repetitivo.

http://github.com/dln/loglady/

dln
fuente
2

Me parece muy conveniente usar algún tipo de Java Logger, sl4j, por ejemplo, con un simple scala wrapper, lo que me brinda 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 muy útil la combinación de marcos de registro probados de Java y la sintaxis elegante de Scala.

Alex Povar
fuente
@sschaef, oh sí. Lo siento. Casi me olvido. Fue una gran pelea con este problema durante unos días, pero parece que esto es realmente imposible. He usado #! ! "mensaje de información" en su lugar. Editaré editar mi respuesta.
Alex Povar