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を作れば何でもロギングできるようになります。
意外と簡単なので皆さんやってみると良いかと思います。