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?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With