前言:本人从08年毕业到现在一直从事开发相关工作。涉及到java、C(嵌入式)、C#、Object-c、Python、VUE等相关语言。但是一直没有写博客的习惯,也懒得写。因为工作经常很忙。周末又想葛优躺,但是现在已经有年龄危机,这个行业对大龄来说真的不友好。和古代的妓院工作人员待遇一样,吃青春饭!
近期在做物联网相关项目,同时在公司解决了netty做游戏服务器时并发的问题。接触到一个新的编程方式:响应式编程!在整个框架的搭建过程中碰到一些问题。这里摘出一个未来能大有用处的功能分享下:如何解决响应式编程中日志调用链的串联。(这里真的要怀念一下,以前用VC++的时候非常喜欢MSDN,现在呢?很难有这么好的帮助文档了!)
第一步创建WebFilter过滤器
在重写filter方法中做如下操做:
@Configuration
@Slf4j
public class LogWebFilter implements WebFilter {
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
//这里生成请求id或者通过网关传入
.
.
.
ServerWebExchange.Builder ex = exchange.mutate();
String requestId = 自己的生成逻辑;
return chain.filter(ex.build()).contextWrite(context -> {
Context contextTmp = context.put(LogMdcConfiguration.PRINT_LOG_ID, requestId);
return contextTmp;
}).doFinally(signalType -> {
long endTime = System.currentTimeMillis();
MDC.put(LogMdcConfiguration.PRINT_LOG_ID, requestId);
log.info("api end time:{}, total time:{}", endTime, endTime - startTime);
MDC.remove(LogMdcConfiguration.PRINT_LOG_ID);
});
}
.//其他业务逻辑
.
.
}
其中的重点是:contextWrite中的,context.put(PRINT_LOG_ID, requestId);
因为在这个Reactor框架中,响应式编程会有个上下文,其中会随着调用链一直传下去。可以利用这一点把打印日志的调用链id放在上下文中
第二步在每次线程的切换时MDC.put()
代码如下:
@Configuration
public class LogMdcConfiguration {
public static String PRINT_LOG_ID = "requestId";
@PostConstruct
public void contextOperatorHook() {
Hooks.onEachOperator(PRINT_LOG_ID, Operators.lift((r, c) ->{
Context ctx = c.currentContext();
if(ctx.hasKey(PRINT_LOG_ID)){
MDC.put(PRINT_LOG_ID, ctx.get(PRINT_LOG_ID));
}
return new MdcContextSubscriber(c);
}));
}
@PreDestroy
public void cleanupHook() {
Hooks.resetOnEachOperator(PRINT_LOG_ID);
}
}
这里用到了@PostConstruct和@PreDestroy注解。注解功能可以想见java的说明。其中的重点Hooks,我想看到这个名字大部分人已经理解了其中的原理了。在每次线程的切换过程中都会调用onEachOperator方法。我们在这里调用MDC.put操做,就能在当前线程中log的上下文插入对应的值。这样我们只需要在日志配置文件中配置如下即可实现打印改值([%X{requestId}]):
<property name="log.pattern" value="%d{yyyy-HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] [%X{requestId}]- %msg%n" />
第三步切出时MDC.remove()
在第二步中,大家看到了
return new MdcContextSubscriber(c);
这个MdcContextSubscriber就是实现了CoreSubscriber接口,分别在onComplete()和onError(Throwable throwable)中调用MDC.remove即可,部分代码如下:
class MdcContextSubscriber<T> implements CoreSubscriber<T> {
.
.
.//其他业务代码省略
@Override
public void onComplete() {
coreSubscriber.onComplete();
MDC.remove(PRINT_LOG_ID);
}
@Override
public void onError(Throwable throwable) {
coreSubscriber.onError(throwable);
MDC.remove(PRINT_LOG_ID);
}
}
经过以上步骤就可以在整个调用链的日志中看到相关ID了
不过这里还有一个问题,就是最后一次调用日志中的日志。比如第一步的doFinally中,日志时不会打印id的,因为onComplete会在他前面调用,所以这里需要单独补一下。
希望以上能给到大家帮助。也可以留言大家一起讨论,共同进步