Search code examples
scalaloggingplayframeworkenumeratorloops

Play / Logging / Print Response Body / Run over enumerator / buffer the body


I'm looking for a way to print the response body in Play framework, I have a code like this:

object AccessLoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    Logger.info(s"""Request:
      id=${request.id} 
      method=${request.method} 
      uri=${request.uri} 
      remote-address=${request.remoteAddress} 
      body=${request.body}
    """)
    val ret = block(request)
    /*
    ret.map {result =>
      Logger.info(s"""Response:
      id=${request.id} 
      body=${result.body}
      """)
    }
    */ //TODO: find out how to print result.body (be careful not to consume the enumerator)
    ret
  }
}

Currently the commented-out code is not working as I wanted, I mean, it would print:

Response:
id=1
body=play.api.libs.iteratee.Enumerator$$anon$18@39e6c1a2

So, I need to find a way to get a String out of Enumerator[Array[Byte]]. I tried to grasp the concept of Enumerator by reading this: http://mandubian.com/2012/08/27/understanding-play2-iteratees-for-normal-humans/

So..., if I understand it correctly:

  1. I shouldn't dry-up the enumerator in the process of converting it to String. Otherwise, the client would receive nothing.

  2. Let's suppose I figure out how to implement the T / filter mechanism. But then... wouldn't it defeat the purpose of Play framework as non-blocking streaming framework (because I would be building up the complete array of bytes in the memory, before calling toString on it, and finally log it)?

So, what's the correct way to log the response?

Thanks in advance, Raka


Solution

  • This code works:

    object AccessLoggingAction extends ActionBuilder[Request] {
      def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
        val start = System.currentTimeMillis
    
        Logger.info(s"""Request:
          id=${request.id} 
          method=${request.method} 
          uri=${request.uri} 
          remote-address=${request.remoteAddress} 
          body=${request.body}
        """)
    
        val resultFut = block(request)
    
        resultFut.map {result =>
          val time = System.currentTimeMillis - start
          Result(result.header, result.body &> Enumeratee.map(arrOfBytes => {
            val body = new String(arrOfBytes.map(_.toChar))
            Logger.info(s"""Response:
          id=${request.id} 
          method=${request.method}
          uri=${request.uri}
          delay=${time}ms 
          status=${result.header.status}
          body=${body}""")
            arrOfBytes
          }), result.connection)
        }
      }
    }
    

    I partly learned it from here (on how to get the byte array out of enumerator): Scala Play 2.1: Accessing request and response bodies in a filter.

    I'm using Play 2.3.7 while the link I gave uses 2.1 (and still uses PlainResult, which no longer exists in 2.3).