diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a29631 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Created by .ignore support plugin (hsz.mobi) +### Java template +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ +*.iml + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### SBT template +# Simple Build Tool +# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control + +target/ +lib_managed/ +src_managed/ +project/boot/ +.history +.cache +### Scala template +*.class +*.log + +# sbt specific +.cache +.history +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Scala-IDE specific +.ensime +.ensime_cache/ +.scala_dependencies +.worksheet + +# ENSIME specific +.ensime_cache/ +.ensime + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4baedcb --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +This software is licensed under the Apache 2 license, quoted below. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with +the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..71c0333 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +The Mapped Diagnostic Context (MDC) Propagation Akka Dispatcher +============= + +A Mapped Diagnostic Context (MDC) propagation Akka Dispatcher for the asynchronous environment of the Play Framework. + +#### Links +1. http://yanns.github.io/blog/2014/05/04/slf4j-mapped-diagnostic-context-mdc-with-play-framework/ +2. https://github.com/jroper/thread-local-context-propagation/ + +#### How To Use + +##### Configure Jitpack +1. Add the JitPack repository to your build file - build.sbt +```scala +resolvers += "jitpack" at "https://jitpack.io" +``` +2. Add the dependency +```scala +libraryDependencies += "com.github.rishabh9" % "mdc-propagation-dispatcher" % "0.0.1" +``` + +##### Create a filter +```java +public class MappedDiagnosticContextFilter extends Filter { + + /** + * @param mat This object is needed to handle streaming of requests + * and responses. + */ + @Inject + public MappedDiagnosticContextFilter(Materializer mat) { + super(mat); + } + + @Override + public CompletionStage apply(Function> next, + Http.RequestHeader requestHeader) { + try { + return next.apply(requestHeader); + } finally { + MDC.remove("X-UserId"); + } + } +} +``` + +##### Add to Filters.java +```java +@Singleton +public class Filters implements HttpFilters { + private final MappedDiagnosticContextFilter mdcFilter; + + @Inject + public Filters(MappedDiagnosticContextFilter mdcFilter) { + this.mdcFilter = mdcFilter; + } + + @Override + public EssentialFilter[] filters() { + final EssentialFilter[] filters = { + mdcFilter.asJava() + }; + return filters; + } +} +``` + +##### Update your logging configuration +```xml + + + %d{HH:mm:ss.SSS} %coloredLevel %logger{35} %mdc{X-UserId:--} - %msg%n%rootException + + +``` \ No newline at end of file diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..7f46220 --- /dev/null +++ b/build.sbt @@ -0,0 +1,45 @@ +import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._ + +organization := "dispatcher" + +name := "mdc-propagation-dispatcher" + +version := (version in ThisBuild).value + +scalaVersion := "2.11.7" + +exportJars := true + +retrieveManaged := true + +libraryDependencies ++= Seq( + "com.typesafe" % "config" % "1.3.0" % "provided", + "org.slf4j" % "slf4j-api" % "1.7.21" % "provided", + "com.typesafe.akka" %% "akka-actor" % "2.4.12" % "provided" +) + +javacOptions ++= Seq("-source", "1.8", "-target", "1.8") + +initialize := { + val _ = initialize.value + if (sys.props("java.specification.version") != "1.8") + sys.error("Java 8 is required for this project.") +} + +sources in (Compile, doc) := Seq.empty + +publishArtifact in (Compile, packageDoc) := false + +// The Release configuration +releaseProcess := Seq[ReleaseStep]( + checkSnapshotDependencies, // : ReleaseStep + inquireVersions, // : ReleaseStep + runTest, // : ReleaseStep + setReleaseVersion, // : ReleaseStep + commitReleaseVersion, // : ReleaseStep, performs the initial git checks + tagRelease, // : ReleaseStep + //publishArtifacts, // : ReleaseStep, checks whether `publishTo` is properly set up + setNextVersion, // : ReleaseStep + commitNextVersion, // : ReleaseStep + pushChanges // : ReleaseStep, also checks that an upstream branch is properly configured +) \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..1d50943 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,2 @@ +// SBT Release Plugin +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.3") diff --git a/src/main/scala/MDCPropagatingDispatcherConfigurator.scala b/src/main/scala/MDCPropagatingDispatcherConfigurator.scala new file mode 100644 index 0000000..6752436 --- /dev/null +++ b/src/main/scala/MDCPropagatingDispatcherConfigurator.scala @@ -0,0 +1,88 @@ +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. + * + * 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) + } + } + +} diff --git a/version.sbt b/version.sbt new file mode 100644 index 0000000..91ea14c --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version in ThisBuild := "0.0.1"