如何实现SpringCloud Gateway请求响应日志
发表于:2025-02-01 作者:千家信息网编辑
千家信息网最后更新 2025年02月01日,这篇文章主要讲解了"如何实现SpringCloud Gateway请求响应日志",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何实现SpringClo
千家信息网最后更新 2025年02月01日如何实现SpringCloud Gateway请求响应日志
这篇文章主要讲解了"如何实现SpringCloud Gateway请求响应日志",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何实现SpringCloud Gateway请求响应日志"吧!
获取输入输出参数
首先我们先定义一个日志体
@Data public class GatewayLog { /**访问实例*/ private String targetServer; /**请求路径*/ private String requestPath; /**请求方法*/ private String requestMethod; /**协议 */ private String schema; /**请求体*/ private String requestBody; /**响应体*/ private String responseData; /**请求ip*/ private String ip; /**请求时间*/ private Date requestTime; /**响应时间*/ private Date responseTime; /**执行时间*/ private long executeTime; }
【关键】在网关定义日志过滤器,获取输入输出参数
/** * 日志过滤器,用于记录日志 * @author jianzh6 * @date 2020/3/24 17:17 */ @Slf4j @Component public class AccessLogFilter implements GlobalFilter, Ordered { @Autowired private AccessLogService accessLogService; private final List> messageReaders = HandlerStrategies.withDefaults().messageReaders(); @Override public int getOrder() { return -100; } @Override @SuppressWarnings("unchecked") public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); // 请求路径 String requestPath = request.getPath().pathWithinApplication().value(); Route route = getGatewayRoute(exchange); String ipAddress = WebUtils.getServerHttpRequestIpAddress(request); GatewayLog gatewayLog = new GatewayLog(); gatewayLog.setSchema(request.getURI().getScheme()); gatewayLog.setRequestMethod(request.getMethodValue()); gatewayLog.setRequestPath(requestPath); gatewayLog.setTargetServer(route.getId()); gatewayLog.setRequestTime(new Date()); gatewayLog.setIp(ipAddress); MediaType mediaType = request.getHeaders().getContentType(); if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){ return writeBodyLog(exchange, chain, gatewayLog); }else{ return writeBasicLog(exchange, chain, gatewayLog); } } private Mono writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) { StringBuilder builder = new StringBuilder(); MultiValueMap queryParams = exchange.getRequest().getQueryParams(); for (Map.Entry > entry : queryParams.entrySet()) { builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")); } accessLog.setRequestBody(builder.toString()); //获取响应体 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog); return chain.filter(exchange.mutate().response(decoratedResponse).build()) .then(Mono.fromRunnable(() -> { // 打印日志 writeAccessLog(accessLog); })); } /** * 解决 request body 只能读取一次问题, * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory * @param exchange * @param chain * @param gatewayLog * @return */ @SuppressWarnings("unchecked") private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) { ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders); Mono modifiedBody = serverRequest.bodyToMono(String.class) .flatMap(body ->{ gatewayLog.setRequestBody(body); return Mono.just(body); }); // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次 BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); 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()) .then(Mono.defer(() -> { // 重新封装请求 ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); // 记录响应日志 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); // 记录普通的 return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()) .then(Mono.fromRunnable(() -> { // 打印日志 writeAccessLog(gatewayLog); })); })); } /** * 打印日志 * @author javadaily * @date 2021/3/24 14:53 * @param gatewayLog 网关日志 */ private void writeAccessLog(GatewayLog gatewayLog) { log.info(gatewayLog.toString()); } private Route getGatewayRoute(ServerWebExchange exchange) { return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); } /** * 请求装饰器,重新计算 headers * @param exchange * @param headers * @param outputMessage * @return */ private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) { return 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 getBody() { return outputMessage.getBody(); } }; } /** * 记录响应日志 * 通过 DataBufferFactory 解决响应体分段传输问题。 */ private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) { ServerHttpResponse response = exchange.getResponse(); DataBufferFactory bufferFactory = response.bufferFactory(); return new ServerHttpResponseDecorator(response) { @Override public Mono writeWith(Publisher extends DataBuffer> body) { if (body instanceof Flux) { Date responseTime = new Date(); gatewayLog.setResponseTime(responseTime); // 计算执行时间 long executeTime = (responseTime.getTime() - gatewayLog.getRequestTime().getTime()); gatewayLog.setExecuteTime(executeTime); // 获取响应类型,如果是 json 就打印 String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); if (ObjectUtil.equal(this.getStatusCode(), HttpStatus.OK) && StringUtil.isNotBlank(originalResponseContentType) && originalResponseContentType.contains("application/json")) { Flux extends DataBuffer> fluxBody = Flux.from(body); return super.writeWith(fluxBody.buffer().map(dataBuffers -> { // 合并多个流集合,解决返回体分段传输 DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); DataBuffer join = dataBufferFactory.join(dataBuffers); byte[] content = new byte[join.readableByteCount()]; join.read(content); // 释放掉内存 DataBufferUtils.release(join); String responseResult = new String(content, StandardCharsets.UTF_8); gatewayLog.setResponseData(responseResult); return bufferFactory.wrap(content); })); } } // if body is not a flux. never got there. return super.writeWith(body); } }; } }
代码较长建议直接拷贝到编辑器,只要注意下面一个关键点:
getOrder()方法返回的值必须要<-1,「否则标准的NettyWriteResponseFilter将在您的过滤器被调用的机会之前发送响应,即不会执行获取后端响应参数的方法」
通过上面的两步我们已经可以获取到请求的输入输出参数了,在 writeAccessLog()中将其输出到了日志文件,大家可以在Postman发送请求观察日志。
存储日志
如果需要将日志持久化方便后期检索的话可以考虑将日志存储在MongoDB中,实现过程很简单。(安装MongoDB可以参考这篇文章:实战|MongoDB的安装配置)
引入MongoDB
org.springframework.boot spring-boot-starter-data-mongodb-reactive
由于gateway是基于webflux,所以我们需要选择reactive版本。
在GatewayLog上添加对应的注解
@Data @Document public class GatewayLog { @Id private String id; ... }
建立AccessLogRepository
@Repository public interface AccessLogRepository extends ReactiveMongoRepository{ }
建立Service
public interface AccessLogService { /** * 保存AccessLog * @param gatewayLog 请求响应日志 * @return 响应日志 */ MonosaveAccessLog(GatewayLog gatewayLog); }
建立实现类
@Service public class AccessLogServiceImpl implements AccessLogService { @Autowired private AccessLogRepository accessLogRepository; @Override public MonosaveAccessLog(GatewayLog gatewayLog) { return accessLogRepository.insert(gatewayLog); } }
在Nacos配置中心添加MongoDB对应配置
spring: data: mongodb: host: xxx.xx.x.xx port: 27017 database: accesslog username: accesslog password: xxxxxx
执行请求,打开MongoDB客户端,查看日志结果
感谢各位的阅读,以上就是"如何实现SpringCloud Gateway请求响应日志"的内容了,经过本文的学习后,相信大家对如何实现SpringCloud Gateway请求响应日志这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!
日志
参数
时间
输出
方法
过滤器
问题
学习
输入
配置
关键
内容
篇文章
网关
路径
传输
参考
存储
普通
中将
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
计算机二级怎么学习网络安全
东南亚与大洋洲服务器
海南crm软件开发服务商
大多数数据库管理采用
危害网络安全 拘留
北京创新软件开发服务优化
怎样查美国数据库
软件开发项目验收报告范文
网络安全员能知道你的
福建八四互联网科技有限公司
在线档案管理数据库
同步时间服务器后时间跳回
阿里免费云服务器
直接用手机软件开发app
网络安全电子板报jpg
服务器能不能装手机软件
电子商务的本质是网络技术
陕西项目软件开发排行
权威性的食品科学研发数据库
简述数据库恢复的几种技术
魂斗罗链接服务器
服务器区别
朔州市网络安全环境
aix数据库的计算内存一致很大
商业系统软件开发
mysql 数据库大小写
突然断电导致数据库停止
斯洛文尼亚软件开发公司
运动崁入式软件开发
聊城管理软件开发哪家便宜