173

私たちのソフトウェアでは、 MDCを広範囲に使用して、Web リクエストのセッション ID やユーザー名などを追跡しています。これは、元のスレッドで実行している間は正常に機能します。

ただし、バックグラウンドで処理する必要があるものがたくさんあります。そのために、いくつかの自己ロール型非同期実行サービスとともにjava.concurrent.ThreadPoolExecutorおよびクラスを使用します。これらのサービスはすべて、独自のスレッド プールを管理します。java.util.Timer

このような環境で MDC を使用することについて、Logback のマニュアルには次のように書かれています。

マップされた診断コンテキストのコピーは、開始スレッドからワーカー スレッドに常に継承できるとは限りません。これは、スレッド管理に java.util.concurrent.Executors が使用されている場合です。たとえば、newCachedThreadPool メソッドは ThreadPoolExecutor を作成し、他のスレッド プーリング コードと同様に、複雑なスレッド作成ロジックを備えています。

このような場合、タスクをエグゼキュータに送信する前に、元の (マスター) スレッドで MDC.getCopyOfContextMap() を呼び出すことをお勧めします。タスクが実行されると、最初のアクションとして MDC.setContextMapValues() を呼び出して、元の MDC 値の格納されたコピーを新しい Executor 管理スレッドに関連付ける必要があります。

これは問題ありませんが、これらの呼び出しを追加することを非常に忘れがちであり、手遅れになるまで問題を認識する簡単な方法はありません。Log4j の唯一の兆候は、ログに MDC 情報がないことです。Logback を使用すると、古い MDC 情報が得られます (トレッド プールのスレッドは、最初に実行されたタスクから MDC を継承するため)。どちらも本番システムでは深刻な問題です。

私たちの状況が特別だとは思いませんが、この問題についてはウェブ上であまり見つけることができませんでした。どうやら、これは多くの人がぶつかるものではないので、回避する方法があるはずです。ここで何が間違っていますか?

4

8 に答える 8

92

はい、これは私も遭遇した一般的な問題です。いくつかの回避策があります (説明されているように手動で設定するなど)。

  • MDC を一貫して設定します。
  • MDC が間違っているがそれがわからないという暗黙のバグを回避します。と
  • スレッド プールの使用方法の変更を最小限に抑えます (例: Everywhere でのサブクラス化CallableMyCallableまたは同様の醜さ)。

これら 3 つのニーズを満たす、私が使用するソリューションを次に示します。コードは自明であるべきです。

MoreExecutors.listeningDecorator()(補足として、Guava のを使用する場合、このエグゼキュータを作成して Guava の に供給することができますListanableFuture。)

import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.*;

/**
 * A SLF4J MDC-compatible {@link ThreadPoolExecutor}.
 * <p/>
 * In general, MDC is used to store diagnostic information (e.g. a user's session id) in per-thread variables, to facilitate
 * logging. However, although MDC data is passed to thread children, this doesn't work when threads are reused in a
 * thread pool. This is a drop-in replacement for {@link ThreadPoolExecutor} sets MDC data before each task appropriately.
 * <p/>
 * Created by jlevy.
 * Date: 6/14/13
 */
public class MdcThreadPoolExecutor extends ThreadPoolExecutor {

    final private boolean useFixedContext;
    final private Map<String, Object> fixedContext;

    /**
     * Pool where task threads take MDC from the submitting thread.
     */
    public static MdcThreadPoolExecutor newWithInheritedMdc(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                                                            TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return new MdcThreadPoolExecutor(null, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    /**
     * Pool where task threads take fixed MDC from the thread that creates the pool.
     */
    @SuppressWarnings("unchecked")
    public static MdcThreadPoolExecutor newWithCurrentMdc(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                                                          TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return new MdcThreadPoolExecutor(MDC.getCopyOfContextMap(), corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue);
    }

    /**
     * Pool where task threads always have a specified, fixed MDC.
     */
    public static MdcThreadPoolExecutor newWithFixedMdc(Map<String, Object> fixedContext, int corePoolSize,
                                                        int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                                        BlockingQueue<Runnable> workQueue) {
        return new MdcThreadPoolExecutor(fixedContext, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    private MdcThreadPoolExecutor(Map<String, Object> fixedContext, int corePoolSize, int maximumPoolSize,
                                  long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        this.fixedContext = fixedContext;
        useFixedContext = (fixedContext != null);
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> getContextForTask() {
        return useFixedContext ? fixedContext : MDC.getCopyOfContextMap();
    }

    /**
     * All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)
     * all delegate to this.
     */
    @Override
    public void execute(Runnable command) {
        super.execute(wrap(command, getContextForTask()));
    }

    public static Runnable wrap(final Runnable runnable, final Map<String, Object> context) {
        return new Runnable() {
            @Override
            public void run() {
                Map previous = MDC.getCopyOfContextMap();
                if (context == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(context);
                }
                try {
                    runnable.run();
                } finally {
                    if (previous == null) {
                        MDC.clear();
                    } else {
                        MDC.setContextMap(previous);
                    }
                }
            }
        };
    }
}
于 2013-10-12T02:02:19.910 に答える
28

同様の問題が発生しました。ThreadPoolExecutor を拡張し、before/afterExecute メソッドをオーバーライドして、新しいスレッドを開始/停止する前に必要な MDC 呼び出しを行うことができます。

于 2011-06-20T19:08:16.190 に答える
2

以前に投稿されたソリューションと同様に、 と のメソッドnewTaskForは、.RunnableCallableRunnableFuture

注: したがって、メソッドの代わりに のメソッドを呼び出す必要がありexecutorServiceます。submitexecute

の場合、ScheduledThreadPoolExecutor代わりdecorateTaskにメソッドが上書きされます。

于 2016-09-12T16:59:42.397 に答える