diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeadLetterSuspensionSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeadLetterSuspensionSpec.scala index 1c4d5838a6..91b367ac14 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeadLetterSuspensionSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeadLetterSuspensionSpec.scala @@ -21,11 +21,21 @@ object DeadLetterSuspensionSpec { context.system.eventStream.publish(Dropped(n, "Don't like numbers", self)) } } + + object Unandled { + def props(): Props = Props(new Unandled) + } + + class Unandled extends Actor { + override def receive: Receive = { + case n: Int => unhandled(n) + } + } } class DeadLetterSuspensionSpec extends AkkaSpec(""" akka.loglevel = INFO - akka.log-dead-letters = 3 + akka.log-dead-letters = 4 akka.log-dead-letters-suspend-duration = 2s """) with ImplicitSender { import DeadLetterSuspensionSpec._ @@ -36,6 +46,7 @@ class DeadLetterSuspensionSpec extends AkkaSpec(""" expectTerminated(deadActor) private val droppingActor = system.actorOf(Dropping.props(), "droppingActor") + private val unhandledActor = system.actorOf(Unandled.props(), "unhandledActor") private def expectedDeadLettersLogMessage(count: Int): String = s"Message [java.lang.Integer] from $testActor to $deadActor was not delivered. [$count] dead letters encountered" @@ -43,6 +54,9 @@ class DeadLetterSuspensionSpec extends AkkaSpec(""" private def expectedDroppedLogMessage(count: Int): String = s"Message [java.lang.Integer] to $droppingActor was dropped. Don't like numbers. [$count] dead letters encountered" + private def expectedUnhandledLogMessage(count: Int): String = + s"Message [java.lang.Integer] from $testActor to $unhandledActor was unhandled. [$count] dead letters encountered" + "must suspend dead-letters logging when reaching 'akka.log-dead-letters', and then re-enable" in { EventFilter.info(start = expectedDeadLettersLogMessage(1), occurrences = 1).intercept { deadActor ! 1 @@ -50,27 +64,30 @@ class DeadLetterSuspensionSpec extends AkkaSpec(""" EventFilter.info(start = expectedDroppedLogMessage(2), occurrences = 1).intercept { droppingActor ! 2 } + EventFilter.info(start = expectedUnhandledLogMessage(3), occurrences = 1).intercept { + unhandledActor ! 3 + } EventFilter - .info(start = expectedDeadLettersLogMessage(3) + ", no more dead letters will be logged in next", occurrences = 1) + .info(start = expectedDeadLettersLogMessage(4) + ", no more dead letters will be logged in next", occurrences = 1) .intercept { - deadActor ! 3 + deadActor ! 4 } - deadActor ! 4 - droppingActor ! 5 + deadActor ! 5 + droppingActor ! 6 // let suspend-duration elapse Thread.sleep(2050) // re-enabled EventFilter - .info(start = expectedDeadLettersLogMessage(6) + ", of which 2 were not logged", occurrences = 1) + .info(start = expectedDeadLettersLogMessage(7) + ", of which 2 were not logged", occurrences = 1) .intercept { - deadActor ! 6 + deadActor ! 7 } // reset count EventFilter.info(start = expectedDeadLettersLogMessage(1), occurrences = 1).intercept { - deadActor ! 7 + deadActor ! 8 } } diff --git a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala index f01139de27..0a9a279068 100644 --- a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala @@ -26,6 +26,7 @@ object EventStreamSpec { stdout-loglevel = WARNING loglevel = WARNING actor.debug.unhandled = on + log-dead-letters = off } """) diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf index 968e32b19c..31f3e3c176 100644 --- a/akka-actor/src/main/resources/reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -47,7 +47,8 @@ akka { # This is useful when you are uncertain of what configuration is used. log-config-on-start = off - # Log at info level when messages are sent to dead letters. + # Log at info level when messages are sent to dead letters, or published to + # eventStream as `DeadLetter`, `Dropped` or `UnhandledMessage`. # Possible values: # on: all dead letters are logged # off: no logging of dead letters diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index aeb93e95bd..18c6001a78 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -289,6 +289,7 @@ final case class UnhandledMessage( @BeanProperty recipient: ActorRef) extends NoSerializationVerificationNeeded with WrappedMessage + with AllDeadLetters /** * Classes for passing status back to the sender. diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 24ea086b3c..83c9ff47a3 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -7,10 +7,11 @@ package akka.actor import java.util.concurrent.ConcurrentHashMap import akka.annotation.InternalApi - import scala.annotation.tailrec import scala.collection.immutable import scala.util.control.NonFatal + +import akka.annotation.DoNotInherit import akka.dispatch._ import akka.dispatch.sysmsg._ import akka.event.AddressTerminatedTopic @@ -478,8 +479,11 @@ private[akka] trait MinimalActorRef extends InternalActorRef with LocalRef { /** * Subscribe to this class to be notified about all [[DeadLetter]] (also the suppressed ones) * and [[Dropped]]. + * + * Not for user extension */ -sealed trait AllDeadLetters extends WrappedMessage { +@DoNotInherit +trait AllDeadLetters extends WrappedMessage { def message: Any def sender: ActorRef def recipient: ActorRef diff --git a/akka-actor/src/main/scala/akka/event/DeadLetterListener.scala b/akka-actor/src/main/scala/akka/event/DeadLetterListener.scala index fb7649b409..0455a5f11d 100644 --- a/akka-actor/src/main/scala/akka/event/DeadLetterListener.scala +++ b/akka-actor/src/main/scala/akka/event/DeadLetterListener.scala @@ -15,6 +15,7 @@ import akka.actor.DeadLetter import akka.actor.DeadLetterActorRef import akka.actor.DeadLetterSuppression import akka.actor.Dropped +import akka.actor.UnhandledMessage import akka.actor.WrappedMessage import akka.event.Logging.Info import akka.util.PrettyDuration._ @@ -29,6 +30,7 @@ class DeadLetterListener extends Actor { override def preStart(): Unit = { eventStream.subscribe(self, classOf[DeadLetter]) eventStream.subscribe(self, classOf[Dropped]) + eventStream.subscribe(self, classOf[UnhandledMessage]) } // don't re-subscribe, skip call to preStart @@ -115,6 +117,10 @@ class DeadLetterListener extends Actor { val destination = if (isReal(d.recipient)) s" to ${d.recipient}" else "" s"Message [$messageStr]$wrappedIn$origin$destination was dropped. ${dropped.reason}. " + s"[$count] dead letters encountered$doneMsg. " + case _: UnhandledMessage => + val destination = if (isReal(d.recipient)) s" to ${d.recipient}" else "" + s"Message [$messageStr]$wrappedIn$origin$destination was unhandled. " + + s"[$count] dead letters encountered$doneMsg. " case _ => s"Message [$messageStr]$wrappedIn$origin to ${d.recipient} was not delivered. " + s"[$count] dead letters encountered$doneMsg. " + diff --git a/akka-actor/src/main/scala/akka/event/Logging.scala b/akka-actor/src/main/scala/akka/event/Logging.scala index c2703d7929..5cd62aab21 100644 --- a/akka-actor/src/main/scala/akka/event/Logging.scala +++ b/akka-actor/src/main/scala/akka/event/Logging.scala @@ -1637,6 +1637,8 @@ class LogMarker(val name: String, val properties: Map[String, Any]) { import akka.util.ccompat.JavaConverters._ properties.map { case (k, v) => (k, v.asInstanceOf[AnyRef]) }.asJava } + + override def toString: String = s"LogMarker($name,$properties)" } object LogMarker {