Fazendo login no Scala


168

Qual é uma boa maneira de fazer logon em um aplicativo Scala? Algo que é consistente com a filosofia da linguagem, não confunde o código e é de baixa manutenção e discreto. Aqui está uma lista de requisitos básicos:

  • simples
  • não confunde o código. Scala é ótimo por sua brevidade. Não quero que metade do meu código seja instruções de log
  • o formato do log pode ser alterado para se ajustar ao restante dos meus registros corporativos e software de monitoramento
  • suporta níveis de log (ou seja, depuração, rastreamento, erro)
  • pode registrar no disco, bem como em outros destinos (por exemplo, soquete, console etc.)
  • configuração mínima, se houver
  • funciona em contêineres (servidor web)
  • (opcional, mas é bom ter) vem como parte do idioma ou como um artefato inventado, por isso não preciso hackear minhas construções para usá-lo

Eu sei que posso usar as soluções de log Java existentes, mas elas falham em pelo menos duas das opções acima, ou seja, desorganização e configuração.

Obrigado por suas respostas.

Respostas:


119

envoltórios slf4j

A maioria das bibliotecas de log do Scala tem alguns wrappers em torno de uma estrutura de log do Java (slf4j, log4j etc.), mas a partir de março de 2015, as bibliotecas de log restantes são todas slf4j. Essas bibliotecas de log fornecer algum tipo de logobjeto ao qual você pode chamar info(...), debug(...)etc. Eu não sou um grande fã de slf4j, mas agora parece ser a estrutura de log predominante. Aqui está a descrição do SLF4J :

A Fachada de Log Simples para Java ou (SLF4J) serve como uma fachada ou abstração simples para várias estruturas de log, por exemplo, java.util.logging, log4j e logback, permitindo que o usuário final conecte a estrutura de log desejada no momento da implantação.

A capacidade de alterar a biblioteca de log subjacente no momento da implantação traz características únicas para toda a família de registradores slf4j, da qual você precisa estar ciente:

  1. caminho de classe como abordagem de configuração . A maneira como o slf4j sabe qual biblioteca de log subjacente você está usando é carregando uma classe com algum nome. Eu tive problemas em que o slf4j não reconhecia meu logger quando o classloader foi personalizado.
  2. Como a fachada simples tenta ser o denominador comum, é limitada apenas às chamadas de log reais. Em outras palavras, a configuração não pode ser feita via código.

Em um projeto grande, seria realmente conveniente controlar o comportamento de log de dependências transitivas se todos usassem slf4j.

Scala Logging

Scala Logging é escrito por Heiko Seeberger como sucessor de seus slf4s . Ele usa macro para expandir as chamadas na expressão if para evitar chamadas de log potencialmente caras.

O Scala Logging é uma biblioteca de log conveniente e de alto desempenho, que envolve bibliotecas de log como o SLF4J e potencialmente outras.

Registradores históricos

  • Logula , um invólucro Log4J escrito por Coda Hale. Costumava gostar deste, mas agora está abandonado.
  • configgy , um wrapper java.util.logging que costumava ser popular nos dias anteriores do Scala. Agora abandonado.

1
Tenho que dizer, eu amo Logula ... finalmente um madeireiro que não me faz querer enfiar uma chave de fenda na minha orelha. Sem xml e sem BS, apenas uma configuração do scala na inicialização
Arg

O SLF4S da Heiko Seeberger não parece mais ser mantido. Fiz meu próprio Scala 2.10 e o SLF4J mais recente. http://slf4s.org/
Matt Roberts

3
O Logula é abandonado de acordo com o README no Github.
Erik Kaplun 30/03

O Oncue Journal é semelhante em conceito ao Scala Logging, mas as mensagens de log são enviadas para um ator que chama SLF4J de um pool de encadeamentos dedicado. Negocia o tempo de CPU para (muito melhor) latência. github.com/oncue/journal
Gary Coady

64

Com o Scala 2.10+, considere o ScalaLogging da Typesafe. Usa macros para fornecer uma API muito limpa

https://github.com/typesafehub/scala-logging

Citando seu wiki:

Felizmente, as macros Scala podem ser usadas para facilitar nossa vida: o ScalaLogging oferece à classe Loggermétodos de registro leves que serão expandidos para o idioma acima. Então, tudo o que precisamos escrever é:

logger.debug(s"Some ${expensiveExpression} message!")

Após a aplicação da macro, o código será transformado no idioma descrito acima.

Além disso, o ScalaLogging oferece a característica Loggingque fornece convenientemente uma Loggerinstância inicializada com o nome da classe misturada em:

import com.typesafe.scalalogging.slf4j.LazyLogging

class MyClass extends LazyLogging {
  logger.debug("This is very convenient ;-)")
}

12
Use class MyClass with Logging.
Andrzej Jozwik

1
Observe que o uso de características de logon faz com que o mesmo tenha o mesmo nome para o criador de log que a classe na qual ele é misturado. Eu acho que você pode obter o criador de logs manualmente, em vez da conveniência de macro, para fornecer o nome do criador de logs manualmente, se necessário. Mas por favor, me corrija se eu estiver errado.
Mkko

1
Isso significa que o MyClass possui métodos públicos para depuração, aviso de informações etc.? Nesse caso, prefiro fazer o trabalho manual extra de instanciar um criador de logs privado para a classe. Os métodos de log não devem vazar para uma interface de classes.
ChucK

2
você recebe um campo de logger, que você pode usar quando quiser. Veja github.com/typesafehub/scalalogging/blob/master/… para obter detalhes
fracca

2
2.x requer Scala 2.11; espero que não haja muita diferença prática; para Scala 2.10.x, existe "com.typesafe" %% "scalalogging-slf4j" % "1.1.0".
Erik Kaplun 30/03

14

Usar slf4j e um wrapper é bom, mas o uso dele é incorporado na interpolação é interrompido quando você tem mais de dois valores para interpolar, desde então você precisa criar uma Matriz de valores para interpolar.

Uma solução mais semelhante ao Scala é usar um thunk ou cluster para atrasar a concatenação da mensagem de erro. Um bom exemplo disso é o logger do Lift

Log.scala Slf4jLog.scala

Que se parece com isso:

class Log4JLogger(val logger: Logger) extends LiftLogger {
  override def trace(msg: => AnyRef) = if (isTraceEnabled) logger.trace(msg)
}

Observe que msg é uma chamada por nome e não será avaliada, a menos que isTraceEnabled seja verdadeiro; portanto, não há custo em gerar uma boa sequência de mensagens. Isso funciona em torno do mecanismo de interpolação do slf4j, que requer a análise da mensagem de erro. Com este modelo, você pode interpolar qualquer número de valores na mensagem de erro.

Se você tem uma característica separada que mistura esse Log4JLogger em sua classe, pode fazer

trace("The foobar from " + a + " doesn't match the foobar from " +
      b + " and you should reset the baz from " + c")

ao invés 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))

2
é possível obter números de linha precisos para instruções de log nas classes scala ?????
Jspwain

13

Não use Logula

Na verdade, segui a recomendação de Eugene e tentei e descobri que ela tem uma configuração desajeitada e está sujeita a erros, que não são corrigidos (como este ). Não parece estar bem conservado e não suporta o Scala 2.10 .

Use slf4s + slf4j-simple

Principais benefícios:

  • Suporta o Scala 2.10 mais recente (até o momento é M7)
  • A configuração é versátil, mas não poderia ser mais simples. É feito com as propriedades do sistema , que podem ser definidas quer acrescentando algo como -Dorg.slf4j.simplelogger.defaultlog=tracea comando de execução ou hardcode no seu script: System.setProperty("org.slf4j.simplelogger.defaultlog", "trace"). Não há necessidade de gerenciar arquivos de configuração inúteis!
  • Adapta-se perfeitamente aos IDEs. Por exemplo, para definir o nível de log para "rastrear" em uma configuração de execução específica no IDEA, basta ir Run/Debug Configurationse adicionar -Dorg.slf4j.simplelogger.defaultlog=tracea VM options.
  • Configuração fácil: basta soltar as dependências na parte inferior desta resposta

Aqui está o que você precisa para executá-lo com o 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>

onde você encontra o slf4s que suporta 2.10? Estou olhando para github.com/weiglewilczek/slf4s e consulte "As versões suportadas do Scala são 2.8.0, 2.8.1, 2.9.0-1 e 2.9.1". .. estou procurando no lugar errado?
nairbv

@ Brian Use o 2.9.1 como sugerido na resposta. Eu testei-o para ficar bem trabalhando com 2.10.0-M7
Nikita Volkov

Estou usando o 2.9.1, fiquei curioso sobre o "benefício principal" destacado e o que isso significa.
nairbv

Depois de pesquisar cuidadosamente as respostas dos prós e contras das bibliotecas de log, escolho a que me diz qual dependência importar. Obrigado senhor.
teo

11

Foi assim que o Scala Logging trabalhou para mim:

Coloque isso no seu build.sbt:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2",
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"

Então, depois de fazer um sbt update, isso imprime uma mensagem de log amigável:

import com.typesafe.scalalogging._
object MyApp extends App with LazyLogging {
  logger.info("Hello there")
}

Se você estiver usando Play, você pode, naturalmente, simplesmente import play.api.Loggerpor escrever mensagens de log: Logger.debug("Hi").

Veja os documentos para mais informações.


Só funcionou para mim depois que eu criei log4j.propertiesdentro da pasta de recursos do projeto.
Nadjib Mami

7

Puxei um pouco de trabalho da Loggingcaracterística scalaxe criei uma característica que também integrava uma MessageFormat-basedbiblioteca.

Então as coisas ficam assim:

class Foo extends Loggable {
    info( "Dude, I'm an {0} with {1,number,#}", "Log message", 1234 )
}

Até agora, gostamos da abordagem.

Implementação:

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

Bonito, embora eu esteja tendo alguns problemas para fazer isso funcionar - especificamente, a saída (na configuração padrão do slf4j) sempre se parece com o seguinte, em vez de a classe realmente estender Loggable: 22 de julho de 2011 3:02:17 redacted.util.Loggable $ informações da classe INFO: Alguma mensagem ... Alguma chance de você ter encontrado isso?
Tomer Gabel

6

Eu uso o SLF4J + Logback classic e o aplico assim:

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

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

Em seguida, você pode usá-lo conforme o seu estilo:

class X with Logging {
    logger.debug("foo")
    debug("bar")
}

mas essa abordagem, é claro, usa uma instância do criador de logs por instância da classe.


4

Você deve dar uma olhada na biblioteca scalax: http://scalax.scalaforge.org/ Nesta biblioteca, há uma característica de log, usando sl4j como back-end. Usando essa característica, você pode registrar com bastante facilidade (basta usar o campo registrador na classe que herda a característica).




3

Formulários rápidos e fáceis.

Scala 2.10 e mais antigo:

import com.typesafe.scalalogging.slf4j.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")

E build.sbt:

libraryDependencies += "com.typesafe" %% "scalalogging-slf4j" % "1.1.0"

Scala 2.11+ e mais recente:

import import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")

E build.sbt:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0"

2

Depois de usar o slf4s e o logula por um tempo, escrevi loglady, um simples traço de log envolvendo o slf4j.

Ele oferece uma API semelhante à da biblioteca de log do Python, que torna os casos comuns (string básico, formatação simples) triviais e evita a formatação padrão.

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


2

Acho muito conveniente usar algum tipo de java logger, sl4j, por exemplo, com um simples wrapper scala, o que me traz essa sintaxe

val #! = new Logger(..) // somewhere deep in dsl.logging.

object User with dsl.logging {

  #! ! "info message"
  #! dbg "debug message"
  #! trace "var a=true"

}

Na minha opinião, mixin muito útil de estruturas de registro comprovadas em java e a sintaxe sofisticada do scala.


@sschaef, sim. Desculpe. Eu quase esqueço. Foi uma grande luta com esse problema por alguns dias, mas parece que isso é realmente impossível. Eu usei #! ! "mensagem informativa". Vou editar, editar minha resposta.
Alex Povar
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.