Konfiguracja fabryki loggerów z biblioteki slogging w Scali

W poście Biblioteki do logowania dla języka Scala skonfigurowałem logger slogging. Zapomniałem tylko wybrać fabrykę loggerów. Bez tego logger w ogóle nie działa.

Przy okazji, tu rodzi się pytanie-zagadka “jak przetestować loggera w testach jednostkowych”?

Rozwiązanie uniwersalne

Uniwersalną fabryką loggerów jest PrintLoggerFactory. Działa ona dla wszystkich platform docelowych. Jej działanie jest oparte na metodzie/funkcji/procedurerze println.

Do pliku build.sbt dodajemy zależność i ustawiamy wersję biblioteki :

val SloggingVersion = "0.6.1"
// ...
val SharedSettings = Seq(
  // ...
  libraryDependencies ++= Seq(
    "biz.enef" %%% "slogging" % SloggingVersion,
  ),
)  

A w pliku Main.scala ustawiamy PrintLoggerFactory jako implementację:

package pl.writeonly.re.main

import pl.writeonly.re.shared.core.Core
import slogging._

object Main extends App {
  LoggerConfig.factory = PrintLoggerFactory()

  LoggerConfig.level = LogLevel.TRACE
  Core.apply("Goodbye to the World")
}

Wszystko działa i można by zakończyć na tym artykuł. Zwłaszcza jeśli dockeryzujemy swoją aplikację i kto inny zajmuje się zapisywaniem logów do plików i baz danych. Jeśli jednak nasza aplikacja ma działać poza dockerem przydatne mogą być bardziej zaawanasowane funkcjonalności loggera.

Rozwiązania dedykowane dla platformy

Aktualnie język Scala można kompilować skrośnie (ang. cross compiler) na trzy platformy JVM, JS i Native Każda z platform posiada inne możliwości konfiguracyjne loggera.

Scala JVM

Dla platformy JVM nie ma dużego wyboru. Jest jedna dedykowana fabryka SLF4JLoggerFactory. Jest ona opakowaniem slf4j, dzięki czemu możemy wybrać dowolny logger implementujący tą fasadę. Ja wybrałem logback.

Do pliku build.sbt dodajemy zależności:

val jvmSettings = Seq(
  // ...
  libraryDependencies ++= Seq(
    "biz.enef" %% "slogging-slf4j" % SloggingVersion,
    "ch.qos.logback" % "logback-classic" % "1.2.3",
  ),
)

A w pliku Main.scala ustawiamy PrintLoggerFactory jako implementację:

package pl.writeonly.re.main

import pl.writeonly.re.shared.core.Core
import slogging._

object Main extends App {
  LoggerConfig.factory = SLF4JLoggerFactory()

  LoggerConfig.level = LogLevel.TRACE
  Core.apply("JVM")
}

I uruchamiamy projekt w Scala JVM za pomocą polecenia:

sbt clean reJVM/run

Warto przeczytać jeszcze sposób konfiguracji loggera logback.

Scala.js

Jeśli używamy transpilatora Scala.js to mamy do wyboru trzy możliwości :

  • ConsoleLoggerFactory - podobny do PrintLoggerFactory, ale używa console.log() zamiast println().
  • WinstonLoggerFactory - opakowanie wokół biblioteki do logowania winston dla Node.js.
  • HttpLoggerFactory - ten backend wysyła komunikaty dziennika do serwera HTTP za pośrednictwem żądań POST Ajax. Należy zauważyć, że ta sama zasada pochodzenia zazwyczaj wymaga, aby serwer, do którego wysyłane są komunikaty, był tym samym serwerem, z którego załadowano źródło javascript.

Ja chcę zbudować aplikację konsolową CLI, więc wybierałem ConsoleLoggerFactory.

Do pliku build.sbt dodajemy… nic:

val jsSettings = Seq(
  // ...
//  scalaJSModuleKind := ModuleKind.CommonJSModule,
  libraryDependencies ++= Seq(
//    "biz.enef" %%% "slogging-winston" % SloggingVersion,
//    "biz.enef" %%% "slogging-http" % SloggingVersion,
  ),
)

Gdybyśmy wybrali WinstonLoggerFactory musielibyśmy:

  • dodać "biz.enef" %%% "slogging-winston" % SloggingVersion do zależności
  • ustawić scalaJSModuleKind := ModuleKind.CommonJSModule

Gdybyśmy wybrali HttpLoggerFactory musielibyśmy dodać "biz.enef" %%% "slogging-http" % SloggingVersion do zależności.

A w pliku Main.scala ustawiamy ConsoleLoggerFactory jako implementację:

package pl.writeonly.re.main

import pl.writeonly.re.shared.core.Core
import slogging._

object Main extends JSApp {
  override def main(): Unit = {
    LoggerConfig.factory = ConsoleLoggerFactory()

    LoggerConfig.level = LogLevel.TRACE
    Core.apply("JS")
  }
}

I uruchamiamy projekt w Scala.js za pomocą polecenia:

sbt clean reJS/run

Scala Native

Jeśli używamy kompilatora Scala Native to mamy do wyboru trzy możliwości :

  • TerminalLoggerFactory - rejestruje wszystkie komunikaty na stderr za pomocą fprintf(). Komunikaty są zawarte w kodach kontrolnych terminala ANSI, które można skonfigurować oddzielnie dla każdego poziomu. Dzięki czemu możemy mieć tęczowe logi.
  • GLibLoggerFactory - ten backend używa GLib’s g_log() do rejestrowania wiadomości, np. do użytku z skalan-gtk.
  • SyslogLoggerFactory - ten backend używa standardowego narzędzia syslog.

Ja chcę zbudować aplikację konsolową CLI, więc wybierałem TerminalLoggerFactory.

W pliku build.sbt dodajemy… nic:

val nativeSettings = Seq(
  // ...
  //  nativeLinkingOptions += "-lglib-2.0",
  libraryDependencies ++= Seq(
  //    "biz.enef" %%% "slogging-glib" % SloggingVersion,
  //    "biz.enef" %%% "slogging-syslog" % SloggingVersion,
  ),
)

Gdybyśmy wybrali GLibLoggerFactory to musielibyśmy:

  • dodać do zależności "biz.enef" %%% "slogging-glib" % SloggingVersion,
  • ustawiamu opcję kompilatora nativeLinkStubs := true

Gdybyśmy wybrali SyslogLoggerFactory musielibyśmy dodać "biz.enef" %%% "slogging-syslog" % SloggingVersion do zależności.

A w pliku Main.scala ustawiamy TerminalLoggerFactory jako implementację:

package pl.writeonly.re.main

import pl.writeonly.re.shared.core._
import slogging._
import slogging.TerminalLoggerFactory.TerminalControlCode

object Main extends App {
  LoggerConfig.factory = TerminalLoggerFactory()
  TerminalLoggerFactory.infoCode = TerminalControlCode.green
  TerminalLoggerFactory.debugCode = TerminalControlCode.cyan
  TerminalLoggerFactory.traceCode = TerminalControlCode.blue

  LoggerConfig.level = LogLevel.TRACE
  Core.apply("Native")

  StrictLoggingCore.rainbow()
}

Implementacja metody StrictLoggingCore.rainbow() to:

  def rainbow(): Unit = {
    logger.error("rainbow error")
    logger.warn("rainbow warn")
    logger.info("rainbow info")
    logger.debug("rainbow debug")
    logger.trace("rainbow trace")
  }

Uruchamiamy projekt w Scala Native za pomocą polecenia:

sbt clean re/run

I oglądamy tęczę XD

Podsumowanie i wnioski

Kod jest dostępny pod adresem resentiment. Co do wniosków to:

  • warto czytać dokumentację
  • trudno testować logowanie