使用@Async和TaskDecorator记录MDC

时间:2017-08-25 22:31:48

标签: spring-mvc logging logback slf4j mdc

使用Spring MVC,我有以下设置:

  1. 用于记录请求的AbstractRequestLoggingFilter派生过滤器。
  2. 一个TaskDecorator,用于封送从Web请求线程到@Async线程的MDC上下文映射。
  3. 我尝试使用MDC(或ThreadLocal对象)为处理请求所涉及的所有组件收集上下文信息。

    我可以从@Async线程正确检索MDC上下文信息。但是,如果@Async线程要向MDC添加上下文信息,我现在如何将MDC上下文信息封送到处理响应的线程?

    TaskDecorator

    public class MdcTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // Web thread context
        // Get the logging MDC context
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
    
        return () -> {
            try {
                // @Async thread context
                // Restore the web thread MDC context
                if(contextMap != null) {
                    MDC.setContextMap(contextMap);
                }
                else {
                    MDC.clear();
                }
    
                // Run the new thread
                runnable.run();
            }
            finally {
                MDC.clear();
            }
        };
    }
    

    }

    异步方法

    @Async
    public CompletableFuture<String> doSomething_Async() {
        MDC.put("doSomething", "started");
        return doit();
    }
    

    记录过滤器

    public class ServletLoggingFilter extends AbstractRequestLoggingFilter {
    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        MDC.put("webthread", Thread.currentThread().getName()); // Will be webthread-1
    }
    
    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        MDC.put("responsethread", Thread.currentThread().getName()); // Will be webthread-2
        String s = MDC.get("doSomething"); // Will be null
    
        // logthis();
    }
    

    }

3 个答案:

答案 0 :(得分:2)

我希望您已经解决了这个问题,但是如果您没有解决,那么这里有一个解决方案。
您要做的所有事情都可以概括为以下两个简单步骤:

  1. 继续上课MdcTaskDecorator
  2. 为您的主类扩展AsyncConfigurerSupport,并覆盖getAsyncExecutor()以使用您自定义的装饰器设置装饰器。
    public class AsyncTaskdecoratorApplication extends AsyncConfigurerSupport {
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setTaskDecorator(new MdcTaskDecorator());
            executor.initialize();
            return executor;
        }

        public static void main(String[] args) {
            SpringApplication.run(AsyncTaskdecoratorApplication.class, args);
        }
    }

答案 1 :(得分:0)

创建一个 bean,它将 MDC 属性从父线程传递到后继线程。

@Configuration
@Slf4j
public class AsyncMDCConfiguration {
  @Bean
  public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setTaskDecorator(new MDCTaskDecorator());//MDCTaskDecorator i s a custom created  class thet implements  TaskDecorator  that is reponsible for passing on the MDC properties 
    executor.initialize();
    return executor;
  }
}


@Slf4j
public class MDCTaskDecorator implements TaskDecorator {

  @Override
  public Runnable decorate(Runnable runnable) {
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
      try {
        MDC.setContextMap(contextMap);
        runnable.run();
      } finally {
        MDC.clear();
      }
    };
  }
}

现在一切都好。快乐编码

答案 2 :(得分:-2)

我有一些解决方案,大致分为Callable(对于@Async),AsyncExecutionInterceptor(对于@Async),CallableProcessingInterceptor(对于控制器)。

1。用于将上下文信息放入@Async线程的Callable解决方案:

关键是使用ContextAwarePoolExecutor替换@Async的默认执行器:

@Configuration

公共类DemoExecutorConfig {

@Bean("demoExecutor")
public Executor contextAwarePoolExecutor() {
    return new ContextAwarePoolExecutor();
}

}

并且ContextAwarePoolExecutor覆盖了内部具有ContextAwareCallable的commit和submitListenable方法:

public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {

private static final long serialVersionUID = 667815067287186086L;

@Override
public <T> Future<T> submit(Callable<T> task) {
    return super.submit(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}

@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
    return super.submitListenable(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}

/**
 * set infos what we need
 */
private ThreadContextContainer newThreadContextContainer() {
    ThreadContextContainer container = new ThreadContextContainer();
    container.setRequestAttributes(RequestContextHolder.currentRequestAttributes());
    container.setContextMapOfMDC(MDC.getCopyOfContextMap());
    return container;
}

}

ThreadContextContainer只是为了方便存储信息的一种方式:

public class ThreadContextContainer implements Serializable {

private static final long serialVersionUID = -6809291915300091330L;

private RequestAttributes requestAttributes;
private Map<String, String> contextMapOfMDC;

public RequestAttributes getRequestAttributes() {
    return requestAttributes;
}

public Map<String, String> getContextMapOfMDC() {
    return contextMapOfMDC;
}

public void setRequestAttributes(RequestAttributes requestAttributes) {
    this.requestAttributes = requestAttributes;
}

public void setContextMapOfMDC(Map<String, String> contextMapOfMDC) {
    this.contextMapOfMDC = contextMapOfMDC;
}

}

ContextAwareCallable(原始任务的可调用代理)在原始任务执行其调用方法之前,将调用方法覆盖存储MDC或其他上下文信息:

public class ContextAwareCallable<T> implements Callable<T> {

/**
 * the original task
 */
private Callable<T> task;

/**
 * for storing infos what we need
 */
private ThreadContextContainer threadContextContainer;

public ContextAwareCallable(Callable<T> task, ThreadContextContainer threadContextContainer) {
    this.task = task;
    this.threadContextContainer = threadContextContainer;
}

@Override
public T call() throws Exception {
    // set infos
    if (threadContextContainer != null) {
        RequestAttributes requestAttributes = threadContextContainer.getRequestAttributes();
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes);
        }
        Map<String, String> contextMapOfMDC = threadContextContainer.getContextMapOfMDC();
        if (contextMapOfMDC != null) {
            MDC.setContextMap(contextMapOfMDC);
        }
    }

    try {
        // execute the original task
        return task.call();
    } finally {
        // clear infos after task completed
        RequestContextHolder.resetRequestAttributes();
        try {
            MDC.clear();
        } finally {
        }
    }
}

}

最后,将@Async与配置的bean“ demoExecutor”一起使用,像这样:@Async("demoExecutor") void yourTaskMethod();

2。关于您处理回复的问题:

遗憾地告诉我我确实没有经过验证的解决方案。也许org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke可以解决这个问题。

我认为它没有解决方案可以通过ServletLoggingFilter处理响应。因为异步方法将立即返回。 afterRequest方法立即执行并在Async方法执行之前返回。除非您同步等待Async方法完成执行,否则您将无法获得所需的结果。

但是,如果您只想记录某些内容,则可以在原始任务执行其调用方法之后,将这些代码添加到我的示例ContextAwareCallable中:

try {
        // execute the original task
        return task.call();
    } finally {
        String something = MDC.get("doSomething"); // will not be null
        // logthis(something);

        // clear infos after task completed
        RequestContextHolder.resetRequestAttributes();
        try {
            MDC.clear();
        } finally {
        }
    }