typesafe系のログ出力ライブラリを見かけたので使ってみる。名前はscala-logging。Scala2.11から利用可能らしい。
内容的にはよくあるSLF4Jのラッパーだけど、Scalaのマクロ機能を利用しており
logger.debug(s"Some $expensive message!")
と書くだけで
if (logger.isDebugEnabled) logger.debug(s"Some $expensive message!")
的な意味合いに変換してくれるのが売りらしい。マクロ便利。
build.sbtにdepencencyを設定。
libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % "3.1.0",
"ch.qos.logback" % "logback-classic" % "1.1.3"
)
scala-loggingの依存にslf4jは入っている。scala-loggingに加えてlogbackかlog4jを入れれば済む。
ここではlogbackを選択。
classpathの通ってるところにlogback.xmlファイルを作って下記を記述。
せっかくなので実用的っぽい記述を貼っておく。
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> utEncoder -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.log.%d{yyyy-MM-dd}.%i</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>1MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
意味的には、DEBUG以上なら標準出力して、INFO以上ならファイル出力。ファイルは日付でrotate、ログファイルサイズが1MB以上になった時もrotate。という設定。
普段はだいたいこれくらいの設定で動かしてるのだけど、一般的にはどうなのだろう。
まずは自前でloggerを生成してログ出力してみる。
import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
object Example extends App {
val logger = Logger(LoggerFactory.getLogger("logger_name"))
logger.debug("foo")
logger.info("bar")
logger.warn("baz")
}
これでfoo, bar, bazが標準出力され、bar, bazがログファイルに出力される。
scala-loggingの中にはLazyLoggingというtraitが入っていて、中にはprotected lazy val loggerという変数が設定されている。
これを利用すると下記のようにextendsするだけでログ出力が可能。
class Foo extends LazyLogging {
logger.info("foo")
}
これまで自前で似たようなtraitを使ってログ出力していたので、これがデフォルトで用意されているのはありがたい。
LazyLoggingの他にも、遅延評価しない版のStrictLoggigというクラスもある。
詳細はgithubの当該コードを参照。
実際にどういったコードの変換されているのか、マクロを見て確認しておく。
下記はLoggerクラスのinfoのところ。
def info(message: String): Unit = macro LoggerMacro.infoMessage
同パッケージ内にあるLoggerMacroのinfoMessageを呼び出している。
type LoggerContext = Context { type PrefixType = Logger }
def infoMessage(c: LoggerContext)(message: c.Expr[String]) = {
import c.universe._
val underlying = q"${c.prefix}.underlying"
q"if ($underlying.isInfoEnabled) $underlying.info($message)"
}
ちゃんとif(logger.isInfoEnabled)が加えられていることがわかる。