zoukankan      html  css  js  c++  java
  • 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}

  • 相关阅读:
    06.django升级打怪学习记
    05.django升级打怪学习记
    04.django升级打怪学习记
    03.django升级打怪学习记
    02.django升级打怪学习记
    python学习手册笔记——39.元类
    python学习手册笔记——35.异常的设计
    关于我
    [Jenkins]Console Output中文显示问号的问题解决
    [Jenkins]JDK版本过高导致的java.io.IOException: Remote call on xxxx failed
  • 原文地址:https://www.cnblogs.com/westlin/p/10960251.html
Copyright © 2011-2022 走看看