成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

SpringCloud Alibaba實戰之SpringCloud Gateway 請求響應日志

運維 數據庫運維
請求響應日志是日常開發調試定位問題的重要手段,在微服務中引入SpringCloud Gateway后我們希望在網關層統一進行日志的收集。

[[389666]]

本文轉載自微信公眾號「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<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); 
  13.  
  14.     @Override 
  15.     public int getOrder() { 
  16.         return -100; 
  17.     } 
  18.  
  19.     @Override 
  20.     @SuppressWarnings("unchecked"
  21.     public Mono<Void> 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<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) { 
  51.         StringBuilder builder = new StringBuilder(); 
  52.         MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams(); 
  53.         for (Map.Entry<String, List<String>> 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<String> 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<DataBuffer> 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<Void> writeWith(Publisher<? extends DataBuffer> 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<? extends DataBuffer> 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. <dependency> 
  2.     <groupId>org.springframework.boot</groupId> 
  3.     <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> 
  4. </dependency> 

 

由于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<GatewayLog,String> { 
  3.    
  • 建立Service
  1. public interface AccessLogService { 
  2.  
  3.     /** 
  4.      * 保存AccessLog 
  5.      * @param gatewayLog 請求響應日志 
  6.      * @return 響應日志 
  7.      */ 
  8.     Mono<GatewayLog> saveAccessLog(GatewayLog gatewayLog); 
  9.  
  • 建立實現類
  1. @Service 
  2. public class AccessLogServiceImpl implements AccessLogService { 
  3.     @Autowired 
  4.     private AccessLogRepository accessLogRepository; 
  5.  
  6.     @Override 
  7.     public Mono<GatewayLog> 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客戶端,查看日志結果

 

以上,希望對你有所幫助!

 

責任編輯:武曉燕 來源: JAVA日知錄
相關推薦

2021-05-14 09:15:32

SpringCloud微服務日志

2022-04-09 14:45:02

微服務常見概念Spring

2024-06-04 10:05:48

微服務網關日志

2021-08-02 09:27:02

微服務接口場景

2022-05-29 21:38:11

限流熔斷流量

2021-01-28 10:10:51

微服務后端SpringCloud

2021-03-09 09:33:42

網關授權微服務

2023-11-08 07:45:47

Spring微服務

2022-04-27 08:23:34

微服務負載均衡

2021-04-22 09:31:58

服務器微服務配置

2021-02-04 09:18:20

服務器認證自定義

2022-08-29 06:27:15

Nacos微服務

2025-04-17 02:00:00

2021-06-09 09:42:50

SpringCloud微服務灰度發布

2021-07-07 06:38:33

SpringCloud路由配置定位原理分析

2021-08-26 11:52:32

FeignWeb服務

2017-04-13 11:06:28

SpringCloud隨機端口

2022-05-16 08:22:11

網關過濾器路由

2022-05-12 08:21:13

項目網關模塊

2022-06-12 07:27:39

微服務GateWay
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一区二区成人 | 久久久一区二区三区四区 | 亚洲视频欧美视频 | 澳门永久av免费网站 | 国产精品久久久久久久久久 | 久久另类视频 | www.天天操 | 日日夜夜天天 | 国产成人亚洲精品 | 日韩免费av一区二区 | 日韩色在线 | 日韩免费一区二区 | 欧美专区在线视频 | 做a视频在线观看 | 婷婷在线免费 | 久久久久国产一区二区三区不卡 | 亚洲高清三级 | 国产成人精品一区二区三区 | 国产在线一区二区 | 国产在线一区二区 | 国产精品视频偷伦精品视频 | 亚洲男人的天堂网站 | 亚洲精品福利在线 | 成人在线中文字幕 | 中文字幕免费视频 | 精产嫩模国品一二三区 | 九九色综合 | 91精品国产综合久久久动漫日韩 | 色综合视频 | 欧美亚洲综合久久 | 日本久久综合网 | 日韩欧美在线免费观看 | 99re视频这里只有精品 | 日韩在线| 国产精品久久久久一区二区三区 | 操操操日日日 | 国产精品久久亚洲7777 | 国产一区二区高清在线 | 一区二区精品电影 | 视频在线一区二区 | 午夜视频网站 |