spring cloud gateway 日志打印

摘要:
从API请求中获取特定访问信息是一个非常常见的功能。这些天我一直在研究春云,并使用网关记录研究过程的结果。0.版本组织。弹簧框架。启动弹簧启动启动程序父级2.1.1

从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

运行情况如下
spring cloud gateway 日志打印第1张

在日志中的打印为
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}

 
 
转自:https://www.cnblogs.com/westlin/p/10960251.html

免责声明:文章转载自《spring cloud gateway 日志打印》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SSH自动登录脚本ajax传参使用json格式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

java利用SuffixFileFilter统计目录下特定后缀名文件的数目

/** * 文件处理类 * @author zhangcd * @date 2017年1月3日 */ public class FileUtil { /** * 得到所有后缀的数目 * * @param directory 目录 * @param suffixFilter 后缀 * @...

基于Geomesa服务查询轨迹数据无法根据空间和时间范围进行结果查询

一、Geomesa - QuickStart(教程工程包) 百度网盘下载地址:geomesa-tutorials-master.7z 二、解压后,IDEA编译如下 百度网盘下载地址:IDEA2018破解版安装 三、根据日期范围查询 1 @Test 2 public void query() throwsException { 3 4...

AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(菜单篇)

  这章主要说插件的菜单,可以说菜单是最核心的部分,前面我们已经实现了Document添加,现在主要就是生成具有层级关系的菜单,以及把菜单跟我们自定义的Document关联起来,也就是MenuPart->View->Model的关联,菜单的实现部分我也是网上参照别人的来实现的,由于代码比较多,我就抽一些重要的来说,其他的只能靠各位自己去体会了,...

wchar_t,char,string,wstring等的总结

一、LPSTR LPCSTR LPTSTR LPCTSTR等 确定的类型: LPSTR = CHAR * = char *LPCSTR = const CHAR * = char * //c意为const  不确定类型(可变型): LPTSTR = LPWSTR = WCHAR * = wchar_t * //(Unicode编码) = LPSTR =...

c#操作MangoDB 之MangoDB CSharp Driver驱动详解

序言MangoDB CSharp Driver是c#操作mongodb的官方驱动。 官方Api文档:http://api.mongodb.org/csharp/2.2/html/R_Project_CSharpDriverDocs.htm#! 驱动的具体介绍:https://docs.mongodb.org/ecosystem/drivers/csharp...

java 增强for循环与泛型

一 增强for循环   增强for循环是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部 原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。 格式: for(元素的数据类型 变量 : Collection集合or数组){ }   它用于遍历Collection和数组。通常只进行遍历元素,不要...