play 2.6.x logging request body and process time

Viewed 754

I'm using play server 2.6.x and trying to log the request body and the process time of every request.

The problem is that if I'm using Play Filter api I don't have access to request body, and if I'm using play Action api to wrap the action as LoggingAction I have access to the body but can't have the response time, so I had to use them both and create a unique id for each request and update log by request id.

It seems to me that there has to be much more elegant solution.

2 Answers

Had the same question - It wasn't trivial to find the answer - take a look at the play.test.Helpers class - you have methods that achieve that there.

Which one to use will depend if you have access to the Materializer or not and/or where/how you're doing the log. If it's a filter or an action.

In my case, I use it as an Action, and the code that matches my similar requirements is: ```

@Override
public CompletionStage<Result> call(Http.Context context) {
    if(environment.isProd()) {
        //let's just not log anything
        delegate.call(context);
    }
    CompletionStage<Result> callResult = null;
    try {


    Http.RequestBody body = context.request().body();
    final String basicCallIdentifier = "Call: " + context.request().method() + ":"  + context.request().uri();
    String requestMessage = "Request - " + basicCallIdentifier;
    if(body != null) {
        requestMessage +=   " body: " + body.asText();
    }
        LOG.debug(requestMessage);
        callResult = delegate.call(context);
        return callResult.whenComplete((result, throwable) -> {
            String resultMessage = "Response - " + basicCallIdentifier + ":";
            if(result == null) {
                resultMessage += " Null result!";
                LOG.debug(resultMessage, throwable);
            } else {
                resultMessage += " Status: " +result.status();
                HttpEntity resultBody = result.body();
                if(resultBody != null
                        && resultBody.contentType().isPresent()
                        && resultBody.contentType().get().contains("json")) {
                    HttpEntity.Strict resultBodyStrict = (HttpEntity.Strict) resultBody;
                    ByteString byteString = resultBodyStrict.data();
                    resultMessage +=  " body: " + byteString.decodeString("UTF-8");

                }
                LOG.debug(resultMessage, throwable);

            }
        });



    } catch(Exception ex) {
        LOG.error("Caught exception on APILogAction", ex);
        if(callResult == null) {
            callResult = delegate.call(context);
        }
        return callResult;
    }

}

```

I've also found an answer to my question, it seems to me a little bit more easy so i will share it.

It involves creating your own Action to have the ability to parse the request body, and also log all headers.

case class LoggingAction @Inject(override val parser: BodyParsers.Default)(implicit ec:ExecutionContext) extends ActionBuilderImpl(parser){
  override def invokeBlock[A](request: Request[A], invoke: (Request[A] => Future[Result])): Future[Result] ={
    val startTime = System.currentTimeMillis
    try {
      invoke(request).map(res => {
        val processTime: Long = System.currentTimeMillis - startTime
        Logger.log(request,time)
        res
      })
    } catch {
      // Throw some error...
    }
  }
}
Related