基于webflux Reactor 响应式编程log调用链id

前言:本人从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会在他前面调用,所以这里需要单独补一下。

希望以上能给到大家帮助。也可以留言大家一起讨论,共同进步

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dcwulin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值