diff --git a/youlai-gateway/src/main/java/com/youlai/gateway/log/LogFilter.java b/youlai-gateway/src/main/java/com/youlai/gateway/log/LogFilter.java
new file mode 100644
index 000000000..ca1d71be0
--- /dev/null
+++ b/youlai-gateway/src/main/java/com/youlai/gateway/log/LogFilter.java
@@ -0,0 +1,217 @@
+package com.youlai.gateway.log;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.reactivestreams.Publisher;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
+import org.springframework.cloud.gateway.support.BodyInserterContext;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.*;
+import org.springframework.http.HttpHeaders;
+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.http.server.reactive.ServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
+import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.BodyInserter;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.HandlerStrategies;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+/**
+ * 网关请求响应日志打印
+ *
+ *
+ * @author haoxr
+ * @see
+ * @date 2022/4/28 17:04
+ */
+@ConditionalOnProperty(
+ prefix = "log",
+ name = {"enabled"},
+ havingValue = "true", // 关闭日志请在youlai-gateway.yaml设置 log.enabled=false
+ matchIfMissing = true // true表示缺少log.enabled属性也会加载该bean
+)
+@Component
+@Slf4j
+public class LogFilter implements GlobalFilter, Ordered {
+
+ private final List> messageReaders = HandlerStrategies.withDefaults().messageReaders();
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+
+ ServerHttpRequest request = exchange.getRequest();
+ String requestPath = request.getPath().pathWithinApplication().value();
+ String requestMethod = request.getMethodValue();
+
+ TraceLog traceLog = new TraceLog();
+ traceLog.setRequestPath(requestPath);
+ traceLog.setRequestMethod(requestMethod);
+ traceLog.setRequestTime(DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_MS_PATTERN));
+
+ MediaType contentType = request.getHeaders().getContentType();
+
+ if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)
+ || MediaType.APPLICATION_JSON.isCompatibleWith(contentType)
+ ) {
+ return writeBodyLog(exchange, chain, traceLog);
+ } else {
+ return writeBasicLog(exchange, chain, traceLog);
+ }
+ }
+
+ public Mono writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, TraceLog traceLog) {
+ StringBuilder sb = new StringBuilder();
+ MultiValueMap queryParams = exchange.getRequest().getQueryParams();
+
+ for (Map.Entry> entry : queryParams.entrySet()) {
+ String val = entry.getValue().stream().map(String::valueOf).collect(Collectors.joining(","));
+ sb.append(entry.getKey()).append("=").append(val).append("&");
+ }
+ if (sb.length() > 0) {
+ traceLog.setRequestBody(sb.substring(0, sb.length() - 1));
+ }
+ ServerHttpResponseDecorator serverHttpResponseDecorator = serverHttpResponseDecorator(exchange, traceLog);
+ return chain.filter(exchange.mutate().response(serverHttpResponseDecorator)
+ .build())
+ .then(Mono.fromRunnable(() -> {
+ log.info(traceLog.toString());
+ }));
+ }
+
+
+ /**
+ * 解决 request body 只能读取一次问题,
+ *
+ * @param exchange
+ * @param chain
+ * @param traceLog
+ * @return
+ * @see org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
+ */
+ public Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, TraceLog traceLog) {
+
+ ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
+
+ Mono cachedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
+ traceLog.setRequestBody(body);
+ return Mono.just(body);
+ });
+
+ BodyInserter bodyInserter = BodyInserters.fromPublisher(cachedBody, String.class);
+ HttpHeaders headers = new HttpHeaders();
+ headers.putAll(exchange.getRequest().getHeaders());
+ headers.remove(HttpHeaders.CONTENT_LENGTH);
+
+ CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
+ return bodyInserter.insert(outputMessage, new BodyInserterContext())
+ .then(Mono.defer(() -> {
+ ServerHttpRequest serverHttpRequest = serverHttpRequestDecorator(exchange, headers, outputMessage);
+ ServerHttpResponseDecorator serverHttpResponseDecorator = serverHttpResponseDecorator(exchange, traceLog);
+ return chain.filter(exchange.mutate().request(serverHttpRequest).response(serverHttpResponseDecorator).build())
+ .then(Mono.fromRunnable(() -> {
+ log.info(traceLog.toString());
+ }));
+ }));
+ }
+
+
+ private ServerHttpRequestDecorator serverHttpRequestDecorator(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 {
+ httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
+ }
+ return httpHeaders;
+ }
+
+ @Override
+ public Flux getBody() {
+ return outputMessage.getBody();
+ }
+ };
+ }
+
+
+ /**
+ * 响应结果读取
+ *
+ * @param exchange
+ * @param traceLog
+ * @return
+ */
+ private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerWebExchange exchange, TraceLog traceLog) {
+ 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 now = new Date();
+ String responseTime = DateUtil.format(now, DatePattern.NORM_DATETIME_MS_PATTERN);
+ traceLog.setResponseTime(responseTime);
+ long executeTime = DateUtil.betweenMs(DateUtil.parse(traceLog.getRequestTime(), DatePattern.NORM_DATETIME_MS_FORMATTER), now);
+ traceLog.setExecuteTime(executeTime);
+
+ String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
+
+ if (StrUtil.isNotBlank(originalResponseContentType)
+ && originalResponseContentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
+
+ Flux extends DataBuffer> fluxBody = Flux.from(body);
+
+ return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
+
+ DefaultDataBuffer dataBuffer = new DefaultDataBufferFactory().join(dataBuffers);
+
+ byte[] content = new byte[dataBuffer.readableByteCount()];
+ dataBuffer.read(content);
+
+ DataBufferUtils.release(dataBuffer);
+
+ String responseBody = new String(content, StandardCharsets.UTF_8);
+ traceLog.setResponseBody(responseBody);
+ return bufferFactory.wrap(content);
+ }));
+ }
+ }
+ return super.writeWith(body);
+ }
+ };
+ }
+
+ @Override
+ public int getOrder() {
+ return -1;
+ }
+}
diff --git a/youlai-gateway/src/main/java/com/youlai/gateway/log/TraceLog.java b/youlai-gateway/src/main/java/com/youlai/gateway/log/TraceLog.java
new file mode 100644
index 000000000..b50cdef7d
--- /dev/null
+++ b/youlai-gateway/src/main/java/com/youlai/gateway/log/TraceLog.java
@@ -0,0 +1,61 @@
+package com.youlai.gateway.log;
+
+import lombok.Data;
+
+/**
+ * 请求响应日志实体对象
+ *
+ * @author haoxr
+ * @date 2022/4/28 16:54
+ */
+@Data
+public class TraceLog {
+
+ /**
+ * 请求路径
+ */
+ private String requestPath;
+
+ /**
+ * 请求方法
+ */
+ private String requestMethod;
+
+ /**
+ * 请求实体
+ */
+ private String requestBody;
+
+ /**
+ * 响应数据
+ */
+ private String responseBody;
+
+ /**
+ * 请求时间
+ */
+ private String requestTime;
+
+ /**
+ * 响应时间
+ */
+ private String responseTime;
+
+ /**
+ * 执行耗时(毫秒)
+ */
+ private Long executeTime;
+
+
+ @Override
+ public String toString() {
+ return "========网关请求响应日志========\n" +
+ "请求路径:" + requestPath + '\n' +
+ "请求方法:" + requestMethod + '\n' +
+ "请求参数:" + requestBody + '\n' +
+ "响应数据:" + responseBody + '\n' +
+ "请求时间:" + requestTime + '\n' +
+ "响应时间:" + responseTime + '\n' +
+ "执行耗时:" + executeTime + "毫秒";
+ }
+}