SpringWebflux中WebClient怎么打印日志

一、背景

去年高峰壓測(cè)的時(shí)候,有個(gè)服務(wù)是專(zhuān)門(mén)調(diào)用其它系統(tǒng)的,在測(cè)試接口http請(qǐng)求的時(shí)候,那TPS唰唰的往下掉,還專(zhuān)門(mén)用Arthas看了一下方法執(zhí)行時(shí)間,那家伙,我sleep 2s,看時(shí)間都3-4s了,所以就想著后面優(yōu)化一下。

所以,這不就到我們的主角SpringWebflux了,就想著用服務(wù)用Webflux,Http請(qǐng)求直接用WebClient了。但是有一個(gè)問(wèn)題就不得不思考了,怎么記錄請(qǐng)求的日志呢?這個(gè)日志很重要,必須的記錄的清楚,不然不用系統(tǒng)間溝通(扯皮背鍋)著實(shí)難搞。

二、怎么記錄日志?

2.1 思考記錄

打印日志,那第一步得去看看官網(wǎng)咯?看看官網(wǎng)提供了什么解決方案

2.1.1 官網(wǎng)尋答案

打開(kāi)官網(wǎng),翻到Filter這一頁(yè),發(fā)現(xiàn)著實(shí)有可以記錄日志的,我把官網(wǎng)代碼粘貼在下圖。

WebClient client = WebClient.builder()
        .filter((request, next) -> {
            ClientRequest filtered = ClientRequest.from(request)
                    .header("foo", "bar")
                    .build();

            return next.exchange(filtered);
        })
        .build();

可以看到,我們是可以拿到Request的,這讓我一頓高興,那這操作起來(lái)不簡(jiǎn)單? 但是呢,凡事不能高興太早。

拿到這ClientRequest去代碼一頓敲(clientRequest.xx),這個(gè)點(diǎn)都被我按爛了,發(fā)現(xiàn)并沒(méi)有可以獲取RequestBody的方法,只有Headers,URI... 可惜這都不是我要的啊。沒(méi)辦法,那就只有換別的方法了。

順便點(diǎn)了一下filter的ExchangeFilterFunction,發(fā)現(xiàn)里面有方法還能拿到Rresponse。代碼如下:

static ExchangeFilterFunction ofResponseProcessor(Function<ClientResponse, Mono<ClientResponse>> processor) {
  Assert.notNull(processor, "ClientResponse Function must not be null");
  return (request, next) -> next.exchange(request).flatMap(processor);
}

然后拿著Response又來(lái)一頓操作,可以拿到Response Body等,但是...但是...如果我們?cè)赗esponse里面把Response Body 使用掉,就會(huì)報(bào)錯(cuò):nested exception is java.lang.IllegalStateException: The client response body can only be consumed once。如圖所示。

提前使用ResponseBody

沒(méi)有辦法,查看了ClientResponse實(shí)現(xiàn)類(lèi)org.springframework.web.reactive.function.client.DefaultClientResponse,這個(gè)是個(gè)包權(quán)限的類(lèi),雖然很想直接用,比如:requestDescription(URL路徑),getBody, 但是是在拿不到啊~~難受

既然如此,那就只有從別的地方尋找。

2.1.2 其它方案

在官網(wǎng)上WebFlux有三種方案,分別是集成Jetty, Netty, HttpComponent5。由于我使用的是HttpComponent5,所以就直接從http這個(gè)尋找方案了。

尋尋覓覓,找到了了相似的方案,也可以添加Interceptor。我就直接貼出代碼,如下圖。

Http添加攔截器

但是在實(shí)現(xiàn)時(shí),發(fā)現(xiàn)了有兩個(gè)問(wèn)題。

問(wèn)題一

打印Request日志的時(shí)候會(huì)出現(xiàn)兩次。

問(wèn)題二

打印Response的時(shí)候也會(huì)出現(xiàn)nested exception is java.lang.IllegalStateException: The client response body can only be consumed once這個(gè)異常。

出現(xiàn)這個(gè)問(wèn)題,沒(méi)有發(fā)現(xiàn)啥好用的解決方案。

2.1.3 Google

后來(lái)就想著想換成Jetty和Netty,就google了一波,我直接把連接貼出來(lái),就不過(guò)多描述了。 連接如下:

Logging Spring WebClient Calls | Baeldung 這個(gè)方案也嘗試了一波,但是效果如上。會(huì)出現(xiàn)問(wèn)題一,或者在獲取Body的時(shí)候出現(xiàn)問(wèn)題。

2.2 解決方案

在經(jīng)歷了好幾天Debug和測(cè)試后,就選擇了妥協(xié)方案,直接在webClient請(qǐng)求的時(shí)候打印日志。這還算是一個(gè)完美的解決方案。

我貼出一部分代碼,如下。

    /**
     * 打印全流程日志,需要外部傳入?yún)?shù)
     */
    public static Function<ClientResponse, Mono<String>> logging(String url, String method, Object reqBody) {
        return (clientResponse -> clientResponse.bodyToMono(String.class).doOnSuccess(body -> {
            if (log.isDebugEnabled()) {
                log.info("\n" +
                                "TraceId      : {}, {}\n" +
                                "URI          : {}, \n" +
                                "Param        : {}, \n" +
                                "RespHeader   : {}, \n" +
                                "RespStatus   : {}, \n" +
                                "Response     : {}", clientResponse.logPrefix(), method, url, JsonUtils.toJson(reqBody),
                        clientResponse.headers().asHttpHeaders(), clientResponse.rawStatusCode(),
                        StringUtils.abbreviate(body, 4000));
            }
        }));
    }

在獲取到Body的時(shí)候,直接轉(zhuǎn)化成String.class,如果直接轉(zhuǎn)成ParameterizedTypeReference就會(huì)失去部分原請(qǐng)求的數(shù)據(jù),這樣在扯皮的時(shí)候不好找證據(jù)呀?。ㄎ覀兇蛴∏逦娜罩臼菫榱烁玫牟樵?xún)問(wèn)題,不是扯皮︿( ̄︶ ̄)︿ )

這樣打印出來(lái)的日志就是我們想要的了,舒服啊~ 這樣看起來(lái)才舒服。

還寫(xiě)了一些其它的小工具,獲取連接,使用方式如下:

小工具使用方式

三、總結(jié)

以上是探索WebClient的打印日志的方式,還有不足,歡迎大家討論,提出更好的方式。謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容