incorporate review feedback

- tons of documentation added
- lift extraction of logClass into LogSource type-class
- prefer Props.empty
This commit is contained in:
Roland 2012-01-13 13:50:42 +01:00
parent 7d0e006547
commit b01640fddb
9 changed files with 260 additions and 147 deletions

View file

@ -33,7 +33,7 @@ trait DeathWatchSpec { this: AkkaSpec with ImplicitSender with DefaultTimeout
}
"notify with one Terminated message when an Actor is stopped" in {
val terminal = system.actorOf(Props(context { case _ }))
val terminal = system.actorOf(Props.empty)
startWatching(terminal) ! "hallo"
expectMsg("hallo") // this ensures that the DaemonMsgWatch has been received before we send the PoisonPill
@ -43,7 +43,7 @@ trait DeathWatchSpec { this: AkkaSpec with ImplicitSender with DefaultTimeout
}
"notify with one Terminated message when an Actor is already dead" in {
val terminal = system.actorOf(Props(context { case _ }))
val terminal = system.actorOf(Props.empty)
terminal ! PoisonPill
@ -52,7 +52,7 @@ trait DeathWatchSpec { this: AkkaSpec with ImplicitSender with DefaultTimeout
}
"notify with all monitors with one Terminated message when an Actor is stopped" in {
val terminal = system.actorOf(Props(context { case _ }))
val terminal = system.actorOf(Props.empty)
val monitor1, monitor2, monitor3 = startWatching(terminal)
terminal ! PoisonPill
@ -67,7 +67,7 @@ trait DeathWatchSpec { this: AkkaSpec with ImplicitSender with DefaultTimeout
}
"notify with _current_ monitors with one Terminated message when an Actor is stopped" in {
val terminal = system.actorOf(Props(context { case _ }))
val terminal = system.actorOf(Props.empty)
val monitor1, monitor3 = startWatching(terminal)
val monitor2 = system.actorOf(Props(new Actor {
context.watch(terminal)

View file

@ -208,8 +208,9 @@ object ActorModelSpec {
await(deadline)(stats.restarts.get() == restarts)
} catch {
case e
system.eventStream.publish(Error(e, Option(dispatcher).toString,
if (dispatcher ne null) dispatcher.getClass else this.getClass,
system.eventStream.publish(Error(e,
Option(dispatcher).toString,
(Option(dispatcher) getOrElse this).getClass,
"actual: " + stats + ", required: InterceptorStats(susp=" + suspensions +
",res=" + resumes + ",reg=" + registers + ",unreg=" + unregisters +
",recv=" + msgsReceived + ",proc=" + msgsProcessed + ",restart=" + restarts))

View file

@ -59,7 +59,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd
}
val log = LoggingReceive("funky")(r)
log.isDefinedAt("hallo")
expectMsg(1 second, Logging.Debug("funky", classOf[String], "received unhandled message hallo"))
expectMsg(1 second, Logging.Debug("funky", classOf[DummyClassForStringSources], "received unhandled message hallo"))
}
}

View file

@ -168,15 +168,85 @@ trait LoggingBus extends ActorEventBus {
}
/**
* This trait defines the interface to be provided by a log source formatting
* rule as used by [[akka.event.Logging]]s `apply`/`create` method.
*
* See the companion object for default implementations.
*
* Example:
* {{{
* trait MyType { // as an example
* def name: String
* }
*
* implicit val myLogSourceType: LogSource[MyType] = new LogSource {
* 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 systems address:
* {{{
* trait MyType { // as an example
* def name: String
* }
*
* implicit val myLogSourceType: LogSource[MyType] = new LogSource {
* def genString(a: MyType) = a.name
* def genString(a: MyType, s: ActorSystem) = a.name + "," + s
* }
*
* class MyClass extends MyType {
* val sys = ActorSyste("sys")
* val log = Logging(sys, this) // will use "hallo,akka://sys" as logSource
* def name = "hallo"
* }
* }}}
*
* The default implementation of the second variant will just call the first.
*/
trait LogSource[-T] {
def genString(t: T): String
def genString(t: T, system: ActorSystem): String = genString(t)
def getClazz(t: T): Class[_] = t.getClass
}
/**
* This is a marker class which is inserted as originator class into
* [[akka.event.LogEvent]] when the string representation was supplied
* directly.
*/
class DummyClassForStringSources
/**
* This object holds predefined formatting rules for log sources.
*
* In case an [[akka.actor.ActorSystem]] is provided, the following apply:
* <ul>
* <li>[[akka.actor.Actor]] and [[akka.actor.ActorRef]] will be represented by their absolute physical path</li>
* <li>providing a `String` as source will append "(<system address>)" and use the result</li>
* <li>providing a `Class` will extract its simple name, append "(<system address>)" and use the result</li>
* <li>anything else gives compile error unless implicit [[akka.event.LogSource]] is in scope for it</li>
* </ul>
*
* In case a [[akka.event.LoggingBus]] is provided, the following apply:
* <ul>
* <li>[[akka.actor.Actor]] and [[akka.actor.ActorRef]] will be represented by their absolute physical path</li>
* <li>providing a `String` as source will be used as is</li>
* <li>providing a `Class` will extract its simple name</li>
* <li>anything else gives compile error unless implicit [[akka.event.LogSource]] is in scope for it</li>
* </ul>
*/
object LogSource {
implicit val fromString: LogSource[String] = new LogSource[String] {
def genString(s: String) = s
override def genString(s: String, system: ActorSystem) = s + "(" + system + ")"
override def getClazz(s: String) = classOf[DummyClassForStringSources]
}
implicit val fromActor: LogSource[Actor] = new LogSource[Actor] {
@ -191,29 +261,54 @@ object LogSource {
val fromClass: LogSource[Class[_]] = new LogSource[Class[_]] {
def genString(c: Class[_]) = simpleName(c)
override def genString(c: Class[_], system: ActorSystem) = simpleName(c) + "(" + system + ")"
override def getClazz(c: Class[_]) = c
}
implicit def fromAnyClass[T]: LogSource[Class[T]] = fromClass.asInstanceOf[LogSource[Class[T]]]
def apply[T: LogSource](o: T): String = implicitly[LogSource[T]].genString(o)
def apply[T: LogSource](o: T, system: ActorSystem): String = implicitly[LogSource[T]].genString(o, system)
def fromAnyRef(o: AnyRef): String =
o match {
case c: Class[_] fromClass.genString(c)
case a: Actor fromActor.genString(a)
case a: ActorRef fromActorRef.genString(a)
case s: String s
case x simpleName(x)
/**
* Convenience converter access: given an implicit `LogSource`, generate the
* string representation and originating class.
*/
def apply[T: LogSource](o: T): (String, Class[_]) = {
val ls = implicitly[LogSource[T]]
(ls.genString(o), ls.getClazz(o))
}
def fromAnyRef(o: AnyRef, system: ActorSystem): String =
/**
* Convenience converter access: given an implicit `LogSource` and
* [[akka.actor.ActorSystem]], generate the string representation and
* originating class.
*/
def apply[T: LogSource](o: T, system: ActorSystem): (String, Class[_]) = {
val ls = implicitly[LogSource[T]]
(ls.genString(o, system), ls.getClazz(o))
}
/**
* construct string representation for any object according to
* rules above with fallback to its `Class`s simple name.
*/
def fromAnyRef(o: AnyRef): (String, Class[_]) =
o match {
case c: Class[_] fromClass.genString(c, system)
case a: Actor fromActor.genString(a, system)
case a: ActorRef fromActorRef.genString(a, system)
case s: String fromString.genString(s, system)
case x simpleName(x) + "(" + system + ")"
case c: Class[_] apply(c)
case a: Actor apply(a)
case a: ActorRef apply(a)
case s: String apply(s)
case x (simpleName(x), x.getClass)
}
/**
* construct string representation for any object according to
* rules above (including the actor systems address) with fallback to its
* `Class`s simple name.
*/
def fromAnyRef(o: AnyRef, system: ActorSystem): (String, Class[_]) =
o match {
case c: Class[_] apply(c)
case a: Actor apply(a)
case a: ActorRef apply(a)
case s: String apply(s)
case x (simpleName(x) + "(" + system + ")", x.getClass)
}
}
@ -322,140 +417,79 @@ object Logging {
/**
* Obtain LoggingAdapter for the given actor system and source object. This
* will use the systems event stream.
* will use the systems event stream and include the systems address in the
* log source string.
*
* The source is used to identify the source of this logging channel and must have
* a corresponding implicit LogSource[T] instance in scope; by default these are
* provided for Class[_], Actor, ActorRef and String types. By these, the source
* object is translated to a String according to the following rules:
* <ul>
* <li>if it is an Actor or ActorRef, its path is used</li>
* <li>in case of a String it is used as is</li>
* <li>in case of a class an approximation of its simpleName
* <li>and in all other cases the simpleName of its class</li>
* </ul>
*
* You can add your own rules quite easily:
* <b>Do not use this if you want to supply a log category string (like
* com.example.app.whatever) unaltered,</b> supply `system.eventStream` in this
* case or use
*
* {{{
* trait MyType { // as an example
* def name: String
* }
*
* implicit val myLogSourceType: LogSource[MyType] = new LogSource {
* def genString(a: MyType) = a.name
* }
*
* class MyClass extends MyType {
* val log = Logging(eventStream, this) // will use "hallo" as logSource
* def name = "hallo"
* }
* Logging(system, this.getClass)
* }}}
*
* The source is used to identify the source of this logging channel and
* must have a corresponding implicit LogSource[T] instance in scope; by
* default these are provided for Class[_], Actor, ActorRef and String types.
* See the companion object of [[akka.event.LogSource]] for details.
*
* You can add your own rules quite easily, see [[akka.event.LogSource]].
*/
def apply[T: LogSource](system: ActorSystem, logSource: T): LoggingAdapter =
new BusLogging(system.eventStream, LogSource(logSource, system), logSource.getClass)
def apply[T: LogSource](system: ActorSystem, logSource: T): LoggingAdapter = {
val (str, clazz) = LogSource(logSource, system)
new BusLogging(system.eventStream, str, clazz)
}
/**
* Obtain LoggingAdapter for the given logging bus and source object.
*
* The source is used to identify the source of this logging channel and must have
* a corresponding implicit LogSource[T] instance in scope; by default these are
* provided for Class[_], Actor, ActorRef and String types. By these, the source
* object is translated to a String according to the following rules:
* <ul>
* <li>if it is an Actor or ActorRef, its path is used</li>
* <li>in case of a String it is used as is</li>
* <li>in case of a class an approximation of its simpleName
* <li>and in all other cases the simpleName of its class</li>
* </ul>
* The source is used to identify the source of this logging channel and
* must have a corresponding implicit LogSource[T] instance in scope; by
* default these are provided for Class[_], Actor, ActorRef and String types.
* See the companion object of [[akka.event.LogSource]] for details.
*
* You can add your own rules quite easily:
*
* {{{
* trait MyType { // as an example
* def name: String
* }
*
* implicit val myLogSourceType: LogSource[MyType] = new LogSource {
* def genString(a: MyType) = a.name
* }
*
* class MyClass extends MyType {
* val log = Logging(eventStream, this) // will use "hallo" as logSource
* def name = "hallo"
* }
* }}}
* You can add your own rules quite easily, see [[akka.event.LogSource]].
*/
def apply[T: LogSource](bus: LoggingBus, logSource: T): LoggingAdapter =
new BusLogging(bus, implicitly[LogSource[T]].genString(logSource), logSource.getClass)
def apply[T: LogSource](bus: LoggingBus, logSource: T): LoggingAdapter = {
val (str, clazz) = LogSource(logSource)
new BusLogging(bus, str, clazz)
}
/**
* Obtain LoggingAdapter for the given actor system and source object. This
* will use the systems event stream.
* will use the systems event stream and include the systems address in the
* log source string.
*
* The source is used to identify the source of this logging channel and must have
* a corresponding implicit LogSource[T] instance in scope; by default these are
* provided for Class[_], Actor, ActorRef and String types. By these, the source
* object is translated to a String according to the following rules:
* <ul>
* <li>if it is an Actor or ActorRef, its path is used</li>
* <li>in case of a String it is used as is</li>
* <li>in case of a class an approximation of its simpleName
* <li>and in all other cases the simpleName of its class</li>
* </ul>
*
* You can add your own rules quite easily:
* <b>Do not use this if you want to supply a log category string (like
* com.example.app.whatever) unaltered,</b> supply `system.eventStream` in this
* case or use
*
* {{{
* trait MyType { // as an example
* def name: String
* }
*
* implicit val myLogSourceType: LogSource[MyType] = new LogSource {
* def genString(a: MyType) = a.name
* }
*
* class MyClass extends MyType {
* val log = Logging(eventStream, this) // will use "hallo" as logSource
* def name = "hallo"
* }
* Logging.getLogger(system, this.getClass());
* }}}
*
* The source is used to identify the source of this logging channel and
* must have a corresponding implicit LogSource[T] instance in scope; by
* default these are provided for Class[_], Actor, ActorRef and String types.
* See the companion object of [[akka.event.LogSource]] for details.
*/
def getLogger(system: ActorSystem, logSource: AnyRef): LoggingAdapter = apply(system, LogSource.fromAnyRef(logSource, system))
def getLogger(system: ActorSystem, logSource: AnyRef): LoggingAdapter = {
val (str, clazz) = LogSource.fromAnyRef(logSource, system)
new BusLogging(system.eventStream, str, clazz)
}
/**
* Obtain LoggingAdapter for the given logging bus and source object. This
* will use the systems event stream.
* Obtain LoggingAdapter for the given logging bus and source object.
*
* The source is used to identify the source of this logging channel and must have
* a corresponding implicit LogSource[T] instance in scope; by default these are
* provided for Class[_], Actor, ActorRef and String types. By these, the source
* object is translated to a String according to the following rules:
* <ul>
* <li>if it is an Actor or ActorRef, its path is used</li>
* <li>in case of a String it is used as is</li>
* <li>in case of a class an approximation of its simpleName
* <li>and in all other cases the simpleName of its class</li>
* </ul>
*
* You can add your own rules quite easily:
*
* {{{
* trait MyType { // as an example
* def name: String
* }
*
* implicit val myLogSourceType: LogSource[MyType] = new LogSource {
* def genString(a: MyType) = a.name
* }
*
* class MyClass extends MyType {
* val log = Logging(eventStream, this) // will use "hallo" as logSource
* def name = "hallo"
* }
* }}}
* The source is used to identify the source of this logging channel and
* must have a corresponding implicit LogSource[T] instance in scope; by
* default these are provided for Class[_], Actor, ActorRef and String types.
* See the companion object of [[akka.event.LogSource]] for details.
*/
//def getLogger(bus: LoggingBus, logSource: AnyRef): LoggingAdapter = apply(bus, LogSource.fromAnyRef(logSource))
def getLogger(bus: LoggingBus, logSource: AnyRef): LoggingAdapter = {
val (str, clazz) = LogSource.fromAnyRef(logSource)
new BusLogging(bus, str, clazz)
}
/**
* Artificial exception injected into Error events if no Throwable is

View file

@ -36,7 +36,8 @@ object LoggingReceive {
class LoggingReceive(source: AnyRef, r: Receive)(implicit system: ActorSystem) extends Receive {
def isDefinedAt(o: Any) = {
val handled = r.isDefinedAt(o)
system.eventStream.publish(Debug(LogSource.fromAnyRef(source), source.getClass, "received " + (if (handled) "handled" else "unhandled") + " message " + o))
val (str, clazz) = LogSource.fromAnyRef(source)
system.eventStream.publish(Debug(str, clazz, "received " + (if (handled) "handled" else "unhandled") + " message " + o))
handled
}
def apply(o: Any): Unit = r(o)

View file

@ -17,8 +17,13 @@ as illustrated in this example:
.. includecode:: code/akka/docs/event/LoggingDocTestBase.java
:include: imports,my-actor
The second parameter to the ``Logging.getLogger`` is the source of this logging channel.
The source object is translated to a String according to the following rules:
The first parameter to ``Logging.getLogger`` could also be any
:class:`LoggingBus`, specifically ``system.eventStream()``; in the demonstrated
case, the actor systems address is included in the ``akkaSource``
representation of the log source (see `Logging Thread and Akka Source in MDC`_)
while in the second case this is not automatically done. The second parameter
to ``Logging.getLogger`` is the source of this logging channel. The source
object is translated to a String according to the following rules:
* if it is an Actor or ActorRef, its path is used
* in case of a String it is used as is
@ -28,6 +33,13 @@ The source object is translated to a String according to the following rules:
The log message may contain argument placeholders ``{}``, which will be substituted if the log level
is enabled.
The Java :class:`Class` of the log source is also included in the generated
:class:`LogEvent`. In case of a simple string this is replaced with a “marker”
class :class:`akka.event.DummyClassForStringSources` in order to allow special
treatment of this case, e.g. in the SLF4J event listener which will then use
the string instead of the class name for looking up the logger instance to
use.
Event Handler
=============
@ -96,6 +108,12 @@ With Logback the thread name is available with ``%X{sourceThread}`` specifier wi
</layout>
</appender>
.. note::
It will probably be a good idea to use the ``sourceThread`` MDC value also in
non-Akka parts of the application in order to have this property consistently
available in the logs.
Another helpful facility is that Akka captures the actors address when
instantiating a logger within it, meaning that the full instance identification
is available for associating log messages e.g. with members of a router. This

View file

@ -47,6 +47,24 @@ object LoggingDocSpec {
}
//#my-event-listener
//#my-source
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)
}
//#my-source
}
class LoggingDocSpec extends AkkaSpec {

View file

@ -22,6 +22,8 @@ For convenience you can mixin the ``log`` member into actors, instead of definin
.. code-block:: scala
class MyActor extends Actor with akka.actor.ActorLogging {
...
}
The second parameter to the ``Logging`` is the source of this logging channel.
The source object is translated to a String according to the following rules:
@ -29,17 +31,46 @@ The source object is translated to a String according to the following rules:
* if it is an Actor or ActorRef, its path is used
* in case of a String it is used as is
* in case of a class an approximation of its simpleName
* and in all other cases the simpleName of its class
* and in all other cases a compile error occurs unless and implicit
:class:`LogSource[T]` is in scope for the type in question.
The log message may contain argument placeholders ``{}``, which will be substituted if the log level
is enabled.
Translating Log Source to String and Class
------------------------------------------
The rules for translating the source object to the source string and class
which are inserted into the :class:`LogEvent` during runtime are implemented
using implicit parameters and thus fully customizable: simply create your own
instance of :class:`LogSource[T]` and have it in scope when creating the
logger.
.. includecode:: code/akka/docs/event/LoggingDocSpec.scala#my-source
This example creates a log source which mimics traditional usage of Java
loggers, which are based upon the originating objects class name as log
category. The override of :meth:`getClazz` is only included for demonstration
purposes as it contains exactly the default behavior.
.. note::
You may also create the string representation up front and pass that in as
the log source, but be aware that then the :class:`Class[_]` which will be
put in the :class:`LogEvent` is
:class:`akka.event.DummyClassForStringSources`.
The SLF4J event listener treats this case specially (using the actual string
to look up the logger instance to use instead of the class name), and you
might want to do this also in case you implement your own loggin adapter.
Event Handler
=============
Logging is performed asynchronously through an event bus. You can configure which event handlers that should
subscribe to the logging events. That is done using the 'event-handlers' element in the :ref:`configuration`.
Here you can also define the log level.
Logging is performed asynchronously through an event bus. You can configure
which event handlers that should subscribe to the logging events. That is done
using the ``event-handlers`` element in the :ref:`configuration`. Here you can
also define the log level.
.. code-block:: ruby
@ -50,7 +81,8 @@ Here you can also define the log level.
loglevel = "DEBUG"
}
The default one logs to STDOUT and is registered by default. It is not intended to be used for production. There is also an :ref:`slf4j-scala`
The default one logs to STDOUT and is registered by default. It is not intended
to be used for production. There is also an :ref:`slf4j-scala`
event handler available in the 'akka-slf4j' module.
Example of creating a listener:
@ -58,7 +90,6 @@ Example of creating a listener:
.. includecode:: code/akka/docs/event/LoggingDocSpec.scala
:include: my-event-listener
.. _slf4j-scala:
SLF4J
@ -98,6 +129,12 @@ With Logback the thread name is available with ``%X{sourceThread}`` specifier wi
</layout>
</appender>
.. note::
It will probably be a good idea to use the ``sourceThread`` MDC value also in
non-Akka parts of the application in order to have this property consistently
available in the logs.
Another helpful facility is that Akka captures the actors address when
instantiating a logger within it, meaning that the full instance identification
is available for associating log messages e.g. with members of a router. This

View file

@ -8,6 +8,7 @@ import org.slf4j.{ Logger ⇒ SLFLogger, LoggerFactory ⇒ SLFLoggerFactory }
import org.slf4j.MDC
import akka.event.Logging._
import akka.actor._
import akka.event.DummyClassForStringSources
/**
* Base trait for all classes that wants to be able use the SLF4J logging infrastructure.
@ -19,7 +20,10 @@ trait SLF4JLogging {
object Logger {
def apply(logger: String): SLFLogger = SLFLoggerFactory getLogger logger
def apply(logClass: Class[_]): SLFLogger = SLFLoggerFactory getLogger logClass
def apply(logClass: Class[_], logSource: String): SLFLogger = logClass match {
case c if c == classOf[DummyClassForStringSources] apply(logSource)
case _ SLFLoggerFactory getLogger logClass
}
def root: SLFLogger = apply(SLFLogger.ROOT_LOGGER_NAME)
}
@ -39,24 +43,24 @@ class Slf4jEventHandler extends Actor with SLF4JLogging {
case event @ Error(cause, logSource, logClass, message)
withMdc(logSource, event.thread.getName) {
cause match {
case Error.NoCause Logger(logClass).error(message.toString)
case _ Logger(logClass).error(message.toString, cause)
case Error.NoCause Logger(logClass, logSource).error(message.toString)
case _ Logger(logClass, logSource).error(message.toString, cause)
}
}
case event @ Warning(logSource, logClass, message)
withMdc(logSource, event.thread.getName) {
Logger(logClass).warn("{}", message.asInstanceOf[AnyRef])
Logger(logClass, logSource).warn("{}", message.asInstanceOf[AnyRef])
}
case event @ Info(logSource, logClass, message)
withMdc(logSource, event.thread.getName) {
Logger(logClass).info("{}", message.asInstanceOf[AnyRef])
Logger(logClass, logSource).info("{}", message.asInstanceOf[AnyRef])
}
case event @ Debug(logSource, logClass, message)
withMdc(logSource, event.thread.getName) {
Logger(logClass).debug("{}", message.asInstanceOf[AnyRef])
Logger(logClass, logSource).debug("{}", message.asInstanceOf[AnyRef])
}
case InitializeLogger(_)