`
bljoohe
  • 浏览: 1649 次
  • 性别: Icon_minigender_1
  • 来自: 广州
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

(java)如何用Actor记录日志

阅读更多
如何记录日志
创建一个 LoggingAdapter 并使用它的 error, warning, info, 或 debug 方法, 如下例:

import akka.event.Logging

class MyActor extends Actor {
  val log = Logging(context.system, this)
  override def preStart() = {
    log.debug("Starting")
  }
  override def preRestart(reason: Throwable, message: Option[Any]) {
    log.error(reason, "Restarting due to [{}] when processing [{}]",
      reason.getMessage, message.getOrElse(""))
  }
  def receive = {
    case "test" ? log.info("Received test")
    case x      ? log.warning("Received unknown message: {}", x)
  }
}
为了方便你可以向actor中混入 log 成员, 而不是象上例一下定义它.

class MyActor extends Actor with akka.actor.ActorLogging {
  ...
}
Logging 的第二个参数是这个日志通道的源. 这个源对象以下面的规则转换成字符串:

如果它是Actor或ActorRef,则使用它的路径
如果是String,就使用它自己
如果是类,则使用它的simpleName
其它的类型,而且当前作用域中又没有隐式的 LogSource[T] 实例则会导致编译错误.
日志消息可以包含参数占位符 {}, 如果该日志级别被打开,则占位符会被替换。如果给出的参数数量多过 占位符的数量,则在日志语句的结尾将被追加一个警告 (i.e. 在同一级别的同一行)。你可以传入一个Java数组来为多个占位符提供数据:

val args = Array("The", "brown", "fox", "jumps", 42)
system.log.debug("five parameters: {}, {}, {}, {}, {}", args)
日志源的 Java Class 也会被包含在生成的 LogEvent中. 如果是简单的字符串,它会被替换成一个 “标志” 类 akka.event.DummyClassForStringSources 以便对这种情况作特殊处理,例如 在SLF4j事件监听器中会使用字符串而不是类名来查找要用到的日志记录器实例。

辅助的日志选项
Akka 为非常底层的debug提供了一组配置选项,这些主要是为akka的开发者所用,而非普通用户。

你一定要将日志级别设为 DEBUG 来使用这些选项:

akka {
  loglevel = DEBUG
}
下面这个配置选项在你想知道Akka装载了哪些配置设置时非常有用:

akka {
  # 在actor系统启动时以INFO级别记录完整的配置
  # 当你不确定使用的是哪个配置时有用
  log-config-on-start = on
}
如果你希望记录所有被使用akka.event.LoggingReceive的Actor处理的用户级消息的细节:

akka {
  debug {
    # 打开 LoggingReceive 功能, 以DEBUG级别记录所有接收到的消息
    receive = on
  }
}
如果你希望记录Actor所处理的所有自动接收的消息的细节:

akka {
  debug {
    # 为所有的 AutoReceiveMessages(Kill, PoisonPill 之类) 打开DEBUG日志
    autoreceive = on
  }
}
如果你希望记录Actor的所有生命周期变化 (重启,死亡等 ) 的细节:

akka {
  debug {
    # 打开actor生命周期变化的DEBUG日志
    lifecycle = on
  }
}
如果你希望记录所有继承了LoggingFSM的FSM actor的事件、状态转换和计时器的细节:

akka {
  debug {
    # 打开所有 LoggingFSMs 事件、状态转换和计时器的DEBUG日志
    fsm = on
  }
}
如果你希望监控对 ActorSystem.eventStream 的订阅/取消订阅:

akka {
  debug {
    # 打开eventStream上订阅关系变化的DEBUG日志
    event-stream = on
  }
}
辅助的远程日志选项
如果你希望以DEBUG级别查看所有远程发送的消息: (这些日志是被传输层发送时所记录,而非actor)

akka {
  remote {
    # 如果打开这个选项,Akka将以DEBUG级别记录所有发出的消息, 不打开则不记录
    log-sent-messages = on
  }
}
如果你希望以DEBUG级别查看接收到的所有远程消息: (这些日志是被传输层接收时所记录,而非actor)

akka {
  remote {
    # 如果打开这个选项,Akka将以DEBUG级别记录所有接收到的消息, 不打开则不记录
    log-received-messages = on
  }
}
同时参阅 TestKit 的日志选项: 跟踪Actor调用.

将日志源转换为字符串和类
在运行时将源对象转换成要插入 LogEvent 的源字符串和类的规则是使用隐式参数的方式实现的, 因此是可配置的: 只需要创建你自己的 LogSource[T] 实例并将它放在创建logger的作用域中即可。

import akka.event.LogSource
import akka.actor.ActorSystem

object MyType {
  implicit val logSource: LogSource[AnyRef] = new LogSource[AnyRef] {
    def genString(o: AnyRef): String = o.getClass.getName
    override def getClazz(o: AnyRef): Class[_] = o.getClass
  }
}

class MyType(system: ActorSystem) {
  import MyType._
  import akka.event.Logging

  val log = Logging(system, this)
}
这个例子创建了一个日志源来模拟Java logger的传统用法,该日志源使用原对象的类名作为日志类别。 在这里加入对 getClazz 的重写只是为了作说明,因为它精确地包含了缺省行为。

Note
你也可以先创建字符串然后将它作为日志源传入,但要知道这时 LogEvent 中的 Class[_] 将是 akka.event.DummyClassForStringSources.

SLF4J 事件监听器对这种情况会特殊处理 (使用实际的字符串来查找logger实例而不是类名), 你在实现自己的日志适配器时可能也会这么做。

事件处理器
日志记录是通过一个事件总线异步地完成的. 你可以配置要为哪些事件处理器订阅日志事件。通过 Configuration 中的event-handlers 。 你还可以在这里定义日志级别。

akka {
  # 在启动时注册的事件处理器 (Logging$DefaultLogger 记录日志到标准输出)
  event-handlers = ["akka.event.Logging$DefaultLogger"]
  # 可选: ERROR, WARNING, INFO, DEBUG
  loglevel = "DEBUG"
}
缺省会注册一个事件处理器,它将日志记录到标准输出. 在生产系统中不建议使用它。 在 ‘akka-slf4j’ 模块中还有一个 SLF4J 事件处理器。

创建监听器的示例:

import akka.event.Logging.InitializeLogger
import akka.event.Logging.LoggerInitialized
import akka.event.Logging.Error
import akka.event.Logging.Warning
import akka.event.Logging.Info
import akka.event.Logging.Debug

class MyEventListener extends Actor {
  def receive = {
    case InitializeLogger(_)                        ? sender ! LoggerInitialized
    case Error(cause, logSource, logClass, message) ? // ...
    case Warning(logSource, logClass, message)      ? // ...
    case Info(logSource, logClass, message)         ? // ...
    case Debug(logSource, logClass, message)        ? // ...
  }
}
SLF4J
Akka 为 SL4FJ提供了一个事件处理器. 它在 ‘akka-slf4j.jar’ 模块中. 它的唯一依赖是 slf4j-api 包. 在运行时,你还需要一个 SLF4J 后端, 我们推荐 Logback:

lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.0" % "runtime"
你需要打开 Configuration 的‘event-handlers’中的 Slf4jEventHandler。 你还可以在这里定义事件总线的日志级别。更细粒度的日志级别可以在SLF4j后端的配置(例如:logback.xml)中定义。

akka {
  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
  loglevel = "DEBUG"
}
SLF4J 为每个日志事件选择日志记录器是基于创建LoggingAdapter时的日志源的 Class[_], 如果日志源是一个字符串,那么就使用这个串 (i.e. LoggerFactory.getLogger(c: Class[_]) 用于前者而 LoggerFactory.getLogger(s: String) 用于后者).

注意
如果创建LoggingAdapteractor时向工厂方法提供了一个ActorSystem,那么该Acotr系统的名字 将被加在 字符串 日志源的后面。 如果不希望这样,只要象下面这样传一个 LoggingBus :

val log = Logging(system.eventStream, "my.nice.string")
日志线程与MDC中的Akka源
日志的记录是异步的,完成日志记录的线程被保存在 Mapped Diagnostic Context (MDC) 的 sourceThread 属性里. 在 Logback 的模式配置中线程名可以通过 %X{sourceThread} 指定:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>
  </encoder>
</appender>
Note
在应用程序的非Akka的部分也建议使用MDC的 sourceThread 使得这个值在日志里保持一致。

另一个有用的工具是 Akka 的创建logger实例时会捕捉 actor 的地址, 这意味着可以访问到整个actor实例的身份信息来将日志信息与其它信息进行关联(例如:路由的成员)。 这个信息保存在MDC的 akkaSource属性中:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
  </encoder>
</appender>
要了解这个属性所包括的内容(也适用于非actor)的细节见 如何记录日志.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics