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}
spring cloud gateway 日志打印的更多相关文章
- spring cloud gateway之filter篇
转载请标明出处: https://www.fangzhipeng.com 本文出自方志朋的博客 在上一篇文章详细的介绍了Gateway的Predict,Predict决定了请求由哪一个路由处理,在路由 ...
- 微服务网关实战——Spring Cloud Gateway
导读 作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用.本文对Spring Clou ...
- Spring Cloud gateway 网关服务二 断言、过滤器
微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...
- spring cloud gateway 自定义GatewayFilterFactory
spring cloud gateway提供了很多内置的过滤器,那么因为需求的关系,需要自定义实现,并且要可配置,在一番折腾之后,总算是解决了,那么久记录下来 对于自定义的factory,我们可以选择 ...
- springcloud3(五) spring cloud gateway动态路由的四类实现方式
写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于Nacos, 基于数据库(PosgreSQL/Redis), 基于Mem ...
- Spring Cloud Gateway 没有链路信息,我 TM 人傻了(中)
本系列是 我TM人傻了 系列第五期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了 这个大表走索引字段查询的 SQL 怎么就成全扫描了,我TM人傻了 获取异 ...
- Spring Cloud Gateway GatewayFilter的使用
Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...
- Spring Cloud Gateway夺命连环10问?
大家好,我是不才陈某~ 最近有很多小伙伴私信我催更 <Spring Cloud 进阶>,陈某也总结了一下,最终原因就是陈某之前力求一篇文章将一个组件重要知识点讲透,这样导致了文章篇幅很长, ...
- 从0开始构建你的api网关--Spring Cloud Gateway网关实战及原理解析
API 网关 API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题 ...
随机推荐
- git使用-merge request开发操作步骤
0. 如果当前不在develop分支,则切换到develop分支 git checkout develop 1. 获取develop分支最新代码 git pull 注意:这一步正常来说应该是一个Fas ...
- IDEA注册码(附修改hosts文件的方法)
推荐获取IDEA注册码的网站:http://idea.lanyus.com/ 亲测好用! 也可复制下边的注册码: K71U8DBPNE-eyJsaWNlbnNlSWQiOiJLNzFVOERCUE5F ...
- Linux shell 获取目录下时间最新的文件的文件名
ls -lt /dirname/ | grep filename | head -n 1 |awk '{print $9}' 逐条解释: ls -lt /dirname/ 列出此目录下的所有文件并按照 ...
- vue 修改数据
通过数组中的方法改变数据 变异方法(改变原数组) push() pop() shift() unshift() splice() sort() reverse() 替换数组(生成新数组) filter ...
- idea导入配置SSM项目,并进行打包
idea确实是一个功能强大的开发工具,但是配置项目太复杂,每次都要花费很长的时间在配置上. 关于SSM项目导入,配置,打包,看这篇就够了,百试不爽.
- synchronized实现原理及ReentrantLock源码
synchronized synchronized的作用范围 public class SynchronizedTest { // 实例方法,方法访问标志ACC_SYNCHRONIZED,锁对象是对象 ...
- webshell学习
参考文章: https://www.bilibili.com/video/BV1T4411t7BW?p=14 https://blog.csdn.net/mmmsss987/article/detai ...
- Thymeleaf是个什么东东?
Thymeleaf是面向Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本. Thymeleaf的主要目标是提供一个优雅和高度可维护的创建 ...
- C# 高并发、抢单解决思路
高并发 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求.高并发相关常用的一些指标有响应时间(Respon ...
- 创建txt文件,并且写入内容
使用fopen的w方式就可以创建一个新的txt文件,如果文件名存在该文件内容会消失. 1. fopen的函数原型:FILE * fopen(const char * path,const char * ...