使用 akka 进行日志记录。应用程序设计
Logging with akka. app design
假设我有基于 Futures
、scala.concurrent
的 scala 应用程序来处理异步/并发(到目前为止还没有使用参与者)。
在很多地方,我使用 log4j 将内容记录到日志文件中。
因为它是 I/O 我想我可以通过将日志消息发送到 LoggingActor
来提高性能
像这样:
def stuffTodo(arg:String)(implicit ex:ExecutionContext) : Future[Result] = {
// important performant work
// ..
logAcrot ! LogMessage("message-1")
// ...
}
其中消息:case class LogMessage(msg:String, implicit ex:ExecutionContext)
然后在 ActorLog
def receive = {
case LogMessage(msg:String, ex:ExecutionContext) ⇒ {log.info(msg + ex)}
}
我见过其他方法基本上 wrap scala.concurent.ExecutionContext
(使用当前线程)并使用 Mapped Diagnostic Context-magic (log4j) 并通过将线程 ID 附加到日志消息。但最终它 blocks/slows 下降 thread/execution (据我所知)并使应用程序变慢。
在这种情况下,对于这个 Actor,日志记录同时保持独立/异步和顺序。
走这条路好吗?分享经验故事? pros/cons/concerns?在尝试重负载之前询问..
看起来工程过度了。
我会选择 logback,并实现自定义异步 Appender,如果我会这样做的话,所以我不会更改我的代码,如果我决定不停止。
否则日志库是个不错的选择,我会尝试调整它以获得更好的性能,并依靠其他人实现此功能的经验。
Akka 已经很好地支持日志记录,这在页面 Logging - Akka Documentation 上有记录。我认为没有必要或不需要在您的系统中创建记录器参与者,尤其是当此机制已经存在时。
您可能已经使用了异步执行日志记录的 LoggingAdaptor
class,它通过将其发布到事件总线上来实现。您应该能够在 actor 中或外部使用相同的机制。
记录混合
Scala 有一个用于 actor 的 mixin,它为 actor 创建记录器并允许设置 MDC。来自文档:
import Logging.MDC
final case class Req(work: String, visitorId: Int)
class MdcActorMixin extends Actor with akka.actor.DiagnosticActorLogging {
var reqId = 0
override def mdc(currentMessage: Any): MDC = {
reqId += 1
val always = Map("requestId" -> reqId)
val perMessage = currentMessage match {
case r: Req => Map("visitorId" -> r.visitorId)
case _ => Map()
}
always ++ perMessage
}
def receive: Receive = {
case r: Req => {
log.info(s"Starting new request: ${r.work}")
}
}
}
在 actor 外部登录
Logging.scala
文件中有两个在 actor 外部创建 LoggingSource 的示例:
trait MyType { // as an example
def name: String
}
implicit val myLogSourceType: LogSource[MyType] = new LogSource[MyType] {
def genString(a: MyType) = a.name
}
class MyClass extends MyType {
val log = Logging(eventStream, this) // will use "hallo" as logSource
def name = "hallo"
}
The second variant is used for including the actor system’s address:
trait MyType { // as an example
def name: String
}
implicit val myLogSourceType: LogSource[MyType] = new LogSource[MyType] {
def genString(a: MyType) = a.name
def genString(a: MyType, s: ActorSystem) = a.name + "," + s
}
class MyClass extends MyType {
val sys = ActorSystem("sys")
val log = Logging(sys, this) // will use "hallo,akka://sys" as logSource
def name = "hallo"
}
如果需要,可以在日志记录适配器上设置 MDC。
记录线程和 MDC
当日志记录由另一个线程异步执行时,文档还涵盖了您关于日志记录线程的观点。
Since the logging is done asynchronously the thread in which the
logging was performed is captured in Mapped Diagnostic Context (MDC)
with attribute name sourceThread. With Logback the thread name is
available with %X{sourceThread} specifier within the pattern layout
configuration.
假设我有基于 Futures
、scala.concurrent
的 scala 应用程序来处理异步/并发(到目前为止还没有使用参与者)。
在很多地方,我使用 log4j 将内容记录到日志文件中。 因为它是 I/O 我想我可以通过将日志消息发送到 LoggingActor
来提高性能像这样:
def stuffTodo(arg:String)(implicit ex:ExecutionContext) : Future[Result] = {
// important performant work
// ..
logAcrot ! LogMessage("message-1")
// ...
}
其中消息:case class LogMessage(msg:String, implicit ex:ExecutionContext)
然后在 ActorLog
def receive = {
case LogMessage(msg:String, ex:ExecutionContext) ⇒ {log.info(msg + ex)}
}
我见过其他方法基本上 wrap scala.concurent.ExecutionContext
(使用当前线程)并使用 Mapped Diagnostic Context-magic (log4j) 并通过将线程 ID 附加到日志消息。但最终它 blocks/slows 下降 thread/execution (据我所知)并使应用程序变慢。
在这种情况下,对于这个 Actor,日志记录同时保持独立/异步和顺序。
走这条路好吗?分享经验故事? pros/cons/concerns?在尝试重负载之前询问..
看起来工程过度了。
我会选择 logback,并实现自定义异步 Appender,如果我会这样做的话,所以我不会更改我的代码,如果我决定不停止。
否则日志库是个不错的选择,我会尝试调整它以获得更好的性能,并依靠其他人实现此功能的经验。
Akka 已经很好地支持日志记录,这在页面 Logging - Akka Documentation 上有记录。我认为没有必要或不需要在您的系统中创建记录器参与者,尤其是当此机制已经存在时。
您可能已经使用了异步执行日志记录的 LoggingAdaptor
class,它通过将其发布到事件总线上来实现。您应该能够在 actor 中或外部使用相同的机制。
记录混合
Scala 有一个用于 actor 的 mixin,它为 actor 创建记录器并允许设置 MDC。来自文档:
import Logging.MDC
final case class Req(work: String, visitorId: Int)
class MdcActorMixin extends Actor with akka.actor.DiagnosticActorLogging {
var reqId = 0
override def mdc(currentMessage: Any): MDC = {
reqId += 1
val always = Map("requestId" -> reqId)
val perMessage = currentMessage match {
case r: Req => Map("visitorId" -> r.visitorId)
case _ => Map()
}
always ++ perMessage
}
def receive: Receive = {
case r: Req => {
log.info(s"Starting new request: ${r.work}")
}
}
}
在 actor 外部登录
Logging.scala
文件中有两个在 actor 外部创建 LoggingSource 的示例:
trait MyType { // as an example
def name: String
}
implicit val myLogSourceType: LogSource[MyType] = new LogSource[MyType] {
def genString(a: MyType) = a.name
}
class MyClass extends MyType {
val log = Logging(eventStream, this) // will use "hallo" as logSource
def name = "hallo"
}
The second variant is used for including the actor system’s address:
trait MyType { // as an example
def name: String
}
implicit val myLogSourceType: LogSource[MyType] = new LogSource[MyType] {
def genString(a: MyType) = a.name
def genString(a: MyType, s: ActorSystem) = a.name + "," + s
}
class MyClass extends MyType {
val sys = ActorSystem("sys")
val log = Logging(sys, this) // will use "hallo,akka://sys" as logSource
def name = "hallo"
}
如果需要,可以在日志记录适配器上设置 MDC。
记录线程和 MDC
当日志记录由另一个线程异步执行时,文档还涵盖了您关于日志记录线程的观点。
Since the logging is done asynchronously the thread in which the logging was performed is captured in Mapped Diagnostic Context (MDC) with attribute name sourceThread. With Logback the thread name is available with %X{sourceThread} specifier within the pattern layout configuration.