spring cloud gateway 日志打印

从api请求中获取访问的具体信息,是一个很常见的功能,这几天在研究springcloud,使用到了其中的gateway,刚好将研究的过程结果都记录下来

0. Version

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <spring-cloud.version>Greenwich.M3</spring-cloud.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

1. GET请求

对于记录get的请求,gateway中过滤器的exchange.getRequest().getQueryParams()方法就可以获取的到了,关键的代码如下

// 记录请求的参数信息 针对GET 请求
MultiValueMap<String, String> queryParams = request.getQueryParams();
    for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
    builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
}

2. POST请求

对于将请求的参数,存放在body这类的请求(如post),网上的很多方法是从ServerHttpRequest对象的getBody()方法返回的Flux<DataBuffer>进行读取的,依靠响应式编程来进行读取,但在自己demo中都没有办法真正获取到

在参考一遍网友的文章后,可以参照ModifyRequestBodyGatewayFilterFactory提供的类的做法来进行,自己的实现,需要注意的是因为从body中读取出来的内容,是依靠响应式编程的,也就是subscribe()被调用过一次后,不能被springboot内部再调用一次,所以我们需要重新返回一个新的request回去,以下是比较核心的代码

/**
     * 过滤器的内部类
     */
    private class InnerFilter implements GatewayFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 获取用户传来的数据类型
            MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
            ServerRequest serverRequest = new DefaultServerRequest(exchange);

            // 如果是json格式,将body内容转化为object or map 都可
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
                Mono<Object> modifiedBody = serverRequest.bodyToMono(Object.class)
                        .flatMap(body -> {
                            recordLog(exchange.getRequest(), body);
                            return Mono.just(body);
                        });

                return getVoidMono(exchange, chain, Object.class, modifiedBody);
            }
            // 如果是表单请求
            else if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
                Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                        // .log("modify_request_mono", Level.INFO)
                        .flatMap(body -> {
                            recordLog(exchange.getRequest(), body);

                            return Mono.just(body);
                        });

                return getVoidMono(exchange, chain, String.class, modifiedBody);
            }
            // TODO 这里未来还可以限制一些格式


            // 无法兼容的请求,则不读取body,像Get请求这种
            recordLog(exchange.getRequest(), "");
            return chain.filter(exchange.mutate().request(exchange.getRequest()).build());
        }


        /**
         * 优先级默认设置为最高
         * @return
         */
        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE;
        }


        /**
         * 参照 ModifyRequestBodyGatewayFilterFactory.java 截取的方法
         * @param exchange
         * @param chain
         * @param outClass
         * @param modifiedBody
         * @return
         */
        private Mono<Void> getVoidMono(ServerWebExchange exchange, GatewayFilterChain chain, Class outClass, Mono<?> modifiedBody) {
            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());

            // the new content type will be computed by bodyInserter
            // and then set in the request decorator
            headers.remove(HttpHeaders.CONTENT_LENGTH);


            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
            return bodyInserter.insert(outputMessage,  new BodyInserterContext())
                    // .log("modify_request", Level.INFO)
                    .then(Mono.defer(() -> {
                        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public HttpHeaders getHeaders() {
                                long contentLength = headers.getContentLength();
                                HttpHeaders httpHeaders = new HttpHeaders();
                                httpHeaders.putAll(super.getHeaders());
                                if (contentLength > 0) {
                                    httpHeaders.setContentLength(contentLength);
                                } else {
                                    // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
                                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                                }
                                return httpHeaders;
                            }

                            @Override
                            public Flux<DataBuffer> getBody() {
                                return outputMessage.getBody();
                            }
                        };
                        return chain.filter(exchange.mutate().request(decorator).build());
                    }));
        }

        /**
         * 记录到请求日志中去
         * @param request request
         * @param body 请求的body内容
         */
        private void recordLog(ServerHttpRequest request, Object body) {
            // 记录要访问的url
            StringBuilder builder = new StringBuilder(" request url: ");
            builder.append(request.getURI().getRawPath());

            // 记录访问的方法
            HttpMethod method = request.getMethod();
            if (null != method){
                builder.append(", method: ").append(method.name());
            }


            // 记录头部信息
            builder.append(", header { ");
            for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
                builder.append(entry.getKey()).append(":").append(StringUtils.join(entry.getValue(), ",")).append(",");
            }

            // 记录参数
            builder.append("} param: ");
            // 处理get的请求
            if (null != method && HttpMethod.GET.matches(method.name())) {
                // 记录请求的参数信息 针对GET 请求
                MultiValueMap<String, String> queryParams = request.getQueryParams();
                for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
                    builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
                }
            }
            else {
                // 从body中读取参数
                builder.append(body);
            }

            LogUtil.info(builder.toString());
        }
    }

关于项目的完整代码,在我的 github

运行情况如下

在日志中的打印为
2019-06-01 16:47:30.442 [reactor-http-nio-2] INFO - request url: /open/check, method: POST, header { Accept:/,Content-type:application/json,User-Agent:curl/7.58.0,Host:localhost:8888,Content-Length:68,} param: {name=zhangsan, address=3678921789378217397128973982189321}

posted @ 2019-06-01 17:13  westlin  阅读(9253)  评论(0编辑  收藏