├── .gitignore
├── LICENSE
├── README.md
├── build.sbt
├── project
├── build.properties
└── plugins.sbt
├── src
└── main
│ ├── java
│ └── com
│ │ └── github
│ │ └── rishabh9
│ │ ├── EnableMDC.java
│ │ ├── MappedDiagnosticContextAction.java
│ │ └── MappedDiagnosticContextFilter.java
│ └── scala
│ └── com
│ └── github
│ └── rishabh9
│ └── MDCPropagatingDispatcherConfigurator.scala
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Java template
3 | *.class
4 |
5 | # Log file
6 | *.log
7 |
8 | # BlueJ files
9 | *.ctxt
10 |
11 | # Mobile Tools for Java (J2ME)
12 | .mtj.tmp/
13 |
14 | # Package Files #
15 | *.jar
16 | *.war
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | ### JetBrains template
25 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
26 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
27 |
28 | .idea/
29 | *.iml
30 |
31 | # User-specific stuff:
32 | .idea/**/workspace.xml
33 | .idea/**/tasks.xml
34 |
35 | # Sensitive or high-churn files:
36 | .idea/**/dataSources/
37 | .idea/**/dataSources.ids
38 | .idea/**/dataSources.xml
39 | .idea/**/dataSources.local.xml
40 | .idea/**/sqlDataSources.xml
41 | .idea/**/dynamic.xml
42 | .idea/**/uiDesigner.xml
43 |
44 | # Gradle:
45 | .idea/**/gradle.xml
46 | .idea/**/libraries
47 |
48 | # Mongo Explorer plugin:
49 | .idea/**/mongoSettings.xml
50 |
51 | ## File-based project format:
52 | *.iws
53 |
54 | ## Plugin-specific files:
55 |
56 | # IntelliJ
57 | /out/
58 |
59 | # mpeltonen/sbt-idea plugin
60 | .idea_modules/
61 |
62 | # JIRA plugin
63 | atlassian-ide-plugin.xml
64 |
65 | # Crashlytics plugin (for Android Studio and IntelliJ)
66 | com_crashlytics_export_strings.xml
67 | crashlytics.properties
68 | crashlytics-build.properties
69 | fabric.properties
70 | ### SBT template
71 | # Simple Build Tool
72 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
73 |
74 | target/
75 | lib_managed/
76 | src_managed/
77 | project/boot/
78 | .history
79 | .cache
80 | ### Scala template
81 | *.class
82 | *.log
83 |
84 | # sbt specific
85 | .cache
86 | .history
87 | .lib/
88 | dist/*
89 | target/
90 | lib_managed/
91 | src_managed/
92 | project/boot/
93 | project/plugins/project/
94 |
95 | # Scala-IDE specific
96 | .ensime
97 | .ensime_cache/
98 | .scala_dependencies
99 | .worksheet
100 |
101 | # ENSIME specific
102 | .ensime_cache/
103 | .ensime
104 |
105 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This software is licensed under the Apache 2 license, quoted below.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with
4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
5 |
6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
8 | language governing permissions and limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | The Mapped Diagnostic Context (MDC) Propagation Akka Dispatcher
2 | =============
3 |
4 | A Mapped Diagnostic Context (MDC) propagation Akka Dispatcher for the asynchronous environment of the Play Framework.
5 |
6 | Took the idea from here:
7 |
8 | 1. http://yanns.github.io/blog/2014/05/04/slf4j-mapped-diagnostic-context-mdc-with-play-framework/
9 | 2. https://github.com/jroper/thread-local-context-propagation/
10 |
11 | The provided code logs an UUID, per request, by default.
12 |
13 | Refer the code and customize to your whims and fancy.
14 |
15 | > Created this project so that I can have the dispatcher in a separate jar, to make my development easier.
16 |
17 | #### How To Use
18 |
19 | ###### Add the dependency
20 | ```scala
21 | libraryDependencies += "com.github.rishabh9" %% "mdc-propagation-dispatcher" % "0.0.8"
22 | ```
23 |
24 | ###### Either add 'MappedDiagnosticContextFilter' to Filters.java
25 | ```java
26 | import com.github.rishabh9.MappedDiagnosticContextFilter;
27 |
28 | @Singleton
29 | public class Filters implements HttpFilters {
30 | private final MappedDiagnosticContextFilter mdcFilter;
31 |
32 | @Inject
33 | public Filters(MappedDiagnosticContextFilter mdcFilter) {
34 | this.mdcFilter = mdcFilter;
35 | }
36 |
37 | @Override
38 | public EssentialFilter[] filters() {
39 | final EssentialFilter[] filters = {
40 | mdcFilter.asJava()
41 | };
42 | return filters;
43 | }
44 | }
45 | ```
46 |
47 | ###### Or annotate your controllers/methods with 'EnableMDC' annotation
48 | ```java
49 | import com.github.rishabh9.EnableMDC;
50 | import play.mvc.Controller;
51 |
52 | @EnableMDC
53 | public class MyController extends Controller {
54 | // ...
55 | }
56 | ```
57 |
58 | ###### Update your logging configuration
59 | ```xml
60 | %d{HH:mm:ss.SSS} %coloredLevel %logger{35} %mdc{X-UUID:--} - %msg%n%rootException
61 | ```
62 |
63 | ###### Update your application.conf
64 | ```hocon
65 | play {
66 | akka {
67 | actor {
68 | default-dispatcher {
69 | type = "com.github.rishabh9.MDCPropagatingDispatcherConfigurator"
70 | }
71 | }
72 | }
73 | }
74 | ```
75 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._
2 |
3 | organization := "com.github.rishabh9"
4 |
5 | name := "mdc-propagation-dispatcher"
6 |
7 | version := (version in ThisBuild).value
8 |
9 | scalaVersion := "2.13.1"
10 |
11 | exportJars := true
12 |
13 | retrieveManaged := true
14 |
15 | libraryDependencies ++= Seq(
16 | "com.typesafe" % "config" % "1.4.0" % "provided",
17 | "com.typesafe.play" %% "play" % "2.7.3" % "provided",
18 | "org.slf4j" % "slf4j-api" % "1.7.30" % "provided",
19 | "com.typesafe.akka" %% "akka-actor" % "2.6.4" % "provided"
20 | )
21 |
22 | javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
23 |
24 | initialize := {
25 | val _ = initialize.value
26 | if (sys.props("java.specification.version") != "1.8")
27 | sys.error("Java 8 is required for this project.")
28 | }
29 |
30 | sources in (Compile, doc) := Seq.empty
31 |
32 | // RELEASE --
33 |
34 | // Maven publishing info
35 | publishMavenStyle := true
36 |
37 | publishTo := {
38 | val nexus = "https://oss.sonatype.org/"
39 | if (version.value.trim.endsWith("SNAPSHOT"))
40 | Some("snapshots" at nexus + "content/repositories/snapshots")
41 | else
42 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
43 | }
44 |
45 | publishArtifact in Test := false
46 |
47 | pomIncludeRepository := { _ => false }
48 |
49 | releasePublishArtifactsAction := PgpKeys.publishSigned.value
50 |
51 | // The Release configuration
52 | releaseProcess := Seq[ReleaseStep](
53 | checkSnapshotDependencies, // : ReleaseStep
54 | inquireVersions, // : ReleaseStep
55 | runTest, // : ReleaseStep
56 | setReleaseVersion, // : ReleaseStep
57 | commitReleaseVersion, // : ReleaseStep, performs the initial git checks
58 | tagRelease, // : ReleaseStep
59 | publishArtifacts, // : ReleaseStep, checks whether `publishTo` is properly set up
60 | setNextVersion, // : ReleaseStep
61 | commitNextVersion, // : ReleaseStep
62 | pushChanges // : ReleaseStep, also checks that an upstream branch is properly configured
63 | )
64 |
65 | pomExtra := (
66 | https://github.com/rishabh9/mdc-propagation-dispatcher/
67 |
68 |
69 |
70 | Apache 2
71 | http://www.apache.org/licenses/LICENSE-2.0.txt
72 |
73 |
74 |
75 |
76 | scm:git:git@github.com:rishabh9/mdc-propagation-dispatcher.git
77 | scm:git:git@github.com:rishabh9/mdc-propagation-dispatcher.git
78 | https://github.com/rishabh9/mdc-propagation-dispatcher/
79 |
80 |
81 |
82 |
83 | rishabh9
84 | Rishabh Joshi
85 | https://github.com/rishabh9
86 |
87 |
88 | )
89 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.8
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | // SBT Release Plugin
2 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.12")
3 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.0")
4 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rishabh9/EnableMDC.java:
--------------------------------------------------------------------------------
1 | package com.github.rishabh9;
2 |
3 | import play.mvc.With;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * @author rishabh
12 | */
13 | @With(MappedDiagnosticContextAction.class)
14 | @Target({ElementType.TYPE, ElementType.METHOD})
15 | @Retention(RetentionPolicy.RUNTIME)
16 | public @interface EnableMDC {
17 | boolean value() default true;
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rishabh9/MappedDiagnosticContextAction.java:
--------------------------------------------------------------------------------
1 | package com.github.rishabh9;
2 |
3 |
4 | import org.slf4j.MDC;
5 | import play.mvc.Action;
6 | import play.mvc.Http;
7 | import play.mvc.Result;
8 |
9 | import java.util.UUID;
10 | import java.util.concurrent.CompletionStage;
11 |
12 | /**
13 | * @author rishabh
14 | */
15 | public class MappedDiagnosticContextAction extends Action {
16 | public CompletionStage call(Http.Context ctx) {
17 | if (configuration.value()) {
18 | MDC.put("X-UUID", UUID.randomUUID().toString());
19 | }
20 | return delegate.call(ctx).whenComplete((result, throwable) -> {
21 | if (configuration.value()) {
22 | MDC.remove("X-UUID");
23 | }
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rishabh9/MappedDiagnosticContextFilter.java:
--------------------------------------------------------------------------------
1 | package com.github.rishabh9;
2 |
3 | import akka.stream.Materializer;
4 | import org.slf4j.MDC;
5 | import play.mvc.Filter;
6 | import play.mvc.Http;
7 | import play.mvc.Result;
8 |
9 | import javax.inject.Inject;
10 | import java.util.UUID;
11 | import java.util.concurrent.CompletionStage;
12 | import java.util.concurrent.Executor;
13 | import java.util.function.Function;
14 |
15 | /**
16 | * @author rishabh
17 | */
18 | public class MappedDiagnosticContextFilter extends Filter {
19 |
20 | private final Executor exec;
21 |
22 | /**
23 | * @param mat This object is needed to handle streaming of requests
24 | * and responses.
25 | * @param exec This class is needed to execute code asynchronously.
26 | * It is used below by the thenAsyncApply
method.
27 | */
28 | @Inject
29 | public MappedDiagnosticContextFilter(Materializer mat, Executor exec) {
30 | super(mat);
31 | this.exec = exec;
32 | }
33 |
34 | @Override
35 | public CompletionStage apply(Function> next,
36 | Http.RequestHeader requestHeader) {
37 |
38 | MDC.put("X-UUID", UUID.randomUUID().toString());
39 | return next.apply(requestHeader).thenApplyAsync(
40 | result -> {
41 | MDC.remove("X-UUID");
42 | return result;
43 | },
44 | exec
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/rishabh9/MDCPropagatingDispatcherConfigurator.scala:
--------------------------------------------------------------------------------
1 | package com.github.rishabh9
2 |
3 | import java.util.concurrent.TimeUnit
4 |
5 | import akka.dispatch._
6 | import com.typesafe.config.Config
7 | import org.slf4j.MDC
8 |
9 | import scala.concurrent.ExecutionContext
10 | import scala.concurrent.duration.{Duration, FiniteDuration}
11 |
12 | /**
13 | * Configurator for a MDC propagating dispatcher.
14 | *
15 | * To use it, configure play like this:
16 | * {{{
17 | * play {
18 | * akka {
19 | * actor {
20 | * default-dispatcher = {
21 | * type = "com.github.rishabh9.MDCPropagatingDispatcherConfigurator"
22 | * }
23 | * }
24 | * }
25 | * }
26 | * }}}
27 | *
28 | * Credits to James Roper for the [[https://github.com/jroper/thread-local-context-propagation/ initial implementation]]
29 | */
30 | class MDCPropagatingDispatcherConfigurator(config: Config, prerequisites: DispatcherPrerequisites)
31 | extends MessageDispatcherConfigurator(config, prerequisites) {
32 |
33 | private val instance = new MDCPropagatingDispatcher(
34 | this,
35 | config.getString("id"),
36 | config.getInt("throughput"),
37 | FiniteDuration(config.getDuration("throughput-deadline-time", TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS),
38 | configureExecutor(),
39 | FiniteDuration(config.getDuration("shutdown-timeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS))
40 |
41 | override def dispatcher(): MessageDispatcher = instance
42 | }
43 |
44 | /**
45 | * A MDC propagating dispatcher.
46 | *
47 | * This dispatcher propagates the MDC current request context if it's set when it's executed.
48 | */
49 | class MDCPropagatingDispatcher(_configurator: MessageDispatcherConfigurator,
50 | id: String,
51 | throughput: Int,
52 | throughputDeadlineTime: Duration,
53 | executorServiceFactoryProvider: ExecutorServiceFactoryProvider,
54 | shutdownTimeout: FiniteDuration)
55 | extends Dispatcher(_configurator, id, throughput, throughputDeadlineTime, executorServiceFactoryProvider, shutdownTimeout) {
56 |
57 | self =>
58 |
59 | override def prepare(): ExecutionContext = new ExecutionContext {
60 | // capture the MDC
61 | val mdcContext = MDC.getCopyOfContextMap
62 |
63 | def execute(r: Runnable) = self.execute(new Runnable {
64 | def run() = {
65 | // backup the callee MDC context
66 | val oldMDCContext = MDC.getCopyOfContextMap
67 |
68 | // Run the runnable with the captured context
69 | setContextMap(mdcContext)
70 | try {
71 | r.run()
72 | } finally {
73 | // restore the callee MDC context
74 | setContextMap(oldMDCContext)
75 | }
76 | }
77 | })
78 |
79 | def reportFailure(t: Throwable) = self.reportFailure(t)
80 | }
81 |
82 | private[this] def setContextMap(context: java.util.Map[String, String]) {
83 | if (context == null) {
84 | MDC.clear()
85 | } else {
86 | MDC.setContextMap(context)
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | version in ThisBuild := "0.0.9-SNAPSHOT"
2 |
--------------------------------------------------------------------------------