SpringCloud?Gateway之請求應(yīng)答日志打印方式
Gateway請求應(yīng)答日志打印
請求應(yīng)答日志時(shí)在日常開發(fā)調(diào)試問題的重要手段之一,那么如何基于Spring Cloud Gateway做呢,請看我上代碼。
第一步
創(chuàng)建RecorderServerHttpRequestDecorator,緩存請求參數(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'} ]
以上代碼即可完成請求應(yīng)答日志打印功能。
Gateway全局請求日志打印
實(shí)現(xiàn)GlobalFilter則所有該自定義Filter會(huì)對所有的路由生效。
把請求體的數(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 全局日志打印,請求日志以及返回日志,并在返回結(jié)果日志中添加請求時(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();
? ? ? ? // 獲取請求信息
? ? ? ? ServerHttpRequest request = exchange.getRequest();
? ? ? ? InetSocketAddress address = request.getRemoteAddress();
? ? ? ? String method = request.getMethodValue();
? ? ? ? URI uri = request.getURI();
? ? ? ? HttpHeaders headers = request.getHeaders();
? ? ? ? // 獲取請求body
? ? ? ? Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null);
? ? ? ? byte[] body = (byte[]) cachedRequestBodyObject;
? ? ? ? String params = new String(body);
? ? ? ? // 獲取請求query
? ? ? ? Map queryMap = request.getQueryParams();
? ? ? ? String query = JSON.toJSONString(queryMap);
? ? ? ? // 拼接請求日志
? ? ? ? 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ì)算請求時(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 {
? ? /**
? ? ?* 全局過濾器:請求日志打印
? ? ?*/
? ? @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請求日志
- Spring Cloud Gateway 記錄請求應(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ì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06
mybatis foreach遍歷LIST讀到數(shù)據(jù)為null的問題
這篇文章主要介紹了mybatis foreach遍歷LIST讀到數(shù)據(jù)為null的問題,具有很好的參考價(jià)值,希望對大家有所幫助。2022-02-02
Java多線程之 FutureTask:帶有返回值的函數(shù)定義和調(diào)用方式
這篇文章主要介紹了Java多線程之 FutureTask:帶有返回值的函數(shù)定義和調(diào)用方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

