【问题标题】:Track context specific data across threads跨线程跟踪特定于上下文的数据
【发布时间】:2015-02-03 18:45:21
【问题描述】:

我在 Play 中知道这一点!使用 Scala 时没有 Http.context 可用,因为这个想法是利用隐式在堆栈周围传递任何数据。但是,当您需要一条可用于整个上下文的信息时,这似乎需要通过很多样板。

更具体地说,我感兴趣的是跟踪从请求标头传递的 UUID,并将其提供给任何记录器,以便每个请求都有自己的唯一标识符。我希望这与调用记录器(或日志包装器)的任何人都无缝

来自 .NET 背景的 http 上下文通过异步调用流动,这也可以通过 WCF 中的调用上下文来实现。此时,您可以向记录器注册一个函数,以根据“%requestID%”之类的记录模式返回请求的当前 uuid。

构建一个更大的分布式系统,您需要能够跨多个堆栈关联请求。

但是,作为 scala 和 play 的新手,我什至不知道在哪里寻找一种方法来做到这一点?

【问题讨论】:

标签: scala playframework


【解决方案1】:

您在 Java 中寻找的东西称为映射诊断上下文或 MDC(至少由 SLF4J 表示)-here's an article I found that details how to set this up for Play。为了为未来的访问者保留详细信息,这里是用于 MDC 传播的 Akka 调度程序的代码:

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)
    }
  }

}

然后您可以使用 MDC.put 在 MDC 中设置值并使用 MDC.remove 将其删除(或者,如果您需要从一组同步调用中添加和删除一些上下文,请查看 putCloseable):

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")

并使用%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>

in the linked blog post 提供了更多关于调整 ExecutionContext 的详细信息,Play 使用该 ExecutionContext 正确传播 MDC 上下文(作为替代方法)。

【讨论】:

  • 嗨,这个 MDC Dispatcher 是否可以在不是 play framework 应用的 akka 应用上工作?我无法让它与我的 akka 应用程序一起工作,我将 ActorLogging 特征混入我的演员,并将我的 akka loggers 设置为 akka.event.slf4j.Slf4jLoggerlogging-filter 设置为 akka.event.slf4j.Slf4jLoggingFilter。然后我在 application.conf 中将 akka actor default-dispatcher type 设置为 "some.package.monitoring. MDCPropagatingDispatcherConfigurator"。我做错了吗?
  • @Frank - 很难在没有看到更多代码的情况下分辨。值得提出一个单独的问题,参考这个。
  • 嗨,肖恩,我为此创建了一个新问题 - stackoverflow.com/questions/30091356/…,非常感谢!!
猜你喜欢
  • 2015-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-18
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多