How to log internal actor state in receive? - scala

For Actors that can be expressed fairly concisely, it's frustrating to have to add in blocks ({...}) just so I can add a log command. I would like to log my internal state before the message is handled and then after the message is handled - is this possible?
def receive = {
// I want to log here instead and remove the non-critical logs from below
// e.g. log.debug(s"Received $message")
// log.debug(s"Internal state is $subscriptions")
case RegisterChannel(name, owner) => {
getChannel(name) match {
case Some(deadChannel: DeadChannel) => {
subscriptions += (RealChannel(name, Server(owner)) -> subscriptions(deadChannel))
subscriptions -= deadChannel
context.watch(owner)
log.debug(s"Replaced $deadChannel with a real channel $channels")
}
case Some(realChannel: RealChannel) =>
log.error(s"Refusing to use RegisterChannel($name, $owner) due to $realChannel")
case None => {
subscriptions += (RealChannel(name, Server(owner)) -> Vector())
context.watch(owner)
log.debug(s"Registered a new channel $channels")
}
}
}
case Terminated(dead) => {
getRole(dead) match {
case Some(client: Client) => // Remove subscriptions
log.debug(s"Received Client Terminated($dead) $client")
subscriptionsFor(client).foreach { subscription =>
subscriptions += (subscription._1 -> subscription._2.filterNot(c => c == client))
}
case Some(server: Server) => { // Remove any channels
log.debug(s"Received Server Terminated($dead) $server")
channelsBy(server).foreach { realChannel =>
subscriptions += (DeadChannel(realChannel.name) -> subscriptions(realChannel))
subscriptions -= realChannel
}
}
case None =>
log.debug(s"Received Terminated($dead) but no channel is registered")
}
}
// I want to log here as well, to see what effect the message had
// e.g. log.debug(s"Finished $message")
// log.debug(s"Internal state is now $subscriptions")
}
I'm not sure if this is an Akka-specific or Scala pattern-matching specific question, so I tagged both
EDIT: After trying #aepurniet 's answer, I have no idea how to solve the compiler error. receive needs to return PartialFunction[Any,Unit], but when match is not the only item in the => {...} it seems to be returning Any=>AnyRef
// Compiler error because msg=>{...} is not proper type
def receive = msg => {
log.info(s"some log")
msg match {
case RegisterChannel(name, owner) => {
getChannel(name) match {
<snip>

received = { case ... } is actually shorthand for received = msg => msg match { case ... }. you can rewrite that receive = msg => { log.info(..); msg match { case ... } } you may have to additionally specify types.

There is akka.event.LoggingReceive that you can use like this:
def receive = LoggingReceive {
case ...
}
Then you set akka.actor.debug.receive to on and this will log (to DEBUG) all message that were received and whether they were handled or not.
See Tracing Actor Invocations section in the official documentation of Akka.
For the additional state logging, you can do something similar to LoggingReceive
def withStateLogging(handler: Receive): Receive = {
case msg if handler.isDefinedAt(msg) ⇒
log.debug("before actual receive")
log.debug(s"received: $msg")
log.debug(s"state: $state")
handler(msg)
log.debug("after actual receive")
log.debug(s"finished: $msg")
log.debug(s"state: $state")
}
def receive = withStateLogging {
case ...
}

Compiler complains because Actor#receive return type is Receive which is actually defined as
type Receive = PartialFunction[Any, Unit]
Here is nice example how your problem can be solved with stackable traits: how to add logging function in sending and receiving action in akka
It is a little tricky and overrides default behavior of PartialFunction.

Related

Handle Future with transform

I have a telegram bot on Scala and I want to send the image to the user if it exists and the message "Sorry, the image doesn't exist" if it's not. And I have a function getImage(tag), which returns Future.successful(link) or Future.failed(NoImageException(msg)).
onCommand("/img") { implicit msg =>
val tag = msg.text.get.drop("/img ".length)
try {
if (tag.isEmpty) throw new IndexOutOfBoundsException()
service.getImage(tag).transform {
case Success(link) => Success(
try {
replyWithPhoto(InputFile(link))
} catch {
case _ => reply(link) // maybe it isn't a photo...
})
case Failure(e) => Success(reply(e.getMessage))
}.void
} catch {
case _: IndexOutOfBoundsException => reply("Empty argument list. Usage: /img tag").void
}}
And this code sends an image if it's a success, but it doesn't send a message if it's a failure (but it definitely chooses case Failure(e) in this case)
reply family of functions return a Future[Message]. Currently you wrap the result of reply in Success, so the result of your transform is Future[Future[Message]], which doesn't work. Instead you can use transformWith, which expects a Future result from its argument:
onCommand("/img") { implicit msg =>
val tag = msg.text.get.drop("/img ".length)
val message: Future[Message] =
if (tag.isEmpty) reply("Empty argument list. Usage: /img tag")
else {
service.getImage(tag).transformWith {
case Success(link) => replyWithPhoto(InputFile(link)).recoverWith {
case _ => reply(link) // maybe it isn't a photo...
}
case Failure(e) => reply(e.getMessage)
}
}
message.void
}
Note, that I have also removed both try operators. The external is just unnecessary, because you can just use if/else. The internal won't work at all, because replyWithPhoto returns a Future. So it doesn't throw errors, and you need to recover or transform when it fails.

How to work with Source.Queue in Akka-Stream

I am toying around trying to use a source.queue from an Actor. I am stuck in parttern match the result of an offer operation
class MarcReaderActor(file: File, sourceQueue: SourceQueueWithComplete[Record]) extends Actor {
val inStream = file.newInputStream
val reader = new MarcStreamReader(inStream)
override def receive: Receive = {
case Process => {
if (reader.hasNext()) {
val record = reader.next()
pipe(sourceQueue.offer(record)) to self
}
}
case f:Future[QueueOfferResult] =>
}
}
}
I don't know how to check if it was Enqueued or Dropped or Failure
if i write f:Future[QueueOfferResult.Enqueued] the compile complain
Since you use pipeTo, you do no need to match on futures - the contents of the future will be sent to the actor when this future is completed, not the future itself. Do this:
override def receive: Receive = {
case Process =>
if (reader.hasNext()) {
val record = reader.next()
pipe(sourceQueue.offer(record)) to self
}
case r: QueueOfferResult =>
r match {
case QueueOfferResult.Enqueued => // element has been consumed
case QueueOfferResult.Dropped => // element has been ignored because of backpressure
case QueueOfferResult.QueueClosed => // the queue upstream has terminated
case QueueOfferResult.Failure(e) => // the queue upstream has failed with an exception
}
case Status.Failure(e) => // future has failed, e.g. because of invalid usage of `offer()`
}

Update state in actor from within a future

Consider the following code sample:
class MyActor (httpClient: HttpClient) {
var canSendMore = true
override def receive: Receive = {
case PayloadA(name: String) => send(urlA)
case PayloadB(name: String) => send(urlB)
def send(url: String){
if (canSendMore)
httpClient.post(url).map(response => canSendMore = response.canSendMore)
else {
Thread.sleep(5000) //this will be done in a more elegant way, it's just for the example.
httpClient.post(url).map(response => canSendMore = response.canSendMore)
}
}
}
Each message handling will result in an async http request. (post return value is a Future[Response])
My problem is that I want to safely update counter ( At the moment there is a race condition)
BTW, I must somehow update counter in the same thread, or at least before any other message is processed by this actor.
Is this possible?
You can use become + stash combination to keep on stashing messages when the http request future is in process.
object FreeToProcess
case PayloadA(name: String)
class MyActor (httpClient: HttpClient) extends Actor with Stash {
def canProcessReceive: Receive = {
case PayloadA(name: String) => {
// become an actor which just stashes messages
context.become(canNotProcessReceive, discardOld = false)
httpClient.post(urlA).onComplete({
case Success(x) => {
// Use your result
self ! FreeToProcess
}
case Failure(e) => {
// Use your failure
self ! FreeToProcess
}
})
}
}
def canNotProcessReceive: Receive = {
case CanProcess => {
// replay stash to mailbox
unstashAll()
// start processing messages
context.unbecome()
}
case msg => {
stash()
}
}
}

In Scala Play and Slick, how to get request to finish before sending response

In Scala Play and Slick, I wish to send a OK response only after a record has been created in the database, so far I've got:
def createItem = Action(BodyParsers.parse.json) {
request => {
val result = request.body.validate[Item]
result.fold(
invalid => {
val problem = new Problem(BAD_REQUEST, "Invalid Item JSON", invalid.toString)
returnProblemResult(BadRequest, problem)
},
item => {
service.create(item)
// TODO check for success before sending ok
Ok.as(ContentTypes("DEFAULT"))
}
)
}
}
And inside the service.create method:
def create(item: Item): Future[Unit] = {
exists(item.id).map {
case true =>
case _ => db.run(Item.table += cc)
}
}
Currently, the OK response get sent even if no new item is created. I would like it to only return OK if an item is created. If the item already exists, or if there're other errors (e.g. database errors), the createItem method should know what kind of problem occurred and return a Problem result with error message.
Try this:-
For service, change the method to
def create(item: Item): Future[Int] = {
exists(item.id).map {
case true => 0
case _ => db.run(Item.table += cc)
}
}
In controller, do
service.create(item).map {
case i:Int=> Ok.as(ContentTypes("DEFAULT"))
}.recoverWith {
// To handle the error in the processing
case ex: Throwable => InternalServerError(ex)
}

Spray Dead Letter msg

I'm trying to execute the following code
trait CustomHttpService extends HttpService {
import MyJsonProtocol._
import spray.httpx.SprayJsonSupport._
implicit def executionContext = actorRefFactory.dispatcher
implicit val timeout = Timeout(5 seconds)
val offerActor = actorRefFactory.actorOf(Props[OfferActor], "offer-actor")
val defaultRoute = {
path("offer" / JavaUUID) { uuid =>
get {
respondWithMediaType(`application/json`) {
complete {
(offerActor ? Get(uuid)).mapTo[Offer]
}
}
}
}
}
}
class OfferActor extends Actor {
override def receive = {
case Get(id) =>
val future = OfferService.genericService.getById(id)
future.onComplete {
case Success(s) =>
s match {
case Some(offer) => sender ! offer
case None => println("none")
}
case Failure(f) => f
}
case id: String => println("received string id: " + id)
case _ => println("receive nothing")
}
}
Initially I was trying to return directly the future, but it was giving me an error, complaining about the promise that I was trying to cast to my Offer object.
Then I just ugly solve my future inside my actor to finally get the Offer and then return it to the sender.
Doing this I'm getting the following:
[06/09/2015 15:16:43.056]
[spray-system-akka.actor.default-dispatcher-4]
[akka://spray-system/deadLetters] Message
[com.spray.entity.Offer] from
Actor[akka://spray-system/user/spray-actor/offer-actor#-617290326] to
Actor[akka://spray-system/deadLetters] was not delivered. [2] dead
letters encountered. This logging can be turned off or adjusted with
configuration settings 'akka.log-dead-letters' and
'akka.log-dead-letters-during-shutdown'.
Indeed, I'm sending a msg with an Offer that I got from the database.
Instead if I simply create an Offer like this, works perfectly.
case Get(id) => sender ! Offer(Some(id), "offer", new DateTime())
I'm believing the future.onComplete inside the actor is causing something wrong.
Any thoughts?
sender is really a function, so you could write sender() to show that it is not just accessing an immutable value. When you call sender inside the future.onComplete the value of sender isn't valid anymore.
I've run into this problem before and the way I worked around was by saving the value of sender outside of the future:
class OfferActor extends Actor {
override def receive = {
case Get(id) =>
val future = OfferService.genericService.getById(id)
val replyTo = sender
future.onComplete {
case Success(s) =>
s match {
case Some(offer) => replyTo ! offer
case None => println("none")
}
case Failure(f) => f
}
case id: String => println("received string id: " + id)
case _ => println("receive nothing")
}
}
Well, just solved it trying to block my future.
I just created a blocked version of
OfferService.genericService.getByIdBlocking(id)
Where I blocked it with
Await.result
then it worked!
So basically I had to let akka embrace my call with a future using the ask pattern but do blocking operations inside the actor.