SpringCloud?Gateway之請(qǐng)求應(yīng)答日志打印方式
Gateway請(qǐng)求應(yīng)答日志打印
請(qǐng)求應(yīng)答日志時(shí)在日常開發(fā)調(diào)試問題的重要手段之一,那么如何基于Spring Cloud Gateway做呢,請(qǐng)看我上代碼。
第一步
創(chuàng)建RecorderServerHttpRequestDecorator,緩存請(qǐng)求參數(shù),解決body只能讀一次問題。
public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator {? ? ? private final List<DataBuffer> dataBuffers = new ArrayList<>();? ? ? public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) { ? ? ? ? super(delegate); ? ? ? ? super.getBody().map(dataBuffer -> { ? ? ? ? ? ? dataBuffers.add(dataBuffer); ? ? ? ? ? ? return dataBuffer; ? ? ? ? }).subscribe(); ? ? } ? ? ? @Override ? ? public Flux<DataBuffer> getBody() { ? ? ? ? return copy(); ? ? } ? ? ? private Flux<DataBuffer> copy() { ? ? ? ? return Flux.fromIterable(dataBuffers) ? ? ? ? ? ? ? ? .map(buf -> buf.factory().wrap(buf.asByteBuffer())); ? ? }?? }
第二步
創(chuàng)建訪問日志全局過濾器,然后在此過濾器進(jìn)行日志構(gòu)造。
@Slf4j public class AccessLogGlobalFilter implements GlobalFilter , Ordered {? ? ? private static final String REQUEST_PREFIX = "Request Info [ ";? ? ? private static final String REQUEST_TAIL = " ]";? ? ? private static final String RESPONSE_PREFIX = "Response Info [ ";? ? ? private static final String RESPONSE_TAIL = " ]";? ? ? private StringBuilder normalMsg = new StringBuilder(); ? ? ? @Override ? ? public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ? ? ? ? ServerHttpRequest request = exchange.getRequest(); ? ? ? ? RecorderServerHttpRequestDecorator requestDecorator = new RecorderServerHttpRequestDecorator(request); ? ? ? ? InetSocketAddress address = requestDecorator.getRemoteAddress(); ? ? ? ? HttpMethod method = requestDecorator.getMethod(); ? ? ? ? URI url = requestDecorator.getURI(); ? ? ? ? HttpHeaders headers = requestDecorator.getHeaders(); ? ? ? ? Flux<DataBuffer> body = requestDecorator.getBody(); ? ? ? ? //讀取requestBody傳參 ? ? ? ? AtomicReference<String> requestBody = new AtomicReference<>(""); ? ? ? ? body.subscribe(buffer -> { ? ? ? ? ? ? CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); ? ? ? ? ? ? requestBody.set(charBuffer.toString()); ? ? ? ? }); ? ? ? ? String requestParams = requestBody.get(); ? ? ? ? normalMsg.append(REQUEST_PREFIX); ? ? ? ? normalMsg.append(";header=").append(headers); ? ? ? ? normalMsg.append(";params=").append(requestParams); ? ? ? ? normalMsg.append(";address=").append(address.getHostName() + address.getPort()); ? ? ? ? normalMsg.append(";method=").append(method.name()); ? ? ? ? normalMsg.append(";url=").append(url.getPath()); ? ? ? ? normalMsg.append(REQUEST_TAIL); ? ? ? ? ? ServerHttpResponse response = exchange.getResponse(); ? ? ? ? ? DataBufferFactory bufferFactory = response.bufferFactory(); ? ? ? ? normalMsg.append(RESPONSE_PREFIX); ? ? ? ? ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) { ? ? ? ? ? ? @Override ? ? ? ? ? ? public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { ? ? ? ? ? ? ? ? if (body instanceof Flux) { ? ? ? ? ? ? ? ? ? ? Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body; ? ? ? ? ? ? ? ? ? ? return super.writeWith(fluxBody.map(dataBuffer -> { ? ? ? ? ? ? ? ? ? ? ? ? // probably should reuse buffers ? ? ? ? ? ? ? ? ? ? ? ? byte[] content = new byte[dataBuffer.readableByteCount()]; ? ? ? ? ? ? ? ? ? ? ? ? dataBuffer.read(content); ? ? ? ? ? ? ? ? ? ? ? ? String responseResult = new String(content, Charset.forName("UTF-8")); ? ? ? ? ? ? ? ? ? ? ? ? normalMsg.append("status=").append(this.getStatusCode()); ? ? ? ? ? ? ? ? ? ? ? ? normalMsg.append(";header=").append(this.getHeaders()); ? ? ? ? ? ? ? ? ? ? ? ? normalMsg.append(";responseResult=").append(responseResult); ? ? ? ? ? ? ? ? ? ? ? ? normalMsg.append(RESPONSE_TAIL); ? ? ? ? ? ? ? ? ? ? ? ? log.info(normalMsg.toString()); ? ? ? ? ? ? ? ? ? ? ? ? return bufferFactory.wrap(content); ? ? ? ? ? ? ? ? ? ? })); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? return super.writeWith(body); // if body is not a flux. never got there. ? ? ? ? ? ? } ? ? ? ? }; ? ? ? ? ? return chain.filter(exchange.mutate().request(requestDecorator).response(decoratedResponse).build()); ? ? } ? ? ? @Override ? ? public int getOrder() { ? ? ? ? return -2; ? ? }? }
最后結(jié)果:
Request Info [ ;header={cache-control=[no-cache], Postman-Token=[790488a5-a284-4a0e-968f-1b588cb26688], Content-Type=[application/json], User-Agent=[PostmanRuntime/3.0.9], Accept=[*/*], Host=[localhost:8084], cookie=[JSESSIONID=E161AC22204E626FBE6E96EE7B62EE70], accept-encoding=[gzip, deflate], content-length=[13], Connection=[keep-alive]};params={"name":"ss"};address=0:0:0:0:0:0:0:159621;method=POST;url=/account/testBody ]Response Info [ ;status=200;header={Content-Type=[text/plain;charset=UTF-8], Content-Length=[41], Date=[Mon, 18 Mar 2019 08:21:57 GMT]};responseResult=account hellowordAccountEntity{name='ss'} ]
以上代碼即可完成請(qǐng)求應(yīng)答日志打印功能。
Gateway全局請(qǐng)求日志打印
實(shí)現(xiàn)GlobalFilter則所有該自定義Filter會(huì)對(duì)所有的路由生效。
把請(qǐng)求體的數(shù)據(jù)存入exchange
便于打印日志時(shí)獲取
package com.qykj.gateway.filter; import com.qykj.gateway.ConstantFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** ?* @calssName AppCacheRequestBodyFilter ?* @Description 將 request body 中的內(nèi)容 copy 一份,記錄到 exchange 的一個(gè)自定義屬性中 ?* @Author jiangshaoneng ?* @DATE 2020/9/27 14:42 ?*/ public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered { ? ? private static final Logger logger = LoggerFactory.getLogger(GlobalCacheRequestBodyFilter.class); ? ? private int order; ? ? @Override ? ? public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ? ? ? ? //logger.info("GlobalCacheRequestBodyFilter ..."); ? ? ? ? // 將 request body 中的內(nèi)容 copy 一份,記錄到 exchange 的一個(gè)自定義屬性中 ? ? ? ? Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null); ? ? ? ? // 如果已經(jīng)緩存過,略過 ? ? ? ? if (cachedRequestBodyObject != null) { ? ? ? ? ? ? return chain.filter(exchange); ? ? ? ? } ? ? ? ? // 如果沒有緩存過,獲取字節(jié)數(shù)組存入 exchange 的自定義屬性中 ? ? ? ? return DataBufferUtils.join(exchange.getRequest().getBody()) ? ? ? ? ? ? ? ? .map(dataBuffer -> { ? ? ? ? ? ? ? ? ? ? byte[] bytes = new byte[dataBuffer.readableByteCount()]; ? ? ? ? ? ? ? ? ? ? dataBuffer.read(bytes); ? ? ? ? ? ? ? ? ? ? DataBufferUtils.release(dataBuffer); ? ? ? ? ? ? ? ? ? ? return bytes; ? ? ? ? ? ? ? ? }).defaultIfEmpty(new byte[0]) ? ? ? ? ? ? ? ? .doOnNext(bytes -> exchange.getAttributes().put(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, bytes)) ? ? ? ? ? ? ? ? .then(chain.filter(exchange)); ? ? } ? ? @Override ? ? public int getOrder() { ? ? ? ? return this.order; ? ? } ? ? public GlobalCacheRequestBodyFilter(int order){ ? ? ? ? this.order = order; ? ? } }
編寫全局日志攔截器代碼
/** ?* @calssName LogFilter ?* @Description 全局日志打印,請(qǐng)求日志以及返回日志,并在返回結(jié)果日志中添加請(qǐng)求時(shí)間 ?* @Author jiangshaoneng ?* @DATE 2020/9/25 14:54 ?*/ public class GlobalLogFilter implements GlobalFilter, Ordered { ? ? private static final Logger logger = LoggerFactory.getLogger(GlobalLogFilter.class); ? ? private int order; ? ? private static final String REQUEST_PREFIX = "\n--------------------------------- Request ?Info -----------------------------"; ? ? private static final String REQUEST_TAIL ? = "\n-----------------------------------------------------------------------------"; ? ? private static final String RESPONSE_PREFIX = "\n--------------------------------- Response Info -----------------------------"; ? ? private static final String RESPONSE_TAIL ? = "\n-------------------------------------------------------------------------->>>"; ? ? @Override ? ? public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ? ? ? ? long start = DateUtil.getCurrentTime(); ? ? ? ? StringBuilder reqMsg = new StringBuilder(); ? ? ? ? StringBuilder resMsg = new StringBuilder(); ? ? ? ? // 獲取請(qǐng)求信息 ? ? ? ? ServerHttpRequest request = exchange.getRequest(); ? ? ? ? InetSocketAddress address = request.getRemoteAddress(); ? ? ? ? String method = request.getMethodValue(); ? ? ? ? URI uri = request.getURI(); ? ? ? ? HttpHeaders headers = request.getHeaders(); ? ? ? ? // 獲取請(qǐng)求body ? ? ? ? Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null); ? ? ? ? byte[] body = (byte[]) cachedRequestBodyObject; ? ? ? ? String params = new String(body); ? ? ? ? // 獲取請(qǐng)求query ? ? ? ? Map queryMap = request.getQueryParams(); ? ? ? ? String query = JSON.toJSONString(queryMap); ? ? ? ? // 拼接請(qǐng)求日志 ? ? ? ? reqMsg.append(REQUEST_PREFIX); ? ? ? ? reqMsg.append("\n header=").append(headers); ? ? ? ? reqMsg.append("\n query=").append(query); ? ? ? ? reqMsg.append("\n params=").append(params); ? ? ? ? reqMsg.append("\n address=").append(address.getHostName()).append(address.getPort()); ? ? ? ? reqMsg.append("\n method=").append(method); ? ? ? ? reqMsg.append("\n url=").append(uri.getPath()); ? ? ? ? reqMsg.append(REQUEST_TAIL); ? ? ? ? logger.info(reqMsg.toString()); // 打印入?yún)⑷罩? ? ? ? ? ServerHttpResponse response = exchange.getResponse(); ? ? ? ? DataBufferFactory bufferFactory = response.bufferFactory(); ? ? ? ? resMsg.append(RESPONSE_PREFIX); ? ? ? ? ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) { ? ? ? ? ? ? @Override ? ? ? ? ? ? public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { ? ? ? ? ? ? ? ? if (body instanceof Flux) { ? ? ? ? ? ? ? ? ? ? Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body; ? ? ? ? ? ? ? ? ? ? return super.writeWith(fluxBody.map(dataBuffer -> { ? ? ? ? ? ? ? ? ? ? ? ? byte[] content = new byte[dataBuffer.readableByteCount()]; ? ? ? ? ? ? ? ? ? ? ? ? dataBuffer.read(content); ? ? ? ? ? ? ? ? ? ? ? ? String responseResult = new String(content, Charset.forName("UTF-8")); ? ? ? ? ? ? ? ? ? ? ? ? resMsg.append("\n status=").append(this.getStatusCode()); ? ? ? ? ? ? ? ? ? ? ? ? resMsg.append("\n header=").append(this.getHeaders()); ? ? ? ? ? ? ? ? ? ? ? ? resMsg.append("\n responseResult=").append(responseResult); ? ? ? ? ? ? ? ? ? ? ? ? resMsg.append(RESPONSE_TAIL); ? ? ? ? ? ? ? ? ? ? ? ? // 計(jì)算請(qǐng)求時(shí)間 ? ? ? ? ? ? ? ? ? ? ? ? long end = DateUtil.getCurrentTime(); ? ? ? ? ? ? ? ? ? ? ? ? long time = end - start; ? ? ? ? ? ? ? ? ? ? ? ? resMsg.append("耗時(shí)ms:").append(time); ? ? ? ? ? ? ? ? ? ? ? ? logger.info(resMsg.toString()); // 打印結(jié)果日志 ? ? ? ? ? ? ? ? ? ? ? ? return bufferFactory.wrap(content); ? ? ? ? ? ? ? ? ? ? })); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? return super.writeWith(body); ? ? ? ? ? ? } ? ? ? ? }; ? ? ? ? return chain.filter(exchange.mutate().response(decoratedResponse).build()); ? ? } ? ? @Override ? ? public int getOrder() { ? ? ? ? return this.order; ? ? } ? ? public GlobalLogFilter(int order){ ? ? ? ? this.order = order; ? ? } }
在代碼中配置全局?jǐn)r截器
/** ?* @calssName GatewayConfig ?* @Description 網(wǎng)關(guān)配置 ?* @Author jiangshaoneng ?* @DATE 2020/9/25 14:26 ?*/ @Configuration public class GatewayConfig { ? ? /** ? ? ?* 全局過濾器:請(qǐng)求日志打印 ? ? ?*/ ? ? @Bean ? ? public GlobalLogFilter globalLogFilter(){ ? ? ? ? // 該值越小權(quán)重卻大,所以應(yīng)根據(jù)具體項(xiàng)目配置。需要盡早的獲取到參數(shù),一般會(huì)是一個(gè)比較小的值 ? ? ? ? return new GlobalLogFilter(-20);? ? ? } ? ?? ? ? // 其他的路由配置 ...? }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問題及解決
- spring?cloud?gateway中配置uri三種方式
- spring?cloud?gateway中netty線程池小優(yōu)化
- Spring?Cloud?Gateway中netty線程池優(yōu)化示例詳解
- SpringCloudGateway使用Skywalking時(shí)日志打印traceId解析
- Spring Cloud gateway 網(wǎng)關(guān)如何攔截Post請(qǐng)求日志
- Spring Cloud Gateway 記錄請(qǐng)求應(yīng)答數(shù)據(jù)日志操作
- 基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法
相關(guān)文章
SpringBoot實(shí)現(xiàn)異步事件驅(qū)動(dòng)的方法
本文主要介紹了SpringBoot實(shí)現(xiàn)異步事件驅(qū)動(dòng)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06mybatis foreach遍歷LIST讀到數(shù)據(jù)為null的問題
這篇文章主要介紹了mybatis foreach遍歷LIST讀到數(shù)據(jù)為null的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-02-02Java多線程之 FutureTask:帶有返回值的函數(shù)定義和調(diào)用方式
這篇文章主要介紹了Java多線程之 FutureTask:帶有返回值的函數(shù)定義和調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07