Gateway请求应答日志打印
请求应答日志时在日常开发调试问题的重要手段之一,那么如何基于Spring Cloud Gateway做呢,请看我上代码。
第一步
创建RecorderServerHttpRequestDecorator,缓存请求参数,解决body只能读一次问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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())); } } |
第二步
创建访问日志全局过滤器,然后在此过滤器进行日志构造。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
@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 ; } } |
最后结果:
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'} ]
以上代码即可完成请求应答日志打印功能。
Gateway全局请求日志打印
实现GlobalFilter则所有该自定义Filter会对所有的路由生效。
把请求体的数据存入exchange
便于打印日志时获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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 中的内容 copy 一份,记录到 exchange 的一个自定义属性中 * @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 中的内容 copy 一份,记录到 exchange 的一个自定义属性中 Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null ); // 如果已经缓存过,略过 if (cachedRequestBodyObject != null ) { return chain.filter(exchange); } // 如果没有缓存过,获取字节数组存入 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; } } |
编写全局日志拦截器代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
/** * @calssName LogFilter * @Description 全局日志打印,请求日志以及返回日志,并在返回结果日志中添加请求时间 * @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()); // 打印入参日志 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); // 计算请求时间 long end = DateUtil.getCurrentTime(); long time = end - start; resMsg.append( "耗时ms:" ).append(time); logger.info(resMsg.toString()); // 打印结果日志 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; } } |
在代码中配置全局拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/** * @calssName GatewayConfig * @Description 网关配置 * @Author jiangshaoneng * @DATE 2020/9/25 14:26 */ @Configuration public class GatewayConfig { /** * 全局过滤器:请求日志打印 */ @Bean public GlobalLogFilter globalLogFilter(){ // 该值越小权重却大,所以应根据具体项目配置。需要尽早的获取到参数,一般会是一个比较小的值 return new GlobalLogFilter(- 20 ); } // 其他的路由配置 ... } |
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/weixin_34082177/article/details/91392541