AKKA的日志: slf4j,logback和其他

作为一个“搞kernel的”,对日志的理解不过是printk的EMERG,INFO,DEBUG等各种level,关键时刻还是得dump内存,上gcc单步跟踪。但在到处是异步并发,远程分布式通信的jdk世界,日志成了定位问题最重要甚至是唯一的手段。在akka上尤为如此。

akka日志的官方文档http://doc.akka.io/docs/akka/current/scala/logging.html

akka日志功能是基于slf4j构建的。对于不熟悉java的人,slf4j,log4j,logback等基本上是这样一个关系:SLF4J是一套log接口,java.util.logging, logback, log4j等是具体的实现,而logback已逐渐取代log4j成为事实标准。

所以要使用akka的日志,除了akka-slf4j还需增加logback依赖。

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-actor" % akkaVersion,
  "com.typesafe.akka" %% "akka-contrib" % akkaVersion,
  "com.typesafe.akka" %% "akka-testkit" % akkaVersion,
  "com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
  "org.scalatest" %% "scalatest" % "2.2.4" % "test",
  "ch.qos.logback" % "logback-classic" % "1.1.3",
  "commons-io" % "commons-io" % "2.4" % "test")

actor系统中记录日志的三种方法

1 通过ActorLogging记录日志

akka提供了ActorLogging这个trait,方便在actor中记录日志。

class MasterActor extends Actor with akka.actor.ActorLogging{
  def receive = {
    case _ =>
     log.debug("debug log")
     log.info("info log")
     log.warning("warning log")
     log.error("error log")
  }
}

2 通过akka.event.Logging记录日志 

ActorLogging这个trait只能mix到Actor类上。

在其他非actor类上,如果能访问到actor系统,可利用它的event stream进行log。

import akka.event.Logging
val log = Logging(system.eventStream, "log prefix:")
log.debug("debug log")

也可直接使用ActorSystem内置的LoggingAdapter。

val system = akka.actor.ActorSystem()
system.log.error("log from ActorSystem")

3 直接通过slf4j访问logback记录日志

import org.slf4j.LoggerFactory
val log = LoggerFactory.getLogger(getClass)
log.debug("Hello Logger!")

这3种log方式因为用的都是同一个logback实例,所以输出是统一的,区别是akka提供的log接口能够在记录时自动带上actor地址等信息,能极大的方便定位问题。

logback的配置

上面说了半天好像和printk的level级别没有太大差别,java的log系统最强大的地方在于它的可配置性。

如下面这个logback配置,可把ERROR及以上级别的打印输出到akka.log文件,同时把DEBUG及以上级别的打印输出到控制台。还可以配置输出格式,自动在log内容上附带一些上下文信息,如%X{akkaSource}这个变量会解析为发起log的具体actor地址,这在系统上百万actor并发时,将成为跟踪问题重要线索。如果输出到日志文件,还可控制文件体积的最大值,选择原有内容是追加还是覆盖。

<configuration>
        <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>akka.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <append>false</append>
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <!--<pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>-->
            <pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
        </encoder>
    </appender>

        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
            <!--<pattern>%X{akkaTimestamp} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>-->
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

日志的每种输出方式对应一个appender,除了上面用到的ConsoleAppender和FileAppender,还有通过网络发送日志到远程日志服务器的Appender,用户也可自定义appender,如日志云服务提供商loggly就有自己的Appender。分布式消息系统kafka也能通过Appender直接在某个topic上接收log日志。

更多语法参考:http://logback.qos.ch/manual/appenders.html

最后推荐一篇LinkedIn工程师关于日志的长文The Log: What every software engineer should know about real-time data’s unifying abstraction,当然他说的日志已经超出了简单记录调试信息的范畴了,但其中一些观点很有意思,如数据库其实是一种特殊形式的日志,按照这个思路所谓大数据其实就是如何翻日志了,日志将是整个系统最重要的资产。

相关代码已提交github, 欢迎fork交流。