一静尼、背景
去年高峰壓測的時候捉貌,有個服務是專門調(diào)用其它系統(tǒng)的霹菊,在測試接口http請求的時候镰官,那TPS唰唰的往下掉提前,還專門用Arthas看了一下方法執(zhí)行時間,那家伙泳唠,我sleep 2s狈网,看時間都3-4s了,所以就想著后面優(yōu)化一下笨腥。
所以拓哺,這不就到我們的主角SpringWebflux了,就想著用服務用Webflux脖母,Http請求直接用WebClient了士鸥。但是有一個問題就不得不思考了,怎么記錄請求的日志呢谆级?這個日志很重要烤礁,必須的記錄的清楚,不然不用系統(tǒng)間溝通(扯皮背鍋)著實難搞肥照。
二脚仔、怎么記錄日志?
2.1 思考記錄
打印日志舆绎,那第一步得去看看官網(wǎng)咯鲤脏?看看官網(wǎng)提供了什么解決方案
2.1.1 官網(wǎng)尋答案
打開官網(wǎng),翻到Filter這一頁,發(fā)現(xiàn)著實有可以記錄日志的猎醇,我把官網(wǎng)代碼粘貼在下圖窥突。
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
可以看到,我們是可以拿到Request的硫嘶,這讓我一頓高興波岛,那這操作起來不簡單? 但是呢音半,凡事不能高興太早则拷。
拿到這ClientRequest去代碼一頓敲(clientRequest.xx),這個點都被我按爛了曹鸠,發(fā)現(xiàn)并沒有可以獲取RequestBody的方法煌茬,只有Headers,URI... 可惜這都不是我要的啊彻桃。沒辦法坛善,那就只有換別的方法了。
順便點了一下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又來一頓操作,可以拿到Response Body等肆饶,但是...但是...如果我們在Response里面把Response Body 使用掉改衩,就會報錯:nested exception is java.lang.IllegalStateException: The client response body can only be consumed once
。如圖所示驯镊。
沒有辦法葫督,查看了ClientResponse實現(xiàn)類org.springframework.web.reactive.function.client.DefaultClientResponse
,這個是個包權(quán)限的類板惑,雖然很想直接用橄镜,比如:requestDescription(URL路徑),getBody, 但是是在拿不到啊~~難受
既然如此冯乘,那就只有從別的地方尋找洽胶。
2.1.2 其它方案
在官網(wǎng)上WebFlux有三種方案,分別是集成Jetty, Netty, HttpComponent5裆馒。由于我使用的是HttpComponent5姊氓,所以就直接從http這個尋找方案了。
尋尋覓覓领追,找到了了相似的方案他膳,也可以添加Interceptor。我就直接貼出代碼绒窑,如下圖棕孙。
但是在實現(xiàn)時,發(fā)現(xiàn)了有兩個問題。
問題一
打印Request日志的時候會出現(xiàn)兩次蟀俊。
問題二
打印Response的時候也會出現(xiàn)nested exception is java.lang.IllegalStateException: The client response body can only be consumed once
這個異常钦铺。
出現(xiàn)這個問題,沒有發(fā)現(xiàn)啥好用的解決方案肢预。
2.1.3 Google
后來就想著想換成Jetty和Netty矛洞,就google了一波,我直接把連接貼出來烫映,就不過多描述了沼本。 連接如下:
Logging Spring WebClient Calls | Baeldung 這個方案也嘗試了一波,但是效果如上锭沟。會出現(xiàn)問題一抽兆,或者在獲取Body的時候出現(xiàn)問題。
2.2 解決方案
在經(jīng)歷了好幾天Debug和測試后族淮,就選擇了妥協(xié)方案辫红,直接在webClient請求的時候打印日志。這還算是一個完美的解決方案祝辣。
我貼出一部分代碼贴妻,如下。
/**
* 打印全流程日志蝙斜,需要外部傳入?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的時候名惩,直接轉(zhuǎn)化成String.class
,如果直接轉(zhuǎn)成ParameterizedTypeReference
就會失去部分原請求的數(shù)據(jù)乍炉,這樣在扯皮
的時候不好找證據(jù)呀>钇(我們打印清晰的日志是為了更好的查詢問題滤馍,不是扯皮︿( ̄︶ ̄)︿ )
這樣打印出來的日志就是我們想要的了岛琼,舒服啊~ 這樣看起來才舒服。
還寫了一些其它的小工具巢株,獲取連接槐瑞,使用方式如下:
三、總結(jié)
以上是探索WebClient的打印日志的方式阁苞,還有不足困檩,歡迎大家討論,提出更好的方式那槽。謝謝悼沿!