この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
12/16にLEGO Groupの初めてのOSS ライブラリとしてリリースされたScalaのロギングライブラリwoofを眺めてみました。このタイミングでリリースするのすごいな。
We are very proud to announce the LEGO Group's first OSS library release — Woof https://t.co/JPrzx7JRoZ, a small Scala 3 logging library ?https://t.co/99KhEgrfUS
Do we have any #Scala users out there? ?— LEGO Engineering (@LEGOEngineering) December 16, 2021
woofの特徴
リポジトリでも紹介されていますが、以下が特徴です。
- Scala 3のライブラリ(コードが完全に3系で記述されている)
- Cats Effectを使っている
- マクロを使って呼び出し側の情報を出力する(リフレクションなし)
- Scalaのコードで設定が可能
使ってみる
基本的な使い方はREADMEに例があるのでそちらを参照してもらうとしてREADMEでさらっと流されているいくつかのAPIを調べてみます。
Output
例で最初に定義されているのはOutputです。ログの実際の出力はOutputに委譲されるようです。
trait Output[F[_]]:
def output(str: String): F[Unit]
def outputError(str: String): F[Unit]
end Output
object Output:
def fromConsole[F[_]: Console]: Output[F] = new Output[F]:
def output(str: String): F[Unit] = Console[F].println(str)
def outputError(str: String): F[Unit] = Console[F].errorln(str)
end Output
コンパニオンオブジェクトにファクトリがあるので試してみます。
//Output by cats effect Console[F]
val outputFromConsole = Output.fromConsole[IO]
def run: cats.effect.IO[Unit] = for
given Logger[IO] <- Logger.makeIoLogger(outputFromConsole)
_ <- program.withLogContext("trace-id", "4d334544-6462-43fa-b0b1-12846f871573")
_ <- Logger[IO].info("Now the context is gone")
yield ()
これはErrorレベル以上で標準エラー出力が使われます。
Filter
Filterはログイベント(LogLine)を出力するかどうか決定する述語で、レベルやクラス名でフィルタリングするビルダーがプリセットされています。
type Filter = LogLine => Boolean
object Filter:
val atLeastLevel: LogLevel => Filter = level => line => line.level >= level
val exactLevel: LogLevel => Filter = level => line => line.level == level
val regexFilter: Regex => Filter = regex => line => regex.matches(line.info.enclosingClass)
val nothing: Filter = _ => false
val everything: Filter = _ => true
given Monoid[Filter] with
def empty: Filter = nothing
def combine(f: Filter, g: Filter): Filter = f or g
end Filter
extension (f: Filter)
infix def and(g: Filter): Filter = line => f(line) && g(line)
infix def or(g: Filter): Filter = line => f(line) || g(line)
フィルターの合成を試してみます。
given Filter = Filter.exactLevel(LogLevel.Info) or Filter.exactLevel(LogLevel.Error)
// 上記と同じ; Monoidを使う場合
import org.legogroup.woof.Filter.given
given Filter = Filter.exactLevel(LogLevel.Info) |+| Filter.exactLevel(LogLevel.Error)
2021-12-20 22:19:12 [INFO ] trace-id=4d334544-6462-43fa-b0b1-12846f871573 Main$: HEY! (Main.scala:26)
2021-12-20 22:19:12 [INFO ] Main$: Now the context is gone (Main.scala:20)
2021-12-20 22:19:12 [ERROR] trace-id=4d334544-6462-43fa-b0b1-12846f871573 Main$: I give up (Main.scala:28)
Printer
ログのフォーマットを決めるのがPrinterです。
trait Printer:
def toPrint(
epochMillis: EpochMillis,
level: LogLevel,
info: LogInfo,
message: String,
context: List[(String, String)],
): String
end Printer
例で使われているNoColorPrinterの他にColorPrinterがあります。
given Filter = Filter.everything
given Printer = ColorPrinter()
まとめ
woofをざっと眺めてみました。Scala 3で簡潔に書かれていて拡張も簡単にできそうな印象を受けました。これからもpure Scala 3なライブラリが出てくると思うので楽しみです。