数据库实战之SpringCloud Gateway 请求响应日志

    作者:单一色调更新于: 2021-06-27 22:37:11

    SpringCloud Alibaba实战之SpringCloud Gateway 请求响应日志。这种数据集合具有如下特点:尽可能不重复,以最优方式为某个特定组织的多种应用服务,其数据结构独立于使用它的应用程序,对数据的增、删、改、查由统一软件进行管理和控制。从发展的历史看,数据库是数据管理的高级阶段,它是由文件管理系统发展起来的。

    请求响应日志是日常开发调试定位问题的重要手段,在微服务中引入SpringCloud Gateway后我们希望在网关层统一进行日志的收集。

    数据库实战之SpringCloud Gateway 请求响应日志_数据库系统_数据库原理_数据库软件_课课家

    本文转载自微信公众号「java日知录」,作者单一色调。转载本文请联系JAVA日知录公众号。

    请求响应日志是日常开发调试定位问题的重要手段,在微服务中引入SpringCloud Gateway后我们希望在网关层统一进行日志的收集。

    本节内容将实现以下两个功能:

    获取请求的输入输出参数,封装成自定义日志

    将日志发送到MongoDB进行存储

    获取输入输出参数

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

    1. @Data 
    2. public class GatewayLog { 
    3.     /**访问实例*/ 
    4.     private String targetServer; 
    5.     /**请求路径*/ 
    6.     private String requestPath; 
    7.     /**请求方法*/ 
    8.     private String requestMethod; 
    9.     /**协议 */ 
    10.     private String schema
    11.     /**请求体*/ 
    12.     private String requestBody; 
    13.     /**响应体*/ 
    14.     private String responseData; 
    15.     /**请求ip*/ 
    16.     private String ip; 
    17.  /**请求时间*/ 
    18.     private Date requestTime; 
    19.  /**响应时间*/ 
    20.     private Date responseTime; 
    21.     /**执行时间*/ 
    22.     private long executeTime; 

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

    1. /** 
    2.  * 日志过滤器,用于记录日志 
    3.  * @author jianzh5 
    4.  * @date 2020/3/24 17:17 
    5.  */ 
    6. @Slf4j 
    7. @Component 
    8. public class AccessLogFilter implements GlobalFilter, Ordered { 
    9.     @Autowired 
    10.     private AccessLogService accessLogService; 
    11.  
    12.     private final List> messageReaders = HandlerStrategies.withDefaults().messageReaders(); 
    13.  
    14.     @Override 
    15.     public int getOrder() { 
    16.         return -100; 
    17.     } 
    18.  
    19.     @Override 
    20.     @SuppressWarnings("unchecked"
    21.     public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 
    22.  
    23.         ServerHttpRequest request = exchange.getRequest(); 
    24.  
    25.         // 请求路径 
    26.         String requestPath = request.getPath().pathWithinApplication().value(); 
    27.  
    28.         Route route = getGatewayRoute(exchange); 
    29.  
    30.  
    31.         String ipAddress = WebUtils.getServerHttpRequestIpAddress(request); 
    32.  
    33.         GatewayLog gatewayLog = new GatewayLog(); 
    34.         gatewayLog.setSchema(request.getURI().getScheme()); 
    35.         gatewayLog.setRequestMethod(request.getMethodValue()); 
    36.         gatewayLog.setRequestPath(requestPath); 
    37.         gatewayLog.setTargetServer(route.getId()); 
    38.         gatewayLog.setRequestTime(new Date()); 
    39.         gatewayLog.setIp(ipAddress); 
    40.  
    41.         MediaType mediaType = request.getHeaders().getContentType(); 
    42.  
    43.         if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){ 
    44.             return writeBodyLog(exchange, chain, gatewayLog); 
    45.         }else
    46.             return writeBasicLog(exchange, chain, gatewayLog); 
    47.         } 
    48.     } 
    49.  
    50.     private Mono writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) { 
    51.         StringBuilder builder = new StringBuilder(); 
    52.         MultiValueMap queryParams = exchange.getRequest().getQueryParams(); 
    53.         for (Map.Entry> entry : queryParams.entrySet()) { 
    54.             builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")); 
    55.         } 
    56.         accessLog.setRequestBody(builder.toString()); 
    57.  
    58.         //获取响应体 
    59.         ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog); 
    60.  
    61.         return chain.filter(exchange.mutate().response(decoratedResponse).build()) 
    62.                 .then(Mono.fromRunnable(() -> { 
    63.                     // 打印日志 
    64.                     writeAccessLog(accessLog); 
    65.                 })); 
    66.     } 
    67.  
    68.  
    69.     /** 
    70.      * 解决 request body 只能读取一次问题, 
    71.      * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory 
    72.      * @param exchange 
    73.      * @param chain 
    74.      * @param gatewayLog 
    75.      * @return 
    76.      */ 
    77.     @SuppressWarnings("unchecked"
    78.     private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) { 
    79.         ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders); 
    80.  
    81.         Mono modifiedBody = serverRequest.bodyToMono(String.class) 
    82.                 .flatMap(body ->{ 
    83.                     gatewayLog.setRequestBody(body); 
    84.                     return Mono.just(body); 
    85.                 }); 
    86.  
    87.         // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次 
    88.         BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); 
    89.         HttpHeaders headers = new HttpHeaders(); 
    90.         headers.putAll(exchange.getRequest().getHeaders()); 
    91.         // the new content type will be computed by bodyInserter 
    92.         // and then set in the request decorator 
    93.         headers.remove(HttpHeaders.CONTENT_LENGTH); 
    94.  
    95.         CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); 
    96.  
    97.         return bodyInserter.insert(outputMessage,new BodyInserterContext()) 
    98.                 .then(Mono.defer(() -> { 
    99.                     // 重新封装请求 
    100.                     ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); 
    101.  
    102.                     // 记录响应日志 
    103.                     ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); 
    104.  
    105.                     // 记录普通的 
    106.                     return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()) 
    107.                             .then(Mono.fromRunnable(() -> { 
    108.                                 // 打印日志 
    109.                                 writeAccessLog(gatewayLog); 
    110.                             })); 
    111.                 })); 
    112.     } 
    113.  
    114.     /** 
    115.      * 打印日志 
    116.      * @author javadaily 
    117.      * @date 2021/3/24 14:53 
    118.      * @param gatewayLog 网关日志 
    119.      */ 
    120.     private void writeAccessLog(GatewayLog gatewayLog) { 
    121.         log.info(gatewayLog.toString());   
    122.     } 
    123.  
    124.  
    125.  
    126.     private Route getGatewayRoute(ServerWebExchange exchange) { 
    127.         return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); 
    128.     } 
    129.  
    130.  
    131.     /** 
    132.      * 请求装饰器,重新计算 headers 
    133.      * @param exchange 
    134.      * @param headers 
    135.      * @param outputMessage 
    136.      * @return 
    137.      */ 
    138.     private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, 
    139.                                                        CachedBodyOutputMessage outputMessage) { 
    140.         return new ServerHttpRequestDecorator(exchange.getRequest()) { 
    141.             @Override 
    142.             public HttpHeaders getHeaders() { 
    143.                 long contentLength = headers.getContentLength(); 
    144.                 HttpHeaders httpHeaders = new HttpHeaders(); 
    145.                 httpHeaders.putAll(super.getHeaders()); 
    146.                 if (contentLength > 0) { 
    147.                     httpHeaders.setContentLength(contentLength); 
    148.                 } else { 
    149.                     // TODO: this causes a 'HTTP/1.1 411 Length Required' // on 
    150.                     // httpbin.org 
    151.                     httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); 
    152.                 } 
    153.                 return httpHeaders; 
    154.             } 
    155.  
    156.             @Override 
    157.             public Flux getBody() { 
    158.                 return outputMessage.getBody(); 
    159.             } 
    160.         }; 
    161.     } 
    162.  
    163.  
    164.     /** 
    165.      * 记录响应日志 
    166.      * 通过 DataBufferFactory 解决响应体分段传输问题。 
    167.      */ 
    168.     private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) { 
    169.         ServerHttpResponse response = exchange.getResponse(); 
    170.         DataBufferFactory bufferFactory = response.bufferFactory(); 
    171.  
    172.         return new ServerHttpResponseDecorator(response) { 
    173.             @Override 
    174.             public Mono writeWith(Publisher body) { 
    175.                 if (body instanceof Flux) { 
    176.                     Date responseTime = new Date(); 
    177.                     gatewayLog.setResponseTime(responseTime); 
    178.                     // 计算执行时间 
    179.                     long executeTime = (responseTime.getTime() - gatewayLog.getRequestTime().getTime()); 
    180.  
    181.                     gatewayLog.setExecuteTime(executeTime); 
    182.  
    183.                     // 获取响应类型,如果是 json 就打印 
    184.                     String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); 
    185.  
    186.  
    187.                     if (ObjectUtil.equal(this.getStatusCode(), HttpStatus.OK) 
    188.                             && StringUtil.isNotBlank(originalResponseContentType) 
    189.                             && originalResponseContentType.contains("application/json")) { 
    190.  
    191.                         Flux fluxBody = Flux.from(body); 
    192.                         return super.writeWith(fluxBody.buffer().map(dataBuffers -> { 
    193.  
    194.                             // 合并多个流集合,解决返回体分段传输 
    195.                             DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); 
    196.                             DataBuffer join = dataBufferFactory.join(dataBuffers); 
    197.                             byte[] content = new byte[join.readableByteCount()]; 
    198.                             join.read(content); 
    199.  
    200.                             // 释放掉内存 
    201.                             DataBufferUtils.release(join); 
    202.                             String responseResult = new String(content, StandardCharsets.UTF_8); 
    203.  
    204.  
    205.  
    206.                             gatewayLog.setResponseData(responseResult); 
    207.  
    208.                             return bufferFactory.wrap(content); 
    209.                         })); 
    210.                     } 
    211.                 } 
    212.                 // if body is not a flux. never got there. 
    213.                 return super.writeWith(body); 
    214.             } 
    215.         }; 
    216.     } 

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

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

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

    存储日志

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

    • 引入MongoDB
    1.  
    2.     org.springframework.boot 
    3.     spring-boot-starter-data-mongodb-reactive 
    4.  

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

    • 在GatewayLog上添加对应的注解
    1. @Data 
    2. @Document 
    3. public class GatewayLog { 
    4.     @Id 
    5.     private String id; 
    6.  ... 
    • 建立AccessLogRepository
    1. @Repository 
    2. public interface AccessLogRepository extends ReactiveMongoRepository { 
    3.    
    • 建立Service
    1. public interface AccessLogService { 
    2.  
    3.     /** 
    4.      * 保存AccessLog 
    5.      * @param gatewayLog 请求响应日志 
    6.      * @return 响应日志 
    7.      */ 
    8.     Mono saveAccessLog(GatewayLog gatewayLog); 
    9.  
    • 建立实现类
    1. @Service 
    2. public class AccessLogServiceImpl implements AccessLogService { 
    3.     @Autowired 
    4.     private AccessLogRepository accessLogRepository; 
    5.  
    6.     @Override 
    7.     public Mono saveAccessLog(GatewayLog gatewayLog) { 
    8.         return accessLogRepository.insert(gatewayLog); 
    9.     } 
    • 在Nacos配置中心添加MongoDB对应配置
    1. spring: 
    2.   data: 
    3.     mongodb: 
    4.       host: xxx.xx.x.xx 
    5.       port: 27017 
    6.       database: accesslog 
    7.       username: accesslog 
    8.       password: xxxxxx 

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

    以上,希望对你有所帮助!

    严格来说,数据库是长期储存在计算机内、有组织的、可共享的数据集合。数据库中的数据指的是以一定的数据模型组织、描述和储存在一起、具有尽可能小的冗余度、较高的数据独立性和易扩展性的特点并可在一定范围内为多个用户共享。

课课家教育

未登录