4

I know that in Play! using Scala that there is no Http.context available since the idea is to leverage implicits to pass any data around your stack. However, this seems like kind of a lot of boiler plate to pass through when you need a piece of information available for the entire context.

More specifically what I'm interested in is tracking a UUID that is passed from the request header and making it available to any logger so that each request gets its own unique identifier. I'd like this to be seamless from anyone who calls into a logger (or log wrapper)

Coming from a .NET background the http context flows with async calls, and this is also possible with the call context in WCF. At that point you can register a function with the logger to return the current uuid for the request based on a logging pattern of something like "%requestID%".

Building a larger distributed system you need to be able to correlate requests across multiple stacks.

But, being new to scala and play I'm not even sure where to look for a way to do this?

devshorts
  • 8,572
  • 4
  • 50
  • 73
  • 1
    What you are looking for in Java is called the Mapped Diagnostic Context or MDC (at least by SLF4J) - [here's an article I found that details how to set this up for Play](http://yanns.github.io/blog/2014/05/04/slf4j-mapped-diagnostic-context-mdc-with-play-framework/) – Sean Vieira Feb 03 '15 at 21:21

1 Answers1

3

What you are looking for in Java is called the Mapped Diagnostic Context or MDC (at least by SLF4J) - here's an article I found that details how to set this up for Play. In the interest of preserving the details for future visitors here is the code used for an MDC-propagating Akka dispatcher:

package monitoring

import java.util.concurrent.TimeUnit

import akka.dispatch._
import com.typesafe.config.Config
import org.slf4j.MDC

import scala.concurrent.ExecutionContext
import scala.concurrent.duration.{Duration, FiniteDuration}

/**
 * Configurator for a MDC propagating dispatcher.
 * Authored by Yann Simon
 * See: http://yanns.github.io/blog/2014/05/04/slf4j-mapped-diagnostic-context-mdc-with-play-framework/
 *
 * To use it, configure play like this:
 * {{{
 * play {
 *   akka {
 *     actor {
 *       default-dispatcher = {
 *         type = "monitoring.MDCPropagatingDispatcherConfigurator"
 *       }
 *     }
 *   }
 * }
 * }}}
 *
 * Credits to James Roper for the [[https://github.com/jroper/thread-local-context-propagation/ initial implementation]]
 */
class MDCPropagatingDispatcherConfigurator(config: Config, prerequisites: DispatcherPrerequisites)
  extends MessageDispatcherConfigurator(config, prerequisites) {

  private val instance = new MDCPropagatingDispatcher(
    this,
    config.getString("id"),
    config.getInt("throughput"),
    FiniteDuration(config.getDuration("throughput-deadline-time", TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS),
    configureExecutor(),
    FiniteDuration(config.getDuration("shutdown-timeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS))

  override def dispatcher(): MessageDispatcher = instance
}

/**
 * A MDC propagating dispatcher.
 *
 * This dispatcher propagates the MDC current request context if it's set when it's executed.
 */
class MDCPropagatingDispatcher(_configurator: MessageDispatcherConfigurator,
                               id: String,
                               throughput: Int,
                               throughputDeadlineTime: Duration,
                               executorServiceFactoryProvider: ExecutorServiceFactoryProvider,
                               shutdownTimeout: FiniteDuration)
  extends Dispatcher(_configurator, id, throughput, throughputDeadlineTime, executorServiceFactoryProvider, shutdownTimeout ) {

  self =>

  override def prepare(): ExecutionContext = new ExecutionContext {
    // capture the MDC
    val mdcContext = MDC.getCopyOfContextMap

    def execute(r: Runnable) = self.execute(new Runnable {
      def run() = {
        // backup the callee MDC context
        val oldMDCContext = MDC.getCopyOfContextMap

        // Run the runnable with the captured context
        setContextMap(mdcContext)
        try {
          r.run()
        } finally {
          // restore the callee MDC context
          setContextMap(oldMDCContext)
        }
      }
    })
    def reportFailure(t: Throwable) = self.reportFailure(t)
  }

  private[this] def setContextMap(context: java.util.Map[String, String]) {
    if (context == null) {
      MDC.clear()
    } else {
      MDC.setContextMap(context)
    }
  }

}

You can then set values in the MDC using MDC.put and remove it using MDC.remove (alternatively, take a look at putCloseable if you need to add and remove some context from a set of synchronous calls):

import org.slf4j.MDC

// Somewhere in a handler
MDC.put("X-UserId", currentUser.id)

// Later, when the user is no longer available
MDC.remove("X-UserId")

and add them to your logging output using %mdc{field-name:default-value}:

<!-- an example from the blog -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} %coloredLevel %logger{35} %mdc{X-UserId:--} - %msg%n%rootException</pattern>
    </encoder>
</appender>

There are more details in the linked blog post about tweaking the ExecutionContext that Play uses to propagate the MDC context correctly (as an alternative approach).

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • Hi, will this MDC Dispatcher work on an akka application which is not a play framework app? I can't make it work with my akka application, I am mixin `ActorLogging` trait to my actors and also set my `akka loggers` to `akka.event.slf4j.Slf4jLogger` and `logging-filter` to `akka.event.slf4j.Slf4jLoggingFilter`. Then I set my `akka actor default-dispatcher type` to `"some.package.monitoring. MDCPropagatingDispatcherConfigurator"` in my application.conf. Am I doing it wrong? – Frank Smith May 06 '15 at 13:58
  • @Frank - hard to tell without seeing more code. Worth opening up a separate question about it, referencing this one. – Sean Vieira May 06 '15 at 19:31
  • Hi Sean, I created a new question for this - http://stackoverflow.com/questions/30091356/mdc-mapped-diagnostic-context-logging-in-akka, thanks a lot!! – Frank Smith May 07 '15 at 03:24