Ken*_*Ken 6 logging spring-mvc logback slf4j mdc
使用 Spring MVC,我有以下设置:
我正在尝试使用 MDC(或 ThreadLocal 对象)为处理请求所涉及的所有组件收集上下文信息。
我可以从@Async 线程正确检索 MDC 上下文信息。但是,如果@Async 线程要将上下文信息添加到 MDC,我现在如何将 MDC 上下文信息编组到处理响应的线程?
任务装饰器
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();
}
};
}
Run Code Online (Sandbox Code Playgroud)
}
异步方法
@Async
public CompletableFuture<String> doSomething_Async() {
MDC.put("doSomething", "started");
return doit();
}
Run Code Online (Sandbox Code Playgroud)
日志过滤器
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();
}
Run Code Online (Sandbox Code Playgroud)
}
小智 7
创建一个 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();
}
};
}
}
Run Code Online (Sandbox Code Playgroud)
现在一切都好。快乐编码
我希望您已经解决了问题,但如果没有解决,这里有一个解决方案。
您所要做的一切可以概括为以下 2 个简单步骤:
MdcTaskDecorator
。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);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 -3
我有一些解决方案,大致分为Callable(用于@Async),AsyncExecutionInterceptor(用于@Async),CallableProcessingInterceptor(用于控制器)。
1.将上下文信息放入@Async线程的Callable解决方案:
关键是使用ContextAwarePoolExecutor来替换@Async的默认执行器:
@Configuration
Run Code Online (Sandbox Code Playgroud)
公共类 DemoExecutorConfig {
@Bean("demoExecutor")
public Executor contextAwarePoolExecutor() {
return new ContextAwarePoolExecutor();
}
Run Code Online (Sandbox Code Playgroud)
}
并且 ContextAwarePoolExecutor 用 ContextAwareCallable 覆盖了 Submit 和 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;
}
Run Code Online (Sandbox Code Playgroud)
}
为了方便起见,ThreadContextContainer 只是一个存储信息的 pojo:
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;
}
Run Code Online (Sandbox Code Playgroud)
}
ContextAwareCallable(原始任务的 Callable 代理)在原始任务执行其调用方法之前覆盖调用方法以存储 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 {
}
}
}
Run Code Online (Sandbox Code Playgroud)
}
最后,将 @Async 与配置的 bean“demoExecutor”一起使用,如下所示:@Async("demoExecutor")
void yourTaskMethod();
2.关于您回复的处理问题:
遗憾的是,我确实没有经过验证的解决方案。也许 org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke 可以解决这个问题。
我认为它没有解决方案来处理 ServletLoggingFilter 的响应。因为Async方法会立即返回。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 {
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
7260 次 |
最近记录: |