All,
Apologies for resurrecting a dead thread with a big wall of text, but I
found these stackable traits really useful as a drop-in feature so I
upgraded them a bit.
This adds support become+unbecome and postReceive hooks.
Feel free to point out any problems you spot!
/**
* Allows an approach similar to aspects with Actors - invisibly rope in new
* behavior to all your actors by mixing in these traits and then using
* <code>wrappedReceive</code> instead of <code>receive</code>. Also
supports
* post receive calls for enabling behavior there
*
* <code>
* class MyActor extends Actor with Slf4jLogging {
* def wrappedReceive = {
* case x => {}
* }
* override def postReceive = {
* case x => {}
* }
* }
* </code>
* See https://groups.google.com/d/topic/akka-user/J4QTzSj5usQ/discussion
*/
trait ActorStack { this: Actor =>
/** Actor classes should implement this partialFunction for standard
actor message handling */
def wrappedReceive: Receive
/** (Optional) Actor classes can override this */
def postReceive: Receive = {
case x => {}
}
/** Stackable traits should override and call super.receiveWrapper() for
stacking functionality */
@inline
def receiveWrapper(x: Any, receive: Receive) = receive(x)
/** Stackable traits should override this and call super for stacking
this */
@inline
def postReceiveWrapper(x: Any, postreceive: Receive) = postreceive(x)
/** For logging MatchError exceptions */
private[this] val stackLog = LoggerFactory.getLogger(getClass)
private[this] val myPath = self.path.toString
def wrapReceive(receive: Receive = wrappedReceive, postreceive: Receive =
postReceive): Receive = {
case x: Any => try {
val result = receiveWrapper(x, receive)
postReceiveWrapper(x, postreceive)
result
} catch {
case nomatch: MatchError => {
// Because we claim we can handle x: Any, you'll occasionally see
these
// MatchError. I log them because I can, you could drop this code
and just
// invisible ignore them or you could remove the try{}catch{} and
have them
// printed in the standard exception handling manner
org.slf4j.MDC.put("akkaSource", myPath)
stackLog.info(s"Received unhandled message $x")
postReceiveWrapper(x, postreceive)
}
}
}
/** Setup default behavior */
def receive: Receive = wrapReceive()
}
Here is a simple actor to show off usage.
class TestActor extends Actor
with ActorLogging
with ActorStack
with Slf4jLoggingStack {
var count = -3
/** Drop default behavior into wrappedReceive */
def wrappedReceive = original
val original: Receive = {
case a: Int => {
log.debug(s"$count: original behavior")
count = count + a
if (count >= 0) {
log.debug("I am becoming")
/**
* This is the meat. You minimally need wrapReceive(updated),
* adding a different postReceive handler is optional
*/
context.become(wrapReceive(updated,postReceiveUpdated))
}
}
}
def updated: Receive = {
case a: Int => {
log.info(s"$count: Updated behavior")
count = count + a
if (count < 0) {
log.info("I am unbecoming")
context.unbecome
}
}
}
/**
* This is optional (note the override). postReceive handlers are
* passed the original message so you can match if needed
*/
override def postReceive: Receive = {
case x => {
log.info(s"$count: Post Message Processing")
}
}
/** Let's use a custom handler for our updated state */
def postReceiveUpdated: Receive = {
case y => {
log.info(s"$count: Updated Post Message Processing")
}
}
}
Ok, let's run that actor to see what happens:
val test = system.actorOf(Props(new TestActor()), name = "test")
for (i <- 1 to 5) {
test ! 1
Thread.sleep(1000) // Small delay to avoid logging threads getting
mixed
}
for (i <- 1 to 5) {
test ! -1
Thread.sleep(1000) // Small delay to avoid logging threads getting
mixed
}
test ! "chickens"
Here's some (cleaned up) log output. Note that I'm using both SLF4J here
and Akka's ActorLogging, so you can easily tell whihc messages are coming
from the traits and which messages are coming from the TestActor
INFO clasp.TestActor - Starting actor clasp.TestActor
INFO clasp.TestActor - Receiving 1
[DEBUG] [akka.tcp://[email protected]:2552/user/test] -3: original behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] -2: Post Message
Processing for 1
INFO clasp.TestActor - Receiving 1
[DEBUG] [akka.tcp://[email protected]:2552/user/test] -2: original behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] -1: Post Message
Processing for 1
INFO clasp.TestActor - Receiving 1
[DEBUG] [akka.tcp://[email protected]:2552/user/test] -1: original behavior
[DEBUG] [akka.tcp://[email protected]:2552/user/test] I am becoming
[INFO] [akka.tcp://[email protected]:2552/user/test] 0: Post Message
Processing for 1
INFO clasp.TestActor - Receiving 1
[INFO] [akka.tcp://[email protected]:2552/user/test] 0: Updated behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] 1: Updated Post Message
Processing for 1
INFO clasp.TestActor - Receiving 1
[INFO] [akka.tcp://[email protected]:2552/user/test] 1: Updated behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] 2: Updated Post Message
Processing for 1
INFO clasp.TestActor - Receiving -1
[INFO] [akka.tcp://[email protected]:2552/user/test] 2: Updated behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] 1: Updated Post Message
Processing for -1
INFO clasp.TestActor - Receiving -1
[INFO] [akka.tcp://[email protected]:2552/user/test] 1: Updated behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] 0: Updated Post Message
Processing for -1
INFO clasp.TestActor - Receiving -1
[INFO] [akka.tcp://[email protected]:2552/user/test] 0: Updated behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] I am unbecoming
[INFO] [akka.tcp://[email protected]:2552/user/test] -1: Updated Post
Message Processing for -1
INFO clasp.TestActor - Receiving -1
[DEBUG] [akka.tcp://[email protected]:2552/user/test] -1: original behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] -2: Post Message
Processing for -1
INFO clasp.TestActor - Receiving -1
[DEBUG] [akka.tcp://[email protected]:2552/user/test] -2: original behavior
[INFO] [akka.tcp://[email protected]:2552/user/test] -3: Post Message
Processing for -1
INFO clasp.TestActor - Receiving chickens
INFO clasp.TestActor - Received unhandled message chickens
[INFO] [akka.tcp://[email protected]:2552/user/test] -3: Post Message
Processing for chickens
Hopefully this helps someone else (and are no major mistakes :-P)
Best,
Hamilton
On Wednesday, December 19, 2012 2:33:20 PM UTC-5, Raymond Roestenburg wrote:
>
>
>
>
> On Wed, Dec 19, 2012 at 7:58 PM, Evan Chan <[email protected] <javascript:>>
> wrote:
>
>> Raymond,
>>
>> Thanks. I should have given credit to you for inspiring part of this, my
>> apologies. :)
>>
>
>
>> no worries, cool to hear :-)
>>
>
>> I also read your recent blog post on exponential backoff for retries.
>> Really good stuff.
>>
>> Thanks!
>
>
>> By the way, for the Spider pattern .... what are your thoughts on
>> crawling to find the message flows, vs. publishing traces to a central
>> collector, ie Google Dapper or Twitter Zipkin?
>>
>
> Depends on the use case of course, If you know in advance exactly what you
> want to trace/log and you just want to log then publishing is the (easiest
> and best) way to go.
> If you want to discover stuff in a more dynamic setup and query your
> system then crawling is a nice option, the stackable trait in there allows
> you to add some behavior as a cross cutting concern in your app. I haven't
> pushed this to its limits though.
> And definitely look at Martin's eventsourced lib it's really nice and uses
> stackable traits a lot :-)
>
>>
>> -Evan
>>
>>
>> On Wed, Dec 19, 2012 at 10:53 AM, Raymond Roestenburg <
>> [email protected] <javascript:>> wrote:
>>
>>> Hi Evan,
>>>
>>> I've blogged about something similar (but not exactly the same) in part
>>> here, maybe you will enjoy it:
>>>
>>>
>>> http://letitcrash.com/post/30585282971/discovering-message-flows-in-actor-systems-with-the
>>>
>>>
>>> On Wed, Dec 19, 2012 at 9:08 AM, Evan Chan <[email protected]
>>> <javascript:>> wrote:
>>>
>>>> Dear Hakkers,
>>>>
>>>> I don't know if this has been covered before, but I thought this was
>>>> too cool of a pattern not to share. Let's say I want to mix in behavior
>>>> to all my actors, such as logging, metrics collection, etc. I can start
>>>> with a base trait that enables "stackable" actor traits:
>>>>
>>>> trait ActorStack { this: Actor =>
>>>> /** Actor classes should implement this partialFunction for standard
>>>> actor message handling */
>>>> def wrappedReceive: Receive
>>>>
>>>> /** Stackable traits should override and call super.receiveWrapper()
>>>> for stacking functionality */
>>>> @inline
>>>> def receiveWrapper(x: Any) = { wrappedReceive(x) }
>>>>
>>>> def receive: Receive = {
>>>> case x: Any => receiveWrapper(x)
>>>> }
>>>> }
>>>>
>>>> Now, let's say that I want to add a trait for direct logging to SLF4J
>>>> (which has many benefits, but I don't want to get into that here).
>>>> Presto:
>>>>
>>>> trait Slf4jLogging extends Actor with ActorStack {
>>>> val logger = LoggerFactory.getLogger(getClass)
>>>> private[this] val myPath = self.path.toString
>>>>
>>>> logger.info("Starting actor " + getClass.getName)
>>>>
>>>> override def receiveWrapper(x: Any) {
>>>> // Because each actor receive invocation could happen in a
>>>> different thread, and MDC is thread-based,
>>>> // we kind of have to set the MDC anew for each receive invocation.
>>>> :(
>>>> org.slf4j.MDC.put("akkaSource", myPath)
>>>> super.receiveWrapper(x)
>>>> }
>>>> }
>>>>
>>>> The reason why I decided to use a receiveWrapper() (instead of
>>>> stacking Receive's with andThen / orElse ) is to enable code that could
>>>> potentially analyze the entire receive call, such as for metrics
>>>> collection:
>>>>
>>>> trait ActorMetrics extends Actor with ActorStack {
>>>> val metricReceiveTimer = Metrics.newTimer(getClass, "message-handler",
>>>> TimeUnit.MILLISECONDS,
>>>> TimeUnit.SECONDS)
>>>>
>>>> override def receiveWrapper(x: Any) {
>>>> val context = metricReceiveTimer.time()
>>>> try {
>>>> super.receiveWrapper(x)
>>>> } finally {
>>>> context.stop()
>>>> }
>>>> }
>>>> }
>>>>
>>>>
>>>> Now, you can compose all these traits together easily:
>>>>
>>>> abstract class MyInstrumentedActor extends Actor with Slf4jLogging with
>>>> ActorMetrics
>>>>
>>>> Pretty neat, huh?
>>>>
>>>> -Evan
>>>>
>>>> --
>>>> >>>>>>>>>> Read the docs: http://akka.io/docs/
>>>> >>>>>>>>>> Check the FAQ: http://akka.io/faq/
>>>> >>>>>>>>>> Search the archives:
>>>> https://groups.google.com/group/akka-user
>>>> ---
>>>> You received this message because you are subscribed to the Google
>>>> Groups "Akka User List" group.
>>>> To post to this group, send email to [email protected]
>>>> <javascript:>.
>>>> To unsubscribe from this group, send email to
>>>> [email protected] <javascript:>.
>>>> Visit this group at http://groups.google.com/group/akka-user?hl=en.
>>>>
>>>>
>>>>
>>>
>>>
>>>
>>> --
>>> Raymond Roestenburg
>>>
>>> code: http://github.com/RayRoestenburg
>>> blog: http://roestenburg.agilesquad.com
>>> twtr: @RayRoestenburg
>>> book: http://manning.com/roestenburg
>>>
>>> --
>>> >>>>>>>>>> Read the docs: http://akka.io/docs/
>>> >>>>>>>>>> Check the FAQ: http://akka.io/faq/
>>> >>>>>>>>>> Search the archives:
>>> https://groups.google.com/group/akka-user
>>> ---
>>> You received this message because you are subscribed to the Google
>>> Groups "Akka User List" group.
>>> To post to this group, send email to [email protected]
>>> <javascript:>.
>>> To unsubscribe from this group, send email to
>>> [email protected] <javascript:>.
>>> Visit this group at http://groups.google.com/group/akka-user?hl=en.
>>>
>>>
>>>
>>
>>
>>
>> --
>> --
>> *Evan Chan*
>> Senior Software Engineer |
>> [email protected] <javascript:> | (650) 996-4600
>> www.ooyala.com | blog <http://www.ooyala.com/blog> | @ooyala
>> <http://www.twitter.com/ooyala>
>>
>> --
>> >>>>>>>>>> Read the docs: http://akka.io/docs/
>> >>>>>>>>>> Check the FAQ: http://akka.io/faq/
>> >>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
>> ---
>> You received this message because you are subscribed to the Google Groups
>> "Akka User List" group.
>> To post to this group, send email to [email protected]
>> <javascript:>.
>> To unsubscribe from this group, send email to
>> [email protected] <javascript:>.
>> Visit this group at http://groups.google.com/group/akka-user?hl=en.
>>
>>
>>
>
>
>
> --
> Raymond Roestenburg
>
> code: http://github.com/RayRoestenburg
> blog: http://roestenburg.agilesquad.com
> twtr: @RayRoestenburg
> book: http://manning.com/roestenburg
>
>
--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ:
>>>>>>>>>> http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka
User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.