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


前にfluentd + scalaでロギングするときのライブラリについて解説しました。

詳細は以下をご覧下さい。

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

上記の記事ではfluent-logger-scalaを推奨しました。このライブラリについてもう少し書いて行きます。

任意のオブジェクトをloggingしようとしてもできない例

FluentLoggerのlog関数の引数はMap[String, Any]なので、何でも詰め込めます。mapでなく、単一objectを取る関数もAnyなので、何でも渡せます。

ですが、実際には何でも渡して正しく機能する訳ではありません。

例えば、以下のように例外を渡しても・・・

logger.log("myLabel", "key1", new IllegalStateException("oops"))

実際にはこのように何も表示されません。

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

Loggableを使ってloggingする

ではどうするかというと、FluentLoggerにはLoggableというtraitが用意されています。これを使います。

Loggable traitを実装するということはmapへ変換できるようにする、という意味です。つまり結局はMapで扱う、と認識しておけば良いと思います。

例えばこういう感じのコードを用意しておいて、

import java.io.{StringWriter, PrintWriter}

import org.fluentd.logger.scala.{Loggable, FluentLoggerFactory}

object Main extends App {
  implicit def toLoggable(t:Throwable): Loggable = new Loggable {
    override def toRecord: Map[String, Any] = {

      val sw = new StringWriter()
      val pw = new PrintWriter(sw)
      t.printStackTrace(pw)
      pw.flush()
      val stackTrace = sw.toString

      Map(
        "message" -> t.getMessage,
        "cause" -> (if(t.getCause != null) toLoggable(t.getCause).toRecord else null),
        "stackTrace" -> stackTrace
      )
    }
  }

  val logger = FluentLoggerFactory.getLogger("myTag", "localhost", 24224)

  logger.log("myLabel", new IllegalStateException("oops", new IllegalStateException("caused")))
}

実行すると以下のような感じでログ出力されます。

2015-02-01 12:30:40 +0900 myTag.myLabel: {"message":"oops","cause":{"message":"caused","cause":null,"stackTrace":"java.lang.IllegalStateException: caused\n\tat sample.Main$.delayedEndpoint$sample$Main$1(Main.scala:27)\n\tat sample.Main$delayedInit$body.apply(Main.scala:7)\n\tat scala.Function0$class.apply$mcV$sp(Function0.scala:40)\n\tat scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.collection.immutable.List.foreach(List.scala:381)\n\tat scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)\n\tat scala.App$class.main(App.scala:76)\n\tat sample.Main$.main(Main.scala:7)\n\tat sample.Main.main(Main.scala)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:483)\n\tat com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)\n"},"stackTrace":"java.lang.IllegalStateException: oops\n\tat sample.Main$.delayedEndpoint$sample$Main$1(Main.scala:27)\n\tat sample.Main$delayedInit$body.apply(Main.scala:7)\n\tat scala.Function0$class.apply$mcV$sp(Function0.scala:40)\n\tat scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.collection.immutable.List.foreach(List.scala:381)\n\tat scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)\n\tat scala.App$class.main(App.scala:76)\n\tat sample.Main$.main(Main.scala:7)\n\tat sample.Main.main(Main.scala)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:483)\n\tat com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)\nCaused by: java.lang.IllegalStateException: caused\n\t... 16 more\n"}

(良くわからないので、整形します)

{
  "message": "oops",
  "cause": {
    "message": "caused",
    "cause": null,
    "stackTrace": "java.lang.IllegalStateException: caused\n\tat sample.Main$.delayedEndpoint$sample$Main$1(Main.scala:27)\n\tat sample.Main$delayedInit$body.apply(Main.scala:7)\n\tat scala.Function0$class.apply$mcV$sp(Function0.scala:40)\n\tat scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.collection.immutable.List.foreach(List.scala:381)\n\tat scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)\n\tat scala.App$class.main(App.scala:76)\n\tat sample.Main$.main(Main.scala:7)\n\tat sample.Main.main(Main.scala)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:483)\n\tat com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)\n"
  },
  "stackTrace": "java.lang.IllegalStateException: oops\n\tat sample.Main$.delayedEndpoint$sample$Main$1(Main.scala:27)\n\tat sample.Main$delayedInit$body.apply(Main.scala:7)\n\tat scala.Function0$class.apply$mcV$sp(Function0.scala:40)\n\tat scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.App$$anonfun$main$1.apply(App.scala:76)\n\tat scala.collection.immutable.List.foreach(List.scala:381)\n\tat scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)\n\tat scala.App$class.main(App.scala:76)\n\tat sample.Main$.main(Main.scala:7)\n\tat sample.Main.main(Main.scala)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:483)\n\tat com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)\nCaused by: java.lang.IllegalStateException: caused\n\t... 16 more\n"
}

こんな感じでExceptionのメッセージやstack traceを保持したままloggingできました。

解説は特にすることありませんが、一応補足します。

logger.logの部分で例外を渡していますが、これはimplicit conversionによってLoggableへ変換されます。これによりloggingが可能な状態になります。冒頭で定義されているimplicit conversionはThrowableを受け取って愚直にMapに変換するLoggable objectを返しています。

こんな感じで必要に応じてLoggableを作れば何でもロギングできるようになります。

意外と簡単なので皆さんやってみると良いかと思います。