Отображение диагностического контекстного журнала с игрой framewok и akka в java

Я пытаюсь запустить mdc в игровом фильтре в java для всех запросов. Я следовал этому руководству в scala и пытался конвертировать в java http://yanns.github.io/blog/2014/05/04/slf4j-mapped-diagnostic-context-mdc-with-play-framework/

но все же mdc не распространяется на все контексты выполнения. Я использую этот диспетчер в качестве диспетчера по умолчанию, но для него существует множество контекстов выполнения. Мне нужно, чтобы mdc распространялся на все контексты выполнения

ниже мой код Java

import java.util.Map;

import org.slf4j.MDC;

import scala.concurrent.ExecutionContext;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import akka.dispatch.Dispatcher;
import akka.dispatch.ExecutorServiceFactoryProvider;
import akka.dispatch.MessageDispatcherConfigurator;

public class MDCPropagatingDispatcher extends Dispatcher {
    public MDCPropagatingDispatcher(
            MessageDispatcherConfigurator _configurator, String id,
            int throughput, Duration throughputDeadlineTime,
            ExecutorServiceFactoryProvider executorServiceFactoryProvider,
            FiniteDuration shutdownTimeout) {
        super(_configurator, id, throughput, throughputDeadlineTime,
                executorServiceFactoryProvider, shutdownTimeout);

    }

    @Override
    public ExecutionContext prepare() {
        final Map<String, String> mdcContext = MDC.getCopyOfContextMap();
        return new ExecutionContext() {

            @Override
            public void execute(Runnable r) {
                Map<String, String> oldMDCContext = MDC.getCopyOfContextMap();
                setContextMap(mdcContext);
                try {
                    r.run();
                } finally {
                    setContextMap(oldMDCContext);
                }
            }

            @Override
            public ExecutionContext prepare() {
                return this;
            }

            @Override
            public void reportFailure(Throwable t) {
                play.Logger.info("error occured in dispatcher");
            }

        };
    }

    private void setContextMap(Map<String, String> context) {
        if (context == null) {
            MDC.clear();
        } else {
            play.Logger.info("set context "+ context.toString());
            MDC.setContextMap(context);
        }
    }
}



import java.util.concurrent.TimeUnit;

import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;

import com.typesafe.config.Config;

import akka.dispatch.DispatcherPrerequisites;
import akka.dispatch.MessageDispatcher;
import akka.dispatch.MessageDispatcherConfigurator;

public class MDCPropagatingDispatcherConfigurator extends
        MessageDispatcherConfigurator {
    private MessageDispatcher instance;

    public MDCPropagatingDispatcherConfigurator(Config config,
            DispatcherPrerequisites prerequisites) {
        super(config, prerequisites);
        Duration throughputDeadlineTime = new FiniteDuration(-1,
                TimeUnit.MILLISECONDS);
        FiniteDuration shutDownDuration = new FiniteDuration(1,
                TimeUnit.MILLISECONDS);
        instance = new MDCPropagatingDispatcher(this, "play.akka.actor.contexts.play-filter-context",
                100, throughputDeadlineTime,
                configureExecutor(), shutDownDuration);
    }

    public MessageDispatcher dispatcher() {
        return instance;
    }

}

фильтр-перехватчик

public class MdcLogFilter implements EssentialFilter {
@Override
public EssentialAction apply(final EssentialAction next) {
    return new MdcLogAction() {
        @Override
        public Iteratee<byte[], SimpleResult> apply(
                final RequestHeader requestHeader) {
            final String  uuid = Utils.generateRandomUUID();
            MDC.put("uuid", uuid);
            play.Logger.info("request started"+uuid);
            final ExecutionContext playFilterContext = Akka.system()
                    .dispatchers()
                    .lookup("play.akka.actor.contexts.play-custom-filter-context");
            return next.apply(requestHeader).map(
                    new AbstractFunction1<SimpleResult, SimpleResult>() {
                        @Override
                        public SimpleResult apply(SimpleResult simpleResult) {
                            play.Logger.info("request ended"+uuid);
                            MDC.remove("uuid");
                            return simpleResult;
                        }
                    }, playFilterContext);

        }

        @Override
        public EssentialAction apply() {
            return next.apply();
        }
    };
}

}

Ответ 1

Ниже мое решение, доказанное в реальной жизни. Он находится в Scala, а не для Play, но для Scalatra, но базовая концепция такая же. Надеюсь, вы сможете выяснить, как переносить это на Java.

import org.slf4j.MDC
import java.util.{Map => JMap}
import scala.concurrent.{ExecutionContextExecutor, ExecutionContext}

object MDCHttpExecutionContext {

  def fromExecutionContextWithCurrentMDC(delegate: ExecutionContext): ExecutionContextExecutor =
    new MDCHttpExecutionContext(MDC.getCopyOfContextMap(), delegate)
}

class MDCHttpExecutionContext(mdcContext: JMap[String, String], delegate: ExecutionContext)
  extends ExecutionContextExecutor {

  def execute(runnable: Runnable): Unit = {
    val callingThreadMDC = MDC.getCopyOfContextMap()
    delegate.execute(new Runnable {
      def run() {
        val currentThreadMDC = MDC.getCopyOfContextMap()
        setContextMap(callingThreadMDC)
        try {
          runnable.run()
        } finally {
          setContextMap(currentThreadMDC)
        }
      }
    })
  }

  private[this] def setContextMap(context: JMap[String, String]): Unit = {
    Option(context) match {
      case Some(ctx) => {
        MDC.setContextMap(context)
      }
      case None => {
        MDC.clear()
      }
    }
  }

  def reportFailure(t: Throwable): Unit = delegate.reportFailure(t)
}

Вам нужно убедиться, что этот ExecutionContext используется во всех ваших асинхронных вызовах. Я достигаю этого через Injection Dependency, но есть разные способы. Это как я делаю это с subcut:

bind[ExecutionContext] idBy BindingIds.GlobalExecutionContext toSingle {
    MDCHttpExecutionContext.fromExecutionContextWithCurrentMDC(
      ExecutionContext.fromExecutorService(
        Executors.newFixedThreadPool(globalThreadPoolSize)
      )
    )
  }

Идея такого подхода заключается в следующем. MDC использует локальное хранилище потоков для атрибутов и их значений. Если один ваш запрос может работать на нескольких потоках, вам необходимо убедиться, что в новом потоке, который вы используете, используется правильный MDC. Для этого создается пользовательский исполнитель, который обеспечивает правильное копирование значений MDC в новый поток до того, как он начнет выполнять задание, которое вы ему назначили. Вы также должны убедиться, что когда поток завершит вашу задачу и продолжит что-то еще, вы ставите старые значения в свой MDC, потому что потоки из пула могут переключаться между различными запросами.