溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

在Spring中使用 Cloud與Gateway怎么記錄請求并應(yīng)答數(shù)據(jù)日志

發(fā)布時間:2020-12-01 15:02:28 來源:億速云 閱讀:2303 作者:Leah 欄目:開發(fā)技術(shù)

本篇文章給大家分享的是有關(guān)在Spring中使用 Cloud與Gateway怎么記錄請求并應(yīng)答數(shù)據(jù)日志,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

具體方法如下:

public class GatewayContext {
 public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";
 /**
  * cache json body
  */
 private String cacheBody;
 /**
  * cache formdata
  */
 private MultiValueMap<String, String> formData;
 /**
  * cache reqeust path
  */
 private String path;
 public String getCacheBody() {
  return cacheBody;
 }
 public void setCacheBody(String cacheBody) {
  this.cacheBody = cacheBody;
 }
 public MultiValueMap<String, String> getFormData() {
  return formData;
 }
 public void setFormData(MultiValueMap<String, String> formData) {
  this.formData = formData;
 }
 public String getPath() {
  return path;
 }
 public void setPath(String path) {
  this.path = path;
 }
}
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
// https://segmentfault.com/a/1190000017898354
@Component
public class LogRequestGlobalFilter
  implements GlobalFilter {
 /**
  * default HttpMessageReader
  */
 private static final List<HttpMessageReader<?>> messageReaders =
   HandlerStrategies.withDefaults().messageReaders();
 private Logger log = LoggerFactory.getLogger(LogRequestGlobalFilter.class);
 @Override
 public Mono<Void> filter(
   ServerWebExchange exchange,
   GatewayFilterChain chain) {
  /**
   * save request path and serviceId into gateway context
   */
  ServerHttpRequest request = exchange.getRequest();
  String path = request.getPath().pathWithinApplication().value();
  GatewayContext gatewayContext = new GatewayContext();
  gatewayContext.setPath(path);
  /**
   * save gateway context into exchange
   */
  exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT,
    gatewayContext);
  HttpHeaders headers = request.getHeaders();
  MediaType contentType = headers.getContentType();
  log.info("start-------------------------------------------------");
  log.info("HttpMethod:{},Url:{}", request.getMethod(),
    request.getURI().getRawPath());
  log.info("Headers token: {}", headers.getFirst("token"));
  if (request.getMethod() == HttpMethod.GET) {
   log.info("end-------------------------------------------------");
  }
  if (request.getMethod() == HttpMethod.POST) {
   Mono<Void> voidMono = null;
   if (MediaType.APPLICATION_JSON.equals(contentType)
     || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
    voidMono =
      readBody(exchange, chain, gatewayContext);
   }
   if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
    voidMono =
      readFormData(exchange, chain, gatewayContext);
   }
   return voidMono;
  }
  /* log.debug(
   "[GatewayContext]ContentType:{},Gateway context is set with {}",
   contentType, gatewayContext);*/
  return chain.filter(exchange);
 }
 /**
  * ReadFormData
  *
  * @param exchange
  * @param chain
  * @return
  */
 private Mono<Void> readFormData(
   ServerWebExchange exchange,
   GatewayFilterChain chain,
   GatewayContext gatewayContext) {
  final ServerHttpRequest request = exchange.getRequest();
  HttpHeaders headers = request.getHeaders();
  return exchange.getFormData()
    .doOnNext(multiValueMap -> {
     gatewayContext.setFormData(multiValueMap);
     log.info("Post x-www-form-urlencoded:{}",
       multiValueMap);
     log.info(
       "end-------------------------------------------------");
    })
    .then(Mono.defer(() -> {
     Charset charset = headers.getContentType().getCharset();
     charset = charset == null ? StandardCharsets.UTF_8 : charset;
     String charsetName = charset.name();
     MultiValueMap<String, String> formData =
       gatewayContext.getFormData();
     /**
      * formData is empty just return
      */
     if (null == formData || formData.isEmpty()) {
      return chain.filter(exchange);
     }
     StringBuilder formDataBodyBuilder = new StringBuilder();
     String entryKey;
     List<String> entryValue;
     try {
      /**
       * repackage form data
       */
      for (Map.Entry<String, List<String>> entry : formData
        .entrySet()) {
       entryKey = entry.getKey();
       entryValue = entry.getValue();
       if (entryValue.size() > 1) {
        for (String value : entryValue) {
         formDataBodyBuilder.append(entryKey).append("=")
           .append(
             URLEncoder.encode(value, charsetName))
           .append("&");
        }
       } else {
        formDataBodyBuilder
          .append(entryKey).append("=").append(URLEncoder
          .encode(entryValue.get(0), charsetName))
          .append("&");
       }
      }
     } catch (UnsupportedEncodingException e) {
      // ignore URLEncode Exception
     }
     /**
      * substring with the last char '&'
      */
     String formDataBodyString = "";
     if (formDataBodyBuilder.length() > 0) {
      formDataBodyString = formDataBodyBuilder.substring(0,
        formDataBodyBuilder.length() - 1);
     }
     /**
      * get data bytes
      */
     byte[] bodyBytes = formDataBodyString.getBytes(charset);
     int contentLength = bodyBytes.length;
     ServerHttpRequestDecorator decorator =
       new ServerHttpRequestDecorator(
         request) {
        /**
         * change content-length
         *
         * @return
         */
        @Override
        public HttpHeaders getHeaders() {
         HttpHeaders httpHeaders = new HttpHeaders();
         httpHeaders.putAll(super.getHeaders());
         if (contentLength > 0) {
          httpHeaders.setContentLength(contentLength);
         } else {
          httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,
            "chunked");
         }
         return httpHeaders;
        }
        /**
         * read bytes to Flux<Databuffer>
         *
         * @return
         */
        @Override
        public Flux<DataBuffer> getBody() {
         return DataBufferUtils
           .read(new ByteArrayResource(bodyBytes),
             new NettyDataBufferFactory(
               ByteBufAllocator.DEFAULT),
             contentLength);
        }
       };
     ServerWebExchange mutateExchange =
       exchange.mutate().request(decorator).build();
    /* log.info("[GatewayContext]Rewrite Form Data :{}",
      formDataBodyString);*/
     return chain.filter(mutateExchange);
    }));
 }
 /**
  * ReadJsonBody
  *
  * @param exchange
  * @param chain
  * @return
  */
 private Mono<Void> readBody(
   ServerWebExchange exchange,
   GatewayFilterChain chain,
   GatewayContext gatewayContext) {
  /**
   * join the body
   */
  return DataBufferUtils.join(exchange.getRequest().getBody())
    .flatMap(dataBuffer -> {
     /*
      * read the body Flux<DataBuffer>, and release the buffer
      * //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature
      * see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095
      */
     byte[] bytes = new byte[dataBuffer.readableByteCount()];
     dataBuffer.read(bytes);
     DataBufferUtils.release(dataBuffer);
     Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
      DataBuffer buffer =
        exchange.getResponse().bufferFactory().wrap(bytes);
      DataBufferUtils.retain(buffer);
      return Mono.just(buffer);
     });
     /**
      * repackage ServerHttpRequest
      */
     ServerHttpRequest mutatedRequest =
       new ServerHttpRequestDecorator(exchange.getRequest()) {
        @Override
        public Flux<DataBuffer> getBody() {
         return cachedFlux;
        }
       };
     /**
      * mutate exchage with new ServerHttpRequest
      */
     ServerWebExchange mutatedExchange =
       exchange.mutate().request(mutatedRequest).build();
     /**
      * read body string with default messageReaders
      */
     return ServerRequest.create(mutatedExchange, messageReaders)
       .bodyToMono(String.class)
       .doOnNext(objectValue -> {
        log.info("PostBody:{}", objectValue);
        log.info(
          "end-------------------------------------------------");
        gatewayContext.setCacheBody(objectValue);
      /* log.debug("[GatewayContext]Read JsonBody:{}",
        objectValue);*/
       }).then(chain.filter(mutatedExchange));
    });
 }
}
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
@Component
@Slf4j
public class LogResponseGlobalFilter implements GlobalFilter, Ordered {
 private static final String REQUEST_PREFIX = "Request Info [ ";
 private static final String REQUEST_TAIL = " ]";
 private static final String RESPONSE_PREFIX = "Response Info [ ";
 private static final String RESPONSE_TAIL = " ]";
 private StringBuilder normalMsg = new StringBuilder();
 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  ServerHttpRequest request = exchange.getRequest();
  ServerHttpResponse response = exchange.getResponse();
  DataBufferFactory bufferFactory = response.bufferFactory();
  normalMsg.append(RESPONSE_PREFIX);
  ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
   @Override
   public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
    if (body instanceof Flux) {
     Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
     return super.writeWith(fluxBody.map(dataBuffer -> {
      // probably should reuse buffers
      byte[] content = new byte[dataBuffer.readableByteCount()];
      dataBuffer.read(content);
      String responseResult = new String(content, Charset.forName("UTF-8"));
      normalMsg.append("status=").append(this.getStatusCode());
      normalMsg.append(";header=").append(this.getHeaders());
      normalMsg.append(";responseResult=").append(responseResult);
      normalMsg.append(RESPONSE_TAIL);
      log.info(normalMsg.toString());
      return bufferFactory.wrap(content);
     }));
    }
    return super.writeWith(body); // if body is not a flux. never got there.
   }
  };
  return chain.filter(exchange.mutate().response(decoratedResponse).build());
 }
 @Override
 public int getOrder() {
  return -2;
 }
}

補充知識:Spring Cloud Gateway 2.x 打印 Log

場景

在服務(wù)網(wǎng)關(guān)層面,需要打印出用戶每次的請求body和其他的參數(shù),gateway使用的是Reactor響應(yīng)式編程,和Zuul網(wǎng)關(guān)獲取流的寫法還有些不同,

不過基本的思路是一樣的,都是在filter中讀取body流,然后緩存回去,因為body流,框架默認只允許讀取一次。

思路

1. 添加一個filter做一次請求的攔截

GatewayConfig.java

添加一個配置類,配置一個高優(yōu)先級的filter,并且注入一個PayloadServerWebExchangeDecorator 對request和response做包裝的類。

package com.demo.gateway2x.config;
import com.demo.gateway2x.decorator.PayloadServerWebExchangeDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.WebFilter;
@Configuration
public class GatewayConfig {
 @Bean
 @Order(Ordered.HIGHEST_PRECEDENCE) //過濾器順序
 public WebFilter webFilter() {
  return (exchange, chain) -> chain.filter(new PayloadServerWebExchangeDecorator(exchange));
 }
}

PayloadServerWebExchangeDecorator.java

這個類中,我們實現(xiàn)了框架的ServerWebExchangeDecorator類,同時注入了自定義的兩個類,PartnerServerHttpRequestDecorator 和 PartnerServerHttpResponseDecorator ,

這兩個類用于后面對請求與響應(yīng)的攔截。

package com.demo.gateway2x.decorator;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
public class PayloadServerWebExchangeDecorator extends ServerWebExchangeDecorator {
 private PartnerServerHttpRequestDecorator requestDecorator;
 private PartnerServerHttpResponseDecorator responseDecorator;
 public PayloadServerWebExchangeDecorator(ServerWebExchange delegate) {
  super(delegate);
  requestDecorator = new PartnerServerHttpRequestDecorator(delegate.getRequest());
  responseDecorator = new PartnerServerHttpResponseDecorator(delegate.getResponse());
 }
 @Override
 public ServerHttpRequest getRequest() {
  return requestDecorator;
 }
 @Override
 public ServerHttpResponse getResponse() {
  return responseDecorator;
 }
}

2. 在請求進入時,對request做一次攔截

PartnerServerHttpRequestDecorator.java

這個類實現(xiàn)了 ServerHttpRequestDecorator , 并在構(gòu)造函數(shù)中,使用響應(yīng)式編程,調(diào)用了打印log的方法,注意關(guān)注 Mono<DataBuffer> mono = DataBufferUtils.join(flux); ,

這里將Flux合并成了一個Mono,因為如果不這么做,body內(nèi)容過多,將會被分段打印,這里是一個恒重要的點,

在打印RequestParamsHandle.chain打印過日志后,我們又返回了一個dataBuffer,用作向下傳遞,否則dataBuffer被讀取過一次后就不能繼續(xù)使用了。

package com.demo.gateway2x.decorator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static reactor.core.scheduler.Schedulers.single;
@Slf4j
public class PartnerServerHttpRequestDecorator extends ServerHttpRequestDecorator {
 private Flux<DataBuffer> body;
 public PartnerServerHttpRequestDecorator(ServerHttpRequest delegate) {
  super(delegate);
  Flux<DataBuffer> flux = super.getBody();
  if (ParamsUtils.CHAIN_MEDIA_TYPE.contains(delegate.getHeaders().getContentType())) {
   Mono<DataBuffer> mono = DataBufferUtils.join(flux);
   body = mono.publishOn(single()).map(dataBuffer -> RequestParamsHandle.chain(delegate, log, dataBuffer)).flux();
  } else {
   body = flux;
  }
 }
 @Override
 public Flux<DataBuffer> getBody() {
  return body;
 }
}

RequestParamsHandle.java

這個類主要用來讀取dataBuffer并做了日志打印處理,也可以做一些其他的例如參數(shù)校驗等使用。

package com.demo.gateway2x.decorator;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
public class RequestParamsHandle {
 public static <T extends DataBuffer> T chain(ServerHttpRequest delegate, Logger log, T buffer) {
  ParamsUtils.BodyDecorator bodyDecorator = ParamsUtils.buildBodyDecorator(buffer);
  // 參數(shù)校驗 和 參數(shù)打印
  log.info("Payload: {}", JSON.toJSONString(validParams(getParams(delegate, bodyDecorator.getBody()))));
  return (T) bodyDecorator.getDataBuffer();
 }
 public static Map<String,Object> getParams(ServerHttpRequest delegate, String body) {
  // 整理參數(shù)
  Map<String,Object> params = new HashMap<>();
  if (delegate.getQueryParams() != null) {
   params.putAll(delegate.getQueryParams());
  }
  if (!StringUtils.isEmpty(body)) {
   params.putAll(JSON.parseObject(body));
  }
  return params;
 }
 public static Map<String,Object> validParams(Map<String,Object> params) {
  // todo 參數(shù)校驗
  return params;
 }
}

3. 在結(jié)果返回時,對response做一次攔截

PartnerServerHttpResponseDecorator.java

這個類和上面的request的異曲同工,攔截響應(yīng)流,并做記錄入處理。

package com.demo.gateway2x.decorator;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static reactor.core.scheduler.Schedulers.single;
@Slf4j
public class PartnerServerHttpResponseDecorator extends ServerHttpResponseDecorator {
 PartnerServerHttpResponseDecorator(ServerHttpResponse delegate) {
  super(delegate);
 }
 @Override
 public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
  return super.writeAndFlushWith(body);
 }
 @Override
 public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
  final MediaType contentType = super.getHeaders().getContentType();
  if (ParamsUtils.CHAIN_MEDIA_TYPE.contains(contentType)) {
   if (body instanceof Mono) {
    final Mono<DataBuffer> monoBody = (Mono<DataBuffer>) body;
    return super.writeWith(monoBody.publishOn(single()).map(dataBuffer -> ResponseParamsHandle.chain(log, dataBuffer)));
   } else if (body instanceof Flux) {
    Mono<DataBuffer> mono = DataBufferUtils.join(body);
    final Flux<DataBuffer> monoBody = mono.publishOn(single()).map(dataBuffer -> ResponseParamsHandle.chain(log, dataBuffer)).flux();
    return super.writeWith(monoBody);
   }
  }
  return super.writeWith(body);
 }
}

ResponseParamsHandle.java

響應(yīng)流的日志打印

package com.demo.gateway2x.decorator;
import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;
public class ResponseParamsHandle {
 public static <T extends DataBuffer> T chain(Logger log, T buffer) {
  ParamsUtils.BodyDecorator bodyDecorator = ParamsUtils.buildBodyDecorator(buffer);
  // 參數(shù)校驗 和 參數(shù)打印
  log.info("Payload: {}", bodyDecorator.getBody());
  return (T) bodyDecorator.getDataBuffer();
 }
}

下面是實際操作,發(fā)送一次http請求:

在Spring中使用 Cloud與Gateway怎么記錄請求并應(yīng)答數(shù)據(jù)日志

在Spring中使用 Cloud與Gateway怎么記錄請求并應(yīng)答數(shù)據(jù)日志

控制臺log結(jié)果:

在Spring中使用 Cloud與Gateway怎么記錄請求并應(yīng)答數(shù)據(jù)日志

總結(jié)

gateway和zuul打印參數(shù)的方式思路是一致的,只是gateway采用的是reactor,寫法上與zuul的直接讀取流有些不同,這里需要知道的是Flux需要轉(zhuǎn)換為Mono這個地方,如果不轉(zhuǎn)換容易分多批打印。

以上就是在Spring中使用 Cloud與Gateway怎么記錄請求并應(yīng)答數(shù)據(jù)日志,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI