简体   繁体   中英

How can I perform session based logging in Play Framework

We are currently using the Play Framework and we are using the standard logging mechanism. We have implemented a implicit context to support passing username and session id to all service methods. We want to implement logging so that it is session based. This requires implementing our own logger. This works for our own logs but how do we do the same for basic exception handling and logs as a result. Maybe there is a better way to capture this then with implicits or how can we override the exception handling logging. Essentially, we want to get as many log messages to be associated to the session.

It depends if you are doing reactive style development or standard synchronous development:

  • If standard synchronous development (ie no futures, 1 thread per request) - then I'd recommend you just use MDC, which adds values onto Threadlocal for logging. You can then customise the output in logback / log4j. When you get the username / session (possibly in a Filter or in your controller), you can then set the values there and then and you do not need to pass them around with implicits.

If you are doing reactive development you have a couple options:

  • You can still use MDC, except you'd have to use a custom Execution Context that effectively copies the MDC values to the thread, since each request could in theory be handled by multiple threads. (as described here: http://code.hootsuite.com/logging-contextual-info-in-an-asynchronous-scala-application/ )

  • The alternative is the solution which I tend to use (and close to what you have now): You could make a class which represents MyAppRequest. Set the username, session info, and anything else, on that. You can continue to pass it around as an implicit. However, instead of using Action.async, you make your own MyAction class which an be used like below

    myAction.async { implicit myRequest => //some code }

    Inside the myAction, you'd have to catch all Exceptions and deal with future failures, and do the error handling manually instead of relying on the ErrorHandler. I often inject myAction into my Controllers and put common filter functionality in it.

    The down side of this is, it is just a manual method. Also I've made MyAppRequest hold a Map of loggable values which can be set anywhere, which means it had to be a mutable map. Also, sometimes you need to make more than one myAction.async. The pro is, it is quite explicit and in your control without too much ExecutionContext/ThreadLocal magic.

Here is some very rough sample code as a starter, for the manual solution:

def logErrorAndRethrow(myrequest:MyRequest, x:Throwable): Nothing = {
  //log your error here in the format you like
  throw x //you can do this or handle errors how you like
}

class MyRequest {
  val attr : mutable.Map[String, String] = new mutable.HashMap[String, String]()
}

//make this a util to inject, or move it into a common parent controller
def myAsync(block: MyRequest => Future[Result] ): Action[AnyContent] = {
  val myRequest = new MyRequest()
  try {
    Action.async(
      block(myRequest).recover { case cause => logErrorAndRethrow(myRequest, cause) }
    )
  } catch {
    case x:Throwable =>
      logErrorAndRethrow(myRequest, x)
  }
}

//the method your Route file refers to
def getStuff = myAsync { request:MyRequest =>
  //execute your code here, passing around request as an implicit
  Future.successful(Results.Ok)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM