Logowanie w Scali

168

Jaki jest dobry sposób logowania się w aplikacji Scala? Coś, co jest zgodne z filozofią języka, nie zaśmieca kodu, jest łatwe w utrzymaniu i dyskretne. Oto lista podstawowych wymagań:

  • prosty
  • nie zaśmieca kodu. Scala jest świetna ze względu na swoją zwięzłość. Nie chcę, aby połowa mojego kodu zawierała instrukcje logowania
  • format dziennika można zmienić, aby pasował do reszty dzienników przedsiębiorstwa i oprogramowania do monitorowania
  • obsługuje poziomy logowania (tj. debugowanie, śledzenie, błąd)
  • może logować się na dysk, a także w innych miejscach docelowych (np. gniazdo, konsola itp.)
  • minimalna konfiguracja, jeśli istnieje
  • działa w kontenerach (np. serwer WWW)
  • (opcjonalnie, ale miło mieć) pojawia się albo jako część języka, albo jako artefakt mavena, więc nie muszę hakować moich kompilacji, aby go używać

Wiem, że mogę korzystać z istniejących rozwiązań do logowania w Javie, ale zawodzą one co najmniej w dwóch z powyższych, a mianowicie w bałaganie i konfiguracji.

Dziękuję za twoje odpowiedzi.

Jerzy
źródło

Odpowiedzi:

119

slf4j wrapper

Większość bibliotek rejestrowania Scali stanowiła otoki wokół platformy rejestrowania Java (slf4j, log4j itp.), Ale od marca 2015 r. Wszystkie ocalałe biblioteki dzienników to slf4j. Te biblioteki dziennika zapewnić jakiś logobiekt, do którego można zadzwonić info(...), debug(...)itp Nie jestem wielkim fanem slf4j, ale teraz wydaje się być dominującym ramy rejestrowania. Oto opis SLF4J :

Simple Logging Facade for Java lub (SLF4J) służy jako prosta fasada lub abstrakcja dla różnych platform logowania, np. Java.util.logging, log4j i logback, umożliwiając użytkownikowi końcowemu podłączenie żądanej struktury rejestrowania w czasie wdrażania.

Możliwość zmiany podstawowej biblioteki logów w czasie wdrażania nadaje całej rodzinie rejestratorów slf4j unikalną cechę, o której należy pamiętać:

  1. classpath jako podejście do konfiguracji . Slf4j wie, której podstawowej biblioteki rejestrowania używasz, ładując klasę pod jakąś nazwą. Miałem problemy, w których slf4j nie rozpoznawał mojego loggera, gdy dostosowywano classloader.
  2. Ponieważ prosta fasada stara się być wspólnym mianownikiem, ogranicza się tylko do rzeczywistych wywołań dziennika. Innymi słowy, konfiguracji nie można przeprowadzić za pomocą kodu.

W dużym projekcie wygodna byłaby możliwość kontrolowania zachowania rejestrowania zależności przechodnich, gdyby wszyscy używali slf4j.

Rejestrowanie Scala

Scala Logging został napisany przez Heiko Seebergera jako następca jego slf4 . Używa makra do rozszerzania wywołań do wyrażenia if, aby uniknąć potencjalnie kosztownego wywołania dziennika.

Scala Logging to wygodna i wydajna biblioteka rejestrowania zawierająca biblioteki rejestrowania, takie jak SLF4J i potencjalnie inne.

Rejestratory historyczne

  • Logula , opakowanie Log4J napisane przez Codę Hale. Kiedyś lubiłem ten, ale teraz jest porzucony.
  • configgy , opakowanie java.util.logging, które było popularne we wcześniejszych dniach Scali. Teraz opuszczony.
Eugene Yokota
źródło
1
Muszę powiedzieć, że kocham Logulę ... wreszcie drwal, który nie sprawia, że ​​mam ochotę wbić śrubokręt w ucho. Brak xml i BS, tylko konfiguracja scala przy starcie
Arg
Wygląda na to, że SLF4S Heiko Seebergera nie jest już konserwowany. Zrobiłem własny celownik Scala 2.10 i najnowszy SLF4J. http://slf4s.org/
Matt Roberts
3
Logula została porzucona zgodnie z jej README na Github.
Erik Kaplun,
Oncue Journal jest podobny w koncepcji do Scala Logging, ale komunikaty dziennika są wysyłane do aktora, który wywołuje SLF4J z dedykowanej puli wątków. Wymienia czas procesora na (znacznie lepsze) opóźnienie. github.com/oncue/journal
Gary Coady
64

W przypadku wersji 2.10+ Scala rozważ ScalaLogging firmy Typesafe. Używa makr, aby zapewnić bardzo czysty interfejs API

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

Cytując z ich wiki:

Na szczęście makra Scala mogą ułatwić nam życie: ScalaLogging oferuje klasę Loggerz lekkimi metodami logowania, które zostaną rozszerzone do powyższego idiomu. Więc wszystko, co musimy napisać, to:

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

Po zastosowaniu makra kod zostanie przekształcony w wyżej opisany idiom.

Ponadto ScalaLogging oferuje cechę, Loggingktóra w wygodny sposób zapewnia Loggerinstancję zainicjowaną nazwą klasy zmieszaną z:

import com.typesafe.scalalogging.slf4j.LazyLogging

class MyClass extends LazyLogging {
  logger.debug("This is very convenient ;-)")
}
fracca
źródło
12
Użyj class MyClass with Logging.
Andrzej Jozwik 10.04.13
1
Należy pamiętać, że używanie cech logowania powoduje, że cecha ma taką samą nazwę dla rejestrującego, jak klasa, do której jest wmieszana. Myślę, że można uzyskać rejestrator ręcznie zamiast wygodnego makra, aby w razie potrzeby ręcznie podać nazwę rejestratora. Ale proszę, popraw mnie, jeśli się mylę.
mkko
1
Czy to oznacza, że ​​MyClass ma publiczne metody debugowania, ostrzeżenia o informacjach itp.? Jeśli tak, wolałbym wykonać dodatkową pracę ręczną polegającą na utworzeniu instancji rejestratora prywatnego dla klasy. Metody logowania nie powinny przeciekać do interfejsu klas.
ChucK,
2
otrzymujesz pole rejestratora, z którego możesz korzystać w wolnym czasie. Zobacz github.com/typesafehub/scalalogging/blob/master/ ... po szczegóły
fracca
2
2.x wymaga Scala 2.11; miejmy nadzieję, że nie ma dużej praktycznej różnicy; dla Scali 2.10.x jest "com.typesafe" %% "scalalogging-slf4j" % "1.1.0".
Erik Kaplun,
14

Używanie slf4j i wrappera jest fajne, ale użycie jego wbudowanej interpolacji nie działa, gdy masz więcej niż dwie wartości do interpolacji, ponieważ wtedy musisz utworzyć tablicę wartości do interpolacji.

Rozwiązaniem bardziej podobnym do Scali jest użycie thunk lub klastra do opóźnienia konkatenacji komunikatu o błędzie. Dobrym tego przykładem jest rejestrator Lift

Log.scala Slf4jLog.scala

Co wygląda tak:

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

Zauważ, że msg to wywołanie według nazwy i nie będzie oceniane, chyba że isTraceEnabled ma wartość true, więc generowanie ładnego ciągu wiadomości nie wiąże się z żadnymi kosztami. Działa to wokół mechanizmu interpolacji slf4j, który wymaga przeanalizowania komunikatu o błędzie. W tym modelu można interpolować dowolną liczbę wartości do komunikatu o błędzie.

Jeśli masz osobną cechę, która łączy ten Log4JLogger z Twoją klasą, możesz to zrobić

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

zamiast

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 Zając
źródło
2
czy można uzyskać dokładne numery wierszy do rejestrowania instrukcji w klasach scala ?????
jpswain
13

Nie używaj Logula

Właściwie zastosowałem się do zalecenia Eugene'a i wypróbowałem go i odkryłem, że ma niezdarną konfigurację i jest narażony na błędy, które nie są naprawiane (takie jak ten ). Nie wygląda na dobrze utrzymaną i nie obsługuje Scali 2.10 .

Użyj slf4s + slf4j-simple

Kluczowe korzyści:

  • Obsługuje najnowszą wersję Scala 2.10 (obecnie jest to M7)
  • Konfiguracja jest wszechstronna, ale nie może być prostsza. Odbywa się to za pomocą właściwości systemowych , które możesz ustawić, dołączając coś takiego jak -Dorg.slf4j.simplelogger.defaultlog=tracepolecenie wykonania lub twardy kod w swoim skrypcie:System.setProperty("org.slf4j.simplelogger.defaultlog", "trace") . Nie ma potrzeby zarządzania tandetnymi plikami konfiguracyjnymi!
  • Dobrze pasuje do IDE. Na przykład, aby ustawić poziom rejestrowania na „trace” w określonej konfiguracji uruchamiania w IDEA, po prostu przejdź do Run/Debug Configurationsi dodaj -Dorg.slf4j.simplelogger.defaultlog=tracedoVM options .
  • Łatwa konfiguracja: po prostu upuść zależności z dołu tej odpowiedzi

Oto, czego potrzebujesz, aby uruchomić go z 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
źródło
gdzie znajdziesz slf4s obsługujące 2.10? Patrzę na github.com/weiglewilczek/slf4s i widzę „Obsługiwane wersje Scala to 2.8.0, 2.8.1, 2.9.0-1 i 2.9.1”. .. czy szukam w złym miejscu?
nairbv
@Brian Użyj wersji 2.9.1 zgodnie z sugestią zawartą w odpowiedzi. Przetestowałem, że działa dobrze z 2.10.0-M7
Nikita Volkov,
Używam wersji 2.9.1. Byłem po prostu ciekawy wyróżnionej „kluczowej korzyści” i co to oznacza.
nairbv,
Po dokładnym przeszukaniu odpowiedzi pod kątem zalet i wad bibliotek logujących, wybieram tę, która mówi mi, jaką zależność zaimportować. Dziekuje panu.
teo
11

Oto jak sprawiłem, że Scala Logging działa dla mnie:

Umieść to w swoim build.sbt:

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

Następnie, po wykonaniu sbt update, wypisuje przyjazny komunikat dziennika:

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

Jeśli korzystasz z gry, można oczywiście po prostu import play.api.Loggerdo pisania wiadomości dziennika: Logger.debug("Hi").

Zobacz dokumentację, aby uzyskać więcej informacji.

Matthias Braun
źródło
Działało dla mnie tylko po utworzeniu log4j.propertieswewnątrz folderu zasobów projektu.
Nadjib Mami
7

Wyciągnąłem trochę pracy z Loggingcechy scalaxi stworzyłem cechę, która również integruje MessageFormat-basedbibliotekę.

Wtedy rzeczy wyglądają tak:

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

Jak dotąd podoba nam się to podejście.

Realizacja:

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
źródło
Śliczne, chociaż mam pewne problemy z uruchomieniem tego - konkretnie, dane wyjściowe (w domyślnej konfiguracji slf4j) zawsze wyglądają mniej więcej tak, jak poniżej, zamiast klasy faktycznie rozszerzającej Loggable: Jul 22, 2011 3:02:17 AM redacted.util.Loggable $ class info INFO: Jakaś wiadomość ... Czy jest szansa, że ​​się z tym spotkałeś?
Tomer Gabel
6

Używam SLF4J + Logback classic i stosuję to w następujący sposób:

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

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

Następnie możesz go użyć w zależności od tego, który lepiej pasuje do Twojego stylu:

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

ale to podejście oczywiście wykorzystuje instancję rejestrującą na instancję klasy.

Kristof Jozsa
źródło
4

Powinieneś rzucić okiem na bibliotekę scalax: http://scalax.scalaforge.org/ W tej bibliotece jest cecha Logging, wykorzystująca sl4j jako backend. Używając tej cechy, możesz się całkiem łatwo zalogować (po prostu użyj pola loggera w klasie dziedziczącej cechę).


źródło
3

Szybkie i łatwe formularze.

Scala 2.10 i starsze:

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

I build.sbt:

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

Scala 2.11+ i nowsze:

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

I build.sbt:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0"
xgMz
źródło
2

Po pewnym czasie używania slf4s i logula napisałem logladyprosty opis funkcji logowania slf4j.

Oferuje API podobne do biblioteki logującej Pythona, co sprawia, że ​​typowe przypadki (podstawowy ciąg, proste formatowanie) są trywialne i unika formatowania standardowego.

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

dln
źródło
2

Uważam za bardzo wygodne używanie jakiegoś loggera java, na przykład sl4j, z prostym opakowaniem scala, który daje mi taką składnię

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

object User with dsl.logging {

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

}

Moim zdaniem bardzo przydatne połączenie sprawdzonych w Javie frameworków logowania i fantazyjnej składni scali.

Alex Povar
źródło
@sschaef, och, tak. Przepraszam. Prawie zapomniałem. To była świetna walka z tym problemem przez kilka dni, ale wygląda na to, że jest to naprawdę niemożliwe. Użyłem #! ! Zamiast tego „wiadomość informacyjna”. Zmienię moją odpowiedź.
Alex Povar