つかびーの技術日記

(情報)工学修士, 元SIer SE, 現Web系 SEの技術blogです。Scala, Java, JS, TS, Python, Ruby, AWS, GCPあたりが好きです。

Scalaでfluentd loggingするときのライブラリ比較

      2015/02/03

Scalaプログラム内でfluentdを使ったロギング処理を書きたい場合についての話です。

fluentdなので、わざわざプログラム内部でロギングせずとも、他の一般的な方法(例えばlog4j)でファイルにログ出力して、それをfluentdのtailプラグインで拾う・・・という手法が最も楽だと思います。

勿論これでも良いですが、折角ならばより柔軟にロギングしたいと思うかもしれません。

そんなときはライブラリを使いましょう。

ライブラリの候補

  1. https://github.com/InnovaCo/fluentd-scalaLogbackのappenderを経由してfluentdへログ出力できます。Logbackのappenderなので、Logbackと同じI/Fという利点があります。
  2. https://github.com/oza/fluent-logger-scala内部でfluent-logger-javaを利用したライブラリです。独自のLoggerクラスを使って単一の文字列やMapをログ出力します。独自のクラスを使うので、Logbackなどと親和性が無い点は辛いですが、その分自由度は高いです。
    ozaさんのリポジトリとなっていますが、公式ライブラリのようです。
  3. https://github.com/sndyuk/logback-more-appendersLogbackのappenderを経由してfluentdへログ出力できます。1と同じ感じです。
  4. https://github.com/fluent/fluent-logger-javaJava用のライブラリです。公式。

実行例など(fluentd-scala)

インストールはREADMEに従ってsbtに依存性を書きます。logback.xmlも用意して、以下のようなプログラムを実行します。

import org.slf4j.LoggerFactory

object Main extends App {
  val logger = LoggerFactory.getLogger(getClass)

  logger.info("hello")
}

これだけでOKです。実行結果はこんな感じでfluentdにlog出力されます。

2015-02-01 12:30:40 +0900 my.project: {"level":"INFO","logger":"sample.Main$","host":"myhostname","timemillis":"1422775654604","thread":"fluentd-logger-akka.actor.default-dispatcher-4","message":"hello"}

見づらいのでjson部分のみ整形すると・・・

{
  "level":"INFO",
  "logger":"sample.Main$",
  "host":"myhostname",
  "timemillis":"1422775654604",
  "thread":"fluentd-logger-akka.actor.default-dispatcher-4",
  "message":"hello"
}

こんな感じです。

例外の場合も見てみます。

{
  "level": "ERROR",
  "logger": "sample.Main$",
  "throwable": "java.lang.IllegalStateException: oops\n\tat sample.Main$.delayedEndpoint$sample$Main$1(Main.scala:8) [classes/:na]\n\tat sample.Main$delayedInit$body.apply(Main.scala:5) [classes/:na]\n\tat scala.Function0$class.apply$mcV$sp(Function0.scala:40) [scala-library-2.11.5.jar:na]\n\tat scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) [scala-library-2.11.5.jar:na]\n\tat scala.App$$anonfun$main$1.apply(App.scala:76) [scala-library-2.11.5.jar:na]\n\tat scala.App$$anonfun$main$1.apply(App.scala:76) [scala-library-2.11.5.jar:na]\n\tat scala.collection.immutable.List.foreach(List.scala:381) [scala-library-2.11.5.jar:na]\n\tat scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35) [scala-library-2.11.5.jar:na]\n\tat scala.App$class.main(App.scala:76) [scala-library-2.11.5.jar:na]\n\tat sample.Main$.main(Main.scala:5) [classes/:na]\n\tat sample.Main.main(Main.scala) [classes/:na]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_25]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_25]\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]\n\tat java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_25]\n\tat com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) [idea_rt.jar:na]\n",
  "host": "myhostname",
  "timemillis": "1422776273022",
  "thread": "fluentd-logger-akka.actor.default-dispatcher-3",
  "message": "error occurred!"
}

throwableが追加されています。

非常に簡単に使えて、既存のloggerの置き換え等にも対応できるというメリットがあります。反面、Logger.infoなどに任意のobjectを与えられないため、微妙に使いづらいです。

実行例など(fluentd-logger-scala)

これも同様にインストールはREADMEを参考にしてください。こちらはlogbackとは関係ないので、sbtの依存性と以下のコードだけで動きます。

import org.fluentd.logger.scala.FluentLoggerFactory

object Main extends App {
  val logger = FluentLoggerFactory.getLogger("myTag", "localhost", 24224)
  
  logger.log("myLabel", "myKey", "myValue")
}

実行結果はこちら。

2015-02-01 12:30:40 +0900 myTag.myLabel: {"myKey":"myValue"}

先ほどの例と比べると簡素ですが、その分柔軟性があります。

第二引数にはMapが使えるので、例えば以下のようなコードを書くと

logger.log("myLabel", Map(
    "key1" -> "val1",
    "key2" -> "val2",
    "nestedMap" -> Map("nestkey1" -> "nestval1")
  ))

こんな風にネストした形で、正しいjsonで出力されます。

2015-02-01 12:30:40 +0900 myTag.myLabel: {"key1":"val1","key2":"val2","nestedMap":{"nestkey1":"nestval1"}}

logback-more-appendersについて

これも結局はlogback経由なので、初めのfluentd-scalaと同じです。用意されているappenderを使うだけです。ちなみにJavaライブラリです。

fluent-logger-javaについて

これもfluent-logger-scalaと同じです、というよりはfluent-logger-scalaの中身です。なので、やっていることは変わりません。

結局どれを使えばいいの?

オススメはfluent-logger-scalaですが、上記のようにメリットデメリットがあります。

  • fluentd-scala
    なるべく楽したい人、logbackのI/F(Logger class)で統一したい人向け
  • fluent-logger-scala
    柔軟に色々出力したい人、自分でlogのjsonの各fieldをコントロールしたい人、とにかく何でもloggingしたい人向け

ちなみに自分はfluent-logger-scalaを利用しています。かなりいい感じです。

みなさんも使ってみてはいかがでしょうか。

以下の記事でfluent-logger-scalaをもう少し解説しています。

fluent-logger-scalaで任意のobjectをlogging可能にする方法

 - Scala, ライブラリ , , ,