这篇文章主要讲解了“如何实现SpringCloud Gateway请求响应日志”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何实现SpringCloud Gateway请求响应日志”吧!

获取输入输出参数

首先我们先定义一个日志体

@DatapublicclassGatewayLog{/**访问实例*/privateStringtargetServer;/**请求路径*/privateStringrequestPath;/**请求方法*/privateStringrequestMethod;/**协议*/privateStringschema;/**请求体*/privateStringrequestBody;/**响应体*/privateStringresponseData;/**请求ip*/privateStringip;/**请求时间*/privateDaterequestTime;/**响应时间*/privateDateresponseTime;/**执行时间*/privatelongexecuteTime;}

【关键】在网关定义日志过滤器,获取输入输出参数

/***日志过滤器,用于记录日志*@authorjianzh6*@date2020/3/2417:17*/@Slf4j@ComponentpublicclassAccessLogFilterimplementsGlobalFilter,Ordered{@AutowiredprivateAccessLogServiceaccessLogService;privatefinalList<HttpMessageReader<?>>messageReaders=HandlerStrategies.withDefaults().messageReaders();@OverridepublicintgetOrder(){return-100;}@Override@SuppressWarnings("unchecked")publicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){ServerHttpRequestrequest=exchange.getRequest();//请求路径StringrequestPath=request.getPath().pathWithinApplication().value();Routeroute=getGatewayRoute(exchange);StringipAddress=WebUtils.getServerHttpRequestIpAddress(request);GatewayLoggatewayLog=newGatewayLog();gatewayLog.setSchema(request.getURI().getScheme());gatewayLog.setRequestMethod(request.getMethodValue());gatewayLog.setRequestPath(requestPath);gatewayLog.setTargetServer(route.getId());gatewayLog.setRequestTime(newDate());gatewayLog.setIp(ipAddress);MediaTypemediaType=request.getHeaders().getContentType();if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)||MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){returnwriteBodyLog(exchange,chain,gatewayLog);}else{returnwriteBasicLog(exchange,chain,gatewayLog);}}privateMono<Void>writeBasicLog(ServerWebExchangeexchange,GatewayFilterChainchain,GatewayLogaccessLog){StringBuilderbuilder=newStringBuilder();MultiValueMap<String,String>queryParams=exchange.getRequest().getQueryParams();for(Map.Entry<String,List<String>>entry:queryParams.entrySet()){builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(),","));}accessLog.setRequestBody(builder.toString());//获取响应体ServerHttpResponseDecoratordecoratedResponse=recordResponseLog(exchange,accessLog);returnchain.filter(exchange.mutate().response(decoratedResponse).build()).then(Mono.fromRunnable(()->{//打印日志writeAccessLog(accessLog);}));}/***解决requestbody只能读取一次问题,*参考:org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory*@paramexchange*@paramchain*@paramgatewayLog*@return*/@SuppressWarnings("unchecked")privateMonowriteBodyLog(ServerWebExchangeexchange,GatewayFilterChainchain,GatewayLoggatewayLog){ServerRequestserverRequest=ServerRequest.create(exchange,messageReaders);Mono<String>modifiedBody=serverRequest.bodyToMono(String.class).flatMap(body->{gatewayLog.setRequestBody(body);returnMono.just(body);});//通过BodyInserter插入body(支持修改body),避免requestbody只能获取一次BodyInserterbodyInserter=BodyInserters.fromPublisher(modifiedBody,String.class);HttpHeadersheaders=newHttpHeaders();headers.putAll(exchange.getRequest().getHeaders());//thenewcontenttypewillbecomputedbybodyInserter//andthensetintherequestdecoratorheaders.remove(HttpHeaders.CONTENT_LENGTH);CachedBodyOutputMessageoutputMessage=newCachedBodyOutputMessage(exchange,headers);returnbodyInserter.insert(outputMessage,newBodyInserterContext()).then(Mono.defer(()->{//重新封装请求ServerHttpRequestdecoratedRequest=requestDecorate(exchange,headers,outputMessage);//记录响应日志ServerHttpResponseDecoratordecoratedResponse=recordResponseLog(exchange,gatewayLog);//记录普通的returnchain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(()->{//打印日志writeAccessLog(gatewayLog);}));}));}/***打印日志*@authorjavadaily*@date2021/3/2414:53*@paramgatewayLog网关日志*/privatevoidwriteAccessLog(GatewayLoggatewayLog){log.info(gatewayLog.toString());}privateRoutegetGatewayRoute(ServerWebExchangeexchange){returnexchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);}/***请求装饰器,重新计算headers*@paramexchange*@paramheaders*@paramoutputMessage*@return*/privateServerHttpRequestDecoratorrequestDecorate(ServerWebExchangeexchange,HttpHeadersheaders,CachedBodyOutputMessageoutputMessage){returnnewServerHttpRequestDecorator(exchange.getRequest()){@OverridepublicHttpHeadersgetHeaders(){longcontentLength=headers.getContentLength();HttpHeadershttpHeaders=newHttpHeaders();httpHeaders.putAll(super.getHeaders());if(contentLength>0){httpHeaders.setContentLength(contentLength);}else{//TODO:thiscausesa'HTTP/1.1411LengthRequired'//on//httpbin.orghttpHeaders.set(HttpHeaders.TRANSFER_ENCODING,"chunked");}returnhttpHeaders;}@OverridepublicFlux<DataBuffer>getBody(){returnoutputMessage.getBody();}};}/***记录响应日志*通过DataBufferFactory解决响应体分段传输问题。*/privateServerHttpResponseDecoratorrecordResponseLog(ServerWebExchangeexchange,GatewayLoggatewayLog){ServerHttpResponseresponse=exchange.getResponse();DataBufferFactorybufferFactory=response.bufferFactory();returnnewServerHttpResponseDecorator(response){@OverridepublicMono<Void>writeWith(Publisher<?extendsDataBuffer>body){if(bodyinstanceofFlux){DateresponseTime=newDate();gatewayLog.setResponseTime(responseTime);//计算执行时间longexecuteTime=(responseTime.getTime()-gatewayLog.getRequestTime().getTime());gatewayLog.setExecuteTime(executeTime);//获取响应类型,如果是json就打印StringoriginalResponseContentType=exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);if(ObjectUtil.equal(this.getStatusCode(),HttpStatus.OK)&&StringUtil.isNotBlank(originalResponseContentType)&&originalResponseContentType.contains("application/json")){Flux<?extendsDataBuffer>fluxBody=Flux.from(body);returnsuper.writeWith(fluxBody.buffer().map(dataBuffers->{//合并多个流集合,解决返回体分段传输DataBufferFactorydataBufferFactory=newDefaultDataBufferFactory();DataBufferjoin=dataBufferFactory.join(dataBuffers);byte[]content=newbyte[join.readableByteCount()];join.read(content);//释放掉内存DataBufferUtils.release(join);StringresponseResult=newString(content,StandardCharsets.UTF_8);gatewayLog.setResponseData(responseResult);returnbufferFactory.wrap(content);}));}}//ifbodyisnotaflux.nevergotthere.returnsuper.writeWith(body);}};}}

代码较长建议直接拷贝到编辑器,只要注意下面一个关键点:

getOrder()方法返回的值必须要<-1,「否则标准的NettyWriteResponseFilter将在您的过滤器被调用的机会之前发送响应,即不会执行获取后端响应参数的方法」

通过上面的两步我们已经可以获取到请求的输入输出参数了,在 writeAccessLog()中将其输出到了日志文件,大家可以在Postman发送请求观察日志。

存储日志

如果需要将日志持久化方便后期检索的话可以考虑将日志存储在MongoDB中,实现过程很简单。(安装MongoDB可以参考这篇文章:实战|MongoDB的安装配置)

引入MongoDB

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb-reactive</artifactId></dependency>

由于gateway是基于webflux,所以我们需要选择reactive版本。

在GatewayLog上添加对应的注解

@Data@DocumentpublicclassGatewayLog{@IdprivateStringid;...}

建立AccessLogRepository

@RepositorypublicinterfaceAccessLogRepositoryextendsReactiveMongoRepository<GatewayLog,String>{}

建立Service

publicinterfaceAccessLogService{/***保存AccessLog*@paramgatewayLog请求响应日志*@return响应日志*/Mono<GatewayLog>saveAccessLog(GatewayLoggatewayLog);}

建立实现类

@ServicepublicclassAccessLogServiceImplimplementsAccessLogService{@AutowiredprivateAccessLogRepositoryaccessLogRepository;@OverridepublicMono<GatewayLog>saveAccessLog(GatewayLoggatewayLog){returnaccessLogRepository.insert(gatewayLog);}}

在Nacos配置中心添加MongoDB对应配置

spring:data:mongodb:host:xxx.xx.x.xxport:27017database:accesslogusername:accesslogpassword:xxxxxx

执行请求,打开MongoDB客户端,查看日志结果

感谢各位的阅读,以上就是“如何实现SpringCloud Gateway请求响应日志”的内容了,经过本文的学习后,相信大家对如何实现SpringCloud Gateway请求响应日志这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!