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}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM