好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringCloud Gateway之请求应答日志打印方式

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

查看更多关于SpringCloud Gateway之请求应答日志打印方式的详细内容...

  阅读:22次