一、需求分析

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过滤器中实现记录访问日志相关推荐

  1. tomcat记录访问日志

    一般的web server有两部分日志: 一是运行的日志,它主要肌瘤运行的一些信息,尤其是一些异常错误日志信息 二是访问日志信息,他是记录的访问的时间,ip,url,sessionId等信息. 下面来 ...

  2. Oracle中如何记录访问数据库的登录信息?

    曾有同学问过,能不能知道都有谁登陆过Oracle数据库, 碰巧看到老杨的这篇历史文章<Oracle中如何记录访问数据库的登陆信息>,介绍了几种实现这个需求的方案,学习一下. 1. 有哪些审 ...

  3. visualsvn php,VisualSVN 手动记录访问日志

    VisualSVN 手动记录访问日志 VisualSVN 是一个可以免费使用的,SVN服务器端软件,基于 apache .可以实现 http https 多种SVN 发布功能. VisualSVN 默 ...

  4. PHP项目中,记录错误日志

    一.场景介绍: 环境:LNMP 我们通常是通过nginx的错误日志来分析分错的,也就是我们在各个server中定义的error_log. 比如下面这样,就是将错误日志定义在/etc/nginx/log ...

  5. windows脚本记录端口访问日志

    本文介绍了利用windows的cmd脚本记录系统端口的访问日志,主要通过cmd脚本for命令和netstat命令实现,脚本代码参考一下内容: @echo on title 记录访问日志 :str fo ...

  6. Bumblebee微服务网关之访问日志处理

    记录访问日志可以起到非常重要的作用,它不仅记录了API的使用情况,更可以反映API各种相关数据:通过分析日志可以得到API不同时间的负载情况,访问效率和流量分布,更进一步还能分析出用户的操作历史和行为 ...

  7. 实现IHttpModule接口获取Session来实现页面访问日志功能。

    我们在开发企业Web应用程序时经常需要对用户的操作记录日志,以便在发生突发事件后有据可查,比如要对用户访问的每一个页面都做日志记录.通常的做法可能是编写一个记录日志的方法(如:AddAccessLog ...

  8. url能访问但new file()找不到文件_Go Web编程给自己写的服务器添加错误和访问日志...

    错误日志和访问日志是一个服务器必须支持的功能,我们教程里使用的服务器到目前为止还没有这两个功能.正好前两天也写了篇介绍logrus日志库的文章,那么今天的文章里就给我们自己写的服务器加上错误日志和访问 ...

  9. Go Web编程--给自己写的服务器添加错误和访问日志

    错误日志和访问日志是一个服务器必须支持的功能,我们教程里使用的服务器到目前为止还没有这两个功能.正好前两天也写了篇介绍logrus日志库的文章,那么今天的文章里就给我们自己写的服务器加上错误日志和访问 ...

最新文章

  1. Ubuntu16.04编译RK3399:make kernel.img error
  2. java 自定义注解以及获得注解的值
  3. Leetcode::Subsets
  4. Linus 本尊来了!为什么 KubeCon 越来越火?
  5. 第五人格维修服务器时间,第五人格调整维护时间 第五人格新赛季奖励皮肤一览...
  6. 计算机应用基础操作题提示,计算机应用基础_操作题文字提示(已经放大了请不要打印).doc...
  7. 计算机应用基础课程是过程化考试吗,基于能力的计算机应用基础课程过程化考核标准构建与实施.doc...
  8. [转载] python zip 文件解压中文乱码问题解决
  9. GoDaddy Linux主机支持机房的更换
  10. 个股解析软件排名推荐,股票行情分析软件排名
  11. R 语言从Github上安装R语言的程序包
  12. 无线网络密码破解方法
  13. cad卸载不干净_流氓软件卸不干净?这6款超强软件卸载神器专治各种流氓软件...
  14. python正则表达式中的冒号_正则表达式,正则匹配冒号
  15. uni-app 前后端实战课 - 《悦读》
  16. 英雄连的制作公司THQ历史
  17. can not be named yunan because its located directly under the workspace root
  18. 他把互联网“存”了起来!
  19. 解决高分辨率下安装Linux花屏问题
  20. openlayers绘制线段和多边形

热门文章

  1. uikeycommand如何添加键盘快捷键可加快工作流程
  2. Web 页面如何实现动画效果
  3. 移动端H5项目开发遇到的问题
  4. python获取excel特定区域_python获取excel指定区域数据库-女性时尚流行美容健康娱乐mv-ida网...
  5. 2019.10 全世界评分最高动画TOP100
  6. 三星a9s参数_三星A9s参数配置怎么样
  7. 笔记本电脑的html,笔记本电脑是什么
  8. python模糊匹配忽略大小写_正则表达式忽略大小写匹配,但保留特定大小写的结果...
  9. 为JSON 数据加入斜杠 \
  10. swiper-页面的翻页动画--渐变效果