gateway过滤器中实现记录访问日志
一、需求分析
SpringCloud多服务项目环境,前端请求经过网关中转发到各个服务节点。日志中需要记录请求头中的部分参数、请求的body、响应状态及响应内容,并在请求头中新增一个标识。
二、代码实现
1. 装饰Request
@Component
public class RequestFilter implements GlobalFilter, Ordered {/*** 1.封装 HttpRequest,使 Request body 可以重复读取;* 2.封装 HttpRequestHeader,添加requestId;*/@Overridepublic int getOrder() {// 最高优先级的执行顺序return HIGHEST_PRECEDENCE;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// Request.ServerHttpRequest request = exchange.getRequest();// 新建Header 添加 requestId.String requestId = new SnowFlakeId().get();request.mutate().headers(httpHeaders -> {httpHeaders.set("requestId", requestId);}).build();if (Objects.requireNonNull(request.getMethod()).equals(HttpMethod.POST)) {// body 为null, 不会执行里面的重分派.DefaultDataBufferFactory defaultDataBufferFactory = new DefaultDataBufferFactory();DefaultDataBuffer defaultDataBuffer = defaultDataBufferFactory.allocateBuffer(0);//构建新数据流, 当body为空时,构建空流Flux<DataBuffer> bodyDataBuffer = exchange.getRequest().getBody().defaultIfEmpty(defaultDataBuffer);return DataBufferUtils.join(bodyDataBuffer).flatMap(dataBuffer -> {// Request.DataBufferUtils.retain(dataBuffer);Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic Flux<DataBuffer> getBody() {return cachedFlux;}};return chain.filter(exchange.mutate().request(mutatedRequest).build());});}else {return chain.filter(exchange);}}}
PS:
1.Order 最高优先级。
2.Request Header 不可添加值,需重新创建后绑定。
3.Request Body IO流,读取一次后就空了;新建一个Request绑定到请求中。
4. POST请求,Body为空时,没有执行新建Request,需要构建一个空的输入流使新建Request可以执行。
2. 装饰Response
@Component
@Slf4j
@AllArgsConstructor
public class ResponseFilter implements GlobalFilter, Ordered {/*** 1.封装HttpResponse,读取Respond 内容;* 2.记录操作日志:请求信息、响应状态、响应信息等;*/@Overridepublic int getOrder() {// before write response.return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// ResponseServerHttpResponse mutatedResponse = responseDecorator(exchange);return chain.filter(exchange.mutate().response(mutatedResponse).build());}public ServerHttpResponse responseDecorator(ServerWebExchange exchange) {ServerHttpResponse originalResponse = exchange.getResponse();DataBufferFactory bufferFactory = originalResponse.bufferFactory();return new ServerHttpResponseDecorator(originalResponse) {@Overridepublic Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {// 读取 Response 的响应流中的信息后,再写回去if (body instanceof Flux) {// 获取ContentType,判断是否返回JSON格式数据String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);if (StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains(APPLICATION_JSON)) {Flux<? extends DataBuffer> fluxBody = Flux.from(body);return super.writeWith(fluxBody.buffer().map(dataBuffers -> {// Response Content.// String responseContent = resolveContentFromResponse(dataBuffers, bufferFactory);DataBuffer join = bufferFactory.join(dataBuffers);byte[] content = new byte[join.readableByteCount()];join.read(content);DataBufferUtils.release(join);String responseContent = formatStr(new String(content, StandardCharsets.UTF_8));// 添加操作日志.ServerHttpRequest request = exchange.getRequest();String apiPath = request.getPath().toString();HttpHeaders headers = request.getHeaders();String requestBody = resolveBodyFromRequest(request.getBody());appendSystemLog(apiPath, headers, requestBody, responseContent, getStatusCode());// 写回Response Content.// String 转为 byte[]后再写入DataBuffer,可能出现中文全部为?的情况// byte[] uppedContent = new String(responseContent.getBytes(), StandardCharsets.UTF_8).getBytes();// originalResponse.getHeaders().setContentLength(uppedContent.length);return bufferFactory.wrap(content);}));}}return super.writeWith(body);}@Overridepublic Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {return writeWith(Flux.from(body).flatMapSequential(p -> p));}};}/*** 解析 RequestBody*/public String resolveBodyFromRequest(Flux<DataBuffer> body) {AtomicReference<String> bodyRef = new AtomicReference<>();// 缓存读取的request body信息body.subscribe(dataBuffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());DataBufferUtils.release(dataBuffer);bodyRef.set(charBuffer.toString());});return formatStr(bodyRef.get());}/*** 解析 ResponseBody*/public String resolveContentFromResponse(List<? extends DataBuffer> dataBuffers, DataBufferFactory bufferFactory) {//(返回数据内如果字符串过大,默认会切割)解决返回体分段传输// 遍历 List时,拼接字符串会遇到汉字乱码的现象。应全部读取后再转成字符串。DataBuffer join = bufferFactory.join(dataBuffers);byte[] content = new byte[join.readableByteCount()];join.read(content);DataBufferUtils.release(join);return formatStr(new String(content, StandardCharsets.UTF_8));}/*** 去掉空格,换行和制表符*/private String formatStr(String str){if (str != null && str.length() > 0) {Pattern p = Pattern.compile("\t|\r|\n");Matcher m = p.matcher(str);return m.replaceAll("");}return str;}/*** 记录操作日志*/public void appendSystemLog(String apiPath, HttpHeaders headers, String requestBody, String responseBody, HttpStatus status) {try {// todo 记录日志// service } catch (Exception e) {log.error("==== Append SystemLog error.", e);}}}
PS:
1.Order 需要在Response内容被NettyWriteResponseFilter cleanup之前读取响应类容和RequestBody。
2. 读取Response内容时,需要注意由于相容内容分段(List),导致读取时阶段汉字的bytes出现个别汉字乱码。
3.Response内容读取后回写。
gateway过滤器中实现记录访问日志相关推荐
- tomcat记录访问日志
一般的web server有两部分日志: 一是运行的日志,它主要肌瘤运行的一些信息,尤其是一些异常错误日志信息 二是访问日志信息,他是记录的访问的时间,ip,url,sessionId等信息. 下面来 ...
- Oracle中如何记录访问数据库的登录信息?
曾有同学问过,能不能知道都有谁登陆过Oracle数据库, 碰巧看到老杨的这篇历史文章<Oracle中如何记录访问数据库的登陆信息>,介绍了几种实现这个需求的方案,学习一下. 1. 有哪些审 ...
- visualsvn php,VisualSVN 手动记录访问日志
VisualSVN 手动记录访问日志 VisualSVN 是一个可以免费使用的,SVN服务器端软件,基于 apache .可以实现 http https 多种SVN 发布功能. VisualSVN 默 ...
- PHP项目中,记录错误日志
一.场景介绍: 环境:LNMP 我们通常是通过nginx的错误日志来分析分错的,也就是我们在各个server中定义的error_log. 比如下面这样,就是将错误日志定义在/etc/nginx/log ...
- windows脚本记录端口访问日志
本文介绍了利用windows的cmd脚本记录系统端口的访问日志,主要通过cmd脚本for命令和netstat命令实现,脚本代码参考一下内容: @echo on title 记录访问日志 :str fo ...
- Bumblebee微服务网关之访问日志处理
记录访问日志可以起到非常重要的作用,它不仅记录了API的使用情况,更可以反映API各种相关数据:通过分析日志可以得到API不同时间的负载情况,访问效率和流量分布,更进一步还能分析出用户的操作历史和行为 ...
- 实现IHttpModule接口获取Session来实现页面访问日志功能。
我们在开发企业Web应用程序时经常需要对用户的操作记录日志,以便在发生突发事件后有据可查,比如要对用户访问的每一个页面都做日志记录.通常的做法可能是编写一个记录日志的方法(如:AddAccessLog ...
- url能访问但new file()找不到文件_Go Web编程给自己写的服务器添加错误和访问日志...
错误日志和访问日志是一个服务器必须支持的功能,我们教程里使用的服务器到目前为止还没有这两个功能.正好前两天也写了篇介绍logrus日志库的文章,那么今天的文章里就给我们自己写的服务器加上错误日志和访问 ...
- Go Web编程--给自己写的服务器添加错误和访问日志
错误日志和访问日志是一个服务器必须支持的功能,我们教程里使用的服务器到目前为止还没有这两个功能.正好前两天也写了篇介绍logrus日志库的文章,那么今天的文章里就给我们自己写的服务器加上错误日志和访问 ...
最新文章
- Ubuntu16.04编译RK3399:make kernel.img error
- java 自定义注解以及获得注解的值
- Leetcode::Subsets
- Linus 本尊来了!为什么 KubeCon 越来越火?
- 第五人格维修服务器时间,第五人格调整维护时间 第五人格新赛季奖励皮肤一览...
- 计算机应用基础操作题提示,计算机应用基础_操作题文字提示(已经放大了请不要打印).doc...
- 计算机应用基础课程是过程化考试吗,基于能力的计算机应用基础课程过程化考核标准构建与实施.doc...
- [转载] python zip 文件解压中文乱码问题解决
- GoDaddy Linux主机支持机房的更换
- 个股解析软件排名推荐,股票行情分析软件排名
- R 语言从Github上安装R语言的程序包
- 无线网络密码破解方法
- cad卸载不干净_流氓软件卸不干净?这6款超强软件卸载神器专治各种流氓软件...
- python正则表达式中的冒号_正则表达式,正则匹配冒号
- uni-app 前后端实战课 - 《悦读》
- 英雄连的制作公司THQ历史
- can not be named yunan because its located directly under the workspace root
- 他把互联网“存”了起来!
- 解决高分辨率下安装Linux花屏问题
- openlayers绘制线段和多边形
热门文章
- uikeycommand如何添加键盘快捷键可加快工作流程
- Web 页面如何实现动画效果
- 移动端H5项目开发遇到的问题
- python获取excel特定区域_python获取excel指定区域数据库-女性时尚流行美容健康娱乐mv-ida网...
- 2019.10 全世界评分最高动画TOP100
- 三星a9s参数_三星A9s参数配置怎么样
- 笔记本电脑的html,笔记本电脑是什么
- python模糊匹配忽略大小写_正则表达式忽略大小写匹配,但保留特定大小写的结果...
- 为JSON 数据加入斜杠 \
- swiper-页面的翻页动画--渐变效果