• 从零搭建Spring Cloud Gateway网关(二)—— 打印请求响应日志


    作为网关,日志记录是必不可少的功能,可以在网关出增加requestId来查询整个请求链的调用执行情况等等。

    打印请求日志

    打印请求日志最重要的就是打印请求参数这些东西,不过RequestBody通常情况下在被读取一次之后就会失效,这样的话,下游的服务就不能正常获取到请求参数了。所以我们需要重写下请求体。

    具体方法呢有很多,这里说一下我用的两种:

    第一种

    代码如下:

    package com.lifengdi.gateway.filter;
    
    import com.lifengdi.gateway.constant.HeaderConstant;
    import com.lifengdi.gateway.constant.OrderedConstant;
    import com.lifengdi.gateway.log.Log;
    import com.lifengdi.gateway.log.LogHelper;
    import com.lifengdi.gateway.utils.GenerateIdUtils;
    import com.lifengdi.gateway.utils.IpUtils;
    import io.netty.buffer.UnpooledByteBufAllocator;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    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.NettyDataBufferFactory;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StopWatch;
    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.net.URI;
    import java.util.Objects;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.function.Consumer;
    
    /**
     * 请求日志打印
     */
    @Component
    @Slf4j
    public class RequestLogFilter implements GlobalFilter, Ordered {
    
        @Override
        public int getOrder() {
            return OrderedConstant.REQUEST_FILTER;
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            long startTime = System.currentTimeMillis();
            try {
                ServerHttpRequest request = exchange.getRequest();
                // 设置X-Request-Id
                AtomicReference<String> requestId = new AtomicReference<>(GenerateIdUtils.requestIdWithUUID());
                Consumer<HttpHeaders> httpHeadersConsumer = httpHeaders -> {
                    String headerRequestId = request.getHeaders().getFirst(HeaderConstant.REQUEST_ID);
                    if (StringUtils.isBlank(headerRequestId)) {
                        httpHeaders.set(HeaderConstant.REQUEST_ID, requestId.get());
                    } else {
                        requestId.set(headerRequestId);
                    }
                    httpHeaders.set(HeaderConstant.START_TIME_KEY, String.valueOf(startTime));
                };
                ServerRequest serverRequest = ServerRequest.create(exchange,
                        HandlerStrategies.withDefaults().messageReaders());
                URI requestUri = request.getURI();
                String uriQuery = requestUri.getQuery();
                String url = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
                HttpHeaders headers = request.getHeaders();
                MediaType mediaType = headers.getContentType();
                String method = request.getMethodValue().toUpperCase();
    
                // 原始请求体
                final AtomicReference<String> requestBody = new AtomicReference<>();
                final AtomicBoolean newBody = new AtomicBoolean(false);
                if (Objects.nonNull(mediaType) && LogHelper.isUploadFile(mediaType)) {
                    requestBody.set("上传文件");
                } else {
                    if (method.equals("GET")) {
                        if (StringUtils.isNotBlank(uriQuery)) {
                            requestBody.set(uriQuery);
                        }
                    } else {
                        newBody.set(true);
                    }
                }
                final Log logDTO = new Log();
                logDTO.setLevel(Log.LEVEL.INFO);
                logDTO.setRequestUrl(url);
                logDTO.setRequestBody(requestBody.get());
                logDTO.setRequestMethod(method);
                logDTO.setRequestId(requestId.get());
                logDTO.setIp(IpUtils.getClientIp(request));
    
                ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeadersConsumer).build();
                ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
                return build.getSession().flatMap(webSession -> {
                    logDTO.setSessionId(webSession.getId());
                    if (newBody.get() && headers.getContentLength() > 0) {
                        Mono<String> bodyToMono = serverRequest.bodyToMono(String.class);
                        return bodyToMono.flatMap(reqBody -> {
                            logDTO.setRequestBody(reqBody);
                            // 重写原始请求
                            ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                                    DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
                                    return Flux.just(bodyDataBuffer);
                                }
                            };
                            return chain.filter(exchange.mutate()
                                    .request(requestDecorator)
                                    .build()).then(LogHelper.doRecord(logDTO));
                        });
                    } else {
                        return chain.filter(exchange).then(LogHelper.doRecord(logDTO));
                    }
                });
    
            } catch (Exception e) {
                log.error("请求日志打印出现异常", e);
                return chain.filter(exchange);
            }
        }
    
    }
    
    

    上面的核心代码是:

    // 重写原始请求
    ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
        @Override
        public Flux<DataBuffer> getBody() {
            NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
            DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
            return Flux.just(bodyDataBuffer);
        }
    };
    return chain.filter(exchange.mutate()
            .request(requestDecorator)
            .build()).then(LogHelper.doRecord(logDTO));
    

    如果不需要对session进行操作,可以直接调用这块就行。

    关于请求时间,我这里采用的是将时间戳放进请求头中,等到打印日志的时候再从请求头中读取然后计算出时间。否则如果单独在某个filter中计算请求时间,会造成时间不太准确。当然这样时间也不是很准确,毕竟还有Spring本身的filter等业务逻辑,不过时间相差不是很大,大概十几毫秒的样子。

    第二种

    第二种就是自己缓存下请求体,读取的时候读取缓存内容。

    代码如下:

    package com.lifengdi.gateway.log;
    
    import com.lifengdi.gateway.constant.HeaderConstant;
    import com.lifengdi.gateway.utils.IpUtils;
    import io.netty.buffer.UnpooledByteBufAllocator;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.core.io.buffer.NettyDataBufferFactory;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
    import reactor.core.publisher.Flux;
    import reactor.core.scheduler.Schedulers;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URI;
    import java.util.Objects;
    
    /**
     * 对ServerHttpRequest进行二次封装,解决requestBody只能读取一次的问题
     * @author: Li Fengdi
     * @date: 2020-03-17 18:02
     */
    @Slf4j
    public class CacheServerHttpRequestDecorator extends ServerHttpRequestDecorator {
        private DataBuffer bodyDataBuffer;
        private int getBufferTime = 0;
        private byte[] bytes;
    
        public CacheServerHttpRequestDecorator(ServerHttpRequest delegate) {
            super(delegate);
        }
    
        @Override
        public Flux<DataBuffer> getBody() {
            if (getBufferTime == 0) {
                getBufferTime++;
                Flux<DataBuffer> flux = super.getBody();
                return flux.publishOn(Schedulers.single())
                        .map(this::cache)
                        .doOnComplete(() -> trace(getDelegate()));
    
            } else {
                return Flux.just(getBodyMore());
            }
    
        }
    
    
        private DataBuffer getBodyMore() {
            NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
            bodyDataBuffer = nettyDataBufferFactory.wrap(bytes);
            return bodyDataBuffer;
        }
    
        private DataBuffer cache(DataBuffer buffer) {
            try {
                InputStream dataBuffer = buffer.asInputStream();
                bytes = IOUtils.toByteArray(dataBuffer);
                NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                bodyDataBuffer = nettyDataBufferFactory.wrap(bytes);
                return bodyDataBuffer;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private void trace(ServerHttpRequest request) {
            URI requestUri = request.getURI();
            String uriQuery = requestUri.getQuery();
            String url = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
            HttpHeaders headers = request.getHeaders();
            MediaType mediaType = headers.getContentType();
            String schema = requestUri.getScheme();
            String method = request.getMethodValue().toUpperCase();
            if ((!"http".equals(schema) && !"https".equals(schema))) {
                return;
            }
            String reqBody = null;
            if (Objects.nonNull(mediaType) && LogHelper.isUploadFile(mediaType)) {
                reqBody = "上传文件";
            } else {
                if (method.equals("GET")) {
                    if (StringUtils.isNotBlank(uriQuery)) {
                        reqBody = uriQuery;
                    }
                } else if (headers.getContentLength() > 0) {
                    reqBody = LogHelper.readRequestBody(request);
                }
            }
            final Log logDTO = new Log();
            logDTO.setLevel(Log.LEVEL.INFO);
            logDTO.setRequestUrl(url);
            logDTO.setRequestBody(reqBody);
            logDTO.setRequestMethod(method);
            logDTO.setRequestId(headers.getFirst(HeaderConstant.REQUEST_ID));
            logDTO.setIp(IpUtils.getClientIp(request));
            log.info(LogHelper.toJsonString(logDTO));
        }
    
    }
    

    filter这里就简单写下:

    package com.lifengdi.gateway.filter;
    
    import com.lifengdi.gateway.constant.OrderedConstant;
    import com.lifengdi.gateway.log.CacheServerHttpRequestDecorator;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    /**
     * @author: Li Fengdi
     * @date: 2020-03-17 18:17
     */
    //@Component
    public class LogFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            CacheServerHttpRequestDecorator cacheServerHttpRequestDecorator = new CacheServerHttpRequestDecorator(exchange.getRequest());
    
            return chain.filter(exchange.mutate().request(cacheServerHttpRequestDecorator).build());
        }
    
        @Override
        public int getOrder() {
            return OrderedConstant.LOGGING_FILTER;
        }
    }
    

    工具类也贴下:

    package com.lifengdi.gateway.log;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.lifengdi.gateway.constant.HeaderConstant;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.core.io.buffer.DataBufferUtils;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.lang.NonNull;
    import org.springframework.lang.Nullable;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.util.Objects;
    import java.util.concurrent.atomic.AtomicReference;
    
    @Slf4j
    public class LogHelper {
    
        private final static ObjectMapper objectMapper = new ObjectMapper();
    
        /**
         * Log转JSON
         * @param dto Log
         * @return JSON字符串
         */
        public static String toJsonString(@NonNull Log dto) {
            try {
                return objectMapper.writeValueAsString(dto);
            } catch (JsonProcessingException e) {
                log.error("Log转换JSON异常", e);
                return null;
            }
        }
    
        /**
         * 根据MediaType获取字符集,如果获取不到,则默认返回<tt>UTF_8</tt>
         * @param mediaType MediaType
         * @return Charset
         */
        public static Charset getMediaTypeCharset(@Nullable MediaType mediaType) {
            if (Objects.nonNull(mediaType) && mediaType.getCharset() != null) {
                return mediaType.getCharset();
            } else {
                return StandardCharsets.UTF_8;
            }
        }
    
        /**
         * 记录日志(后期可扩展为通过MQ将日志发送到ELK系统)
         * @param dto Log
         * @return Mono.empty()
         */
        public static Mono<Void> doRecord(Log dto) {
            log.info(toJsonString(dto));
            return Mono.empty();
        }
    
        /**
         * 从HttpHeaders获取请求开始时间
         * <p>
         *     要求请求头中必须要有参数{@link HeaderConstant#START_TIME_KEY},否则将返回当前时间戳
         * </p>
         * @param headers HttpHeaders请求头
         * @return 开始时间时间戳(Mills)
         */
        public static long getStartTime(HttpHeaders headers) {
            String startTimeStr = headers.getFirst(HeaderConstant.START_TIME_KEY);
            return StringUtils.isNotBlank(startTimeStr) ? Long.parseLong(startTimeStr) : System.currentTimeMillis();
        }
    
        /**
         * 根据HttpHeaders请求头获取请求执行时间
         * <p>
         *     要求请求头中必须要有参数{@link HeaderConstant#START_TIME_KEY}
         * </p>
         * @param headers HttpHeaders请求头
         * @return 请求执行时间
         */
        public static long getHandleTime(HttpHeaders headers) {
            String startTimeStr = headers.getFirst(HeaderConstant.START_TIME_KEY);
            long startTime = StringUtils.isNotBlank(startTimeStr) ? Long.parseLong(startTimeStr) : System.currentTimeMillis();
            return System.currentTimeMillis() - startTime;
        }
    
        /**
         * 读取请求体内容
         * @param request ServerHttpRequest
         * @return 请求体
         */
        public static String readRequestBody(ServerHttpRequest request) {
            HttpHeaders headers = request.getHeaders();
            MediaType mediaType = headers.getContentType();
            String method = request.getMethodValue().toUpperCase();
            if (Objects.nonNull(mediaType) && mediaType.equals(MediaType.MULTIPART_FORM_DATA)) {
                return "上传文件";
            } else {
                if (method.equals("GET")) {
                    if (!request.getQueryParams().isEmpty()) {
                        return request.getQueryParams().toString();
                    }
                    return null;
                } else {
                    AtomicReference<String> bodyString = new AtomicReference<>();
                    request.getBody().subscribe(buffer -> {
                        byte[] bytes = new byte[buffer.readableByteCount()];
                        buffer.read(bytes);
                        DataBufferUtils.release(buffer);
                        bodyString.set(new String(bytes, getMediaTypeCharset(mediaType)));
                    });
                    return bodyString.get();
                }
            }
        }
    
        /**
         * 判断是否是上传文件
         * @param mediaType MediaType
         * @return Boolean
         */
        public static boolean isUploadFile(@Nullable MediaType mediaType) {
            if (Objects.isNull(mediaType)) {
                return false;
            }
            return mediaType.equals(MediaType.MULTIPART_FORM_DATA)
                    || mediaType.equals(MediaType.IMAGE_GIF)
                    || mediaType.equals(MediaType.IMAGE_JPEG)
                    || mediaType.equals(MediaType.IMAGE_PNG)
                    || mediaType.equals(MediaType.MULTIPART_MIXED);
        }
    }
    

    打印响应报文

    响应报文需要在Spring重写了响应体之后才能获取到,所以对filter的执行顺序有要求,需要在
    NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER之前执行。代码如下:

    package com.lifengdi.gateway.filter;
    
    import com.lifengdi.gateway.constant.HeaderConstant;
    import com.lifengdi.gateway.constant.OrderedConstant;
    import com.lifengdi.gateway.log.Log;
    import com.lifengdi.gateway.log.LogHelper;
    import com.lifengdi.gateway.utils.IpUtils;
    import io.netty.buffer.UnpooledByteBufAllocator;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    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.core.io.buffer.DataBufferUtils;
    import org.springframework.core.io.buffer.NettyDataBufferFactory;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    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.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.net.URI;
    import java.nio.charset.Charset;
    import java.util.Objects;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * 请求响应日志打印
     */
    @Component
    @Slf4j
    public class ResponseLogFilter implements GlobalFilter, Ordered {
    
        @Override
        public int getOrder() {
            return OrderedConstant.LOGGING_FILTER;
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            try {
                ServerHttpRequest request = exchange.getRequest();
                ServerRequest serverRequest = ServerRequest.create(exchange,
                        HandlerStrategies.withDefaults().messageReaders());
                URI requestUri = request.getURI();
                String uriQuery = requestUri.getQuery();
                HttpHeaders headers = request.getHeaders();
                MediaType mediaType = headers.getContentType();
                String schema = requestUri.getScheme();
                String method = request.getMethodValue().toUpperCase();
    
                // 只记录http、https请求
                if ((!"http".equals(schema) && !"https".equals(schema))) {
                    return chain.filter(exchange);
                }
                final AtomicReference<String> requestBody = new AtomicReference<>();// 原始请求体
                // 排除流文件类型,比如上传的文件contentType.contains("multipart/form-data")
                if (Objects.nonNull(mediaType) && LogHelper.isUploadFile(mediaType)) {
                    requestBody.set("上传文件");
                    return chain.filter(exchange);
                } else {
                    if (method.equals("GET")) {
                        if (StringUtils.isNotBlank(uriQuery)) {
                            requestBody.set(uriQuery);
                        }
                    } else if (headers.getContentLength() > 0){
                        return serverRequest.bodyToMono(String.class).flatMap(reqBody -> {
                            requestBody.set(reqBody);
                            // 重写原始请求
                            ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                                @Override
                                public HttpHeaders getHeaders() {
                                    HttpHeaders httpHeaders = new HttpHeaders();
                                    httpHeaders.putAll(super.getHeaders());
                                    return httpHeaders;
                                }
    
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                                    DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
                                    return Flux.just(bodyDataBuffer);
    //                                return Flux.just(reqBody).map(bx -> exchange.getRequest().bufferFactory().wrap(bx.getBytes()));
                                }
                            };
                            ServerHttpResponseDecorator responseDecorator = getServerHttpResponseDecorator(exchange,
                                    requestBody);
                            return chain.filter(exchange.mutate()
                                    .request(requestDecorator)
                                    .response(responseDecorator)
                                    .build());
                        });
                    }
                    ServerHttpResponseDecorator decoratedResponse = getServerHttpResponseDecorator(exchange,
                            requestBody);
                    return chain.filter(exchange.mutate()
                            .response(decoratedResponse)
                            .build());
                }
    
            } catch (Exception e) {
                log.error("请求响应日志打印出现异常", e);
                return chain.filter(exchange);
            }
    
        }
    
        private ServerHttpResponseDecorator getServerHttpResponseDecorator(ServerWebExchange exchange,
                                                                           AtomicReference<String> requestBody) {
            // 获取response的返回数据
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            HttpStatus httpStatus = originalResponse.getStatusCode();
            ServerHttpRequest request = exchange.getRequest();
            URI requestUri = request.getURI();
            String uriQuery = requestUri.getQuery();
            String url = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
            HttpHeaders headers = request.getHeaders();
            String method = request.getMethodValue().toUpperCase();
            String requestId = headers.getFirst(HeaderConstant.REQUEST_ID);
    
            // 封装返回体
            return new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            DataBuffer join = bufferFactory.join(dataBuffers);
                            byte[] content = new byte[join.readableByteCount()];
                            join.read(content);
                            DataBufferUtils.release(join);
                            Charset charset = LogHelper.getMediaTypeCharset(originalResponse.getHeaders().getContentType());
                            String responseBody = new String(content, charset);
    
                            long handleTime = LogHelper.getHandleTime(headers);
                            Log logDTO = new Log(Log.TYPE.RESPONSE);
                            logDTO.setLevel(Log.LEVEL.INFO);
                            logDTO.setRequestUrl(url);
                            logDTO.setRequestBody(requestBody.get());
                            logDTO.setResponseBody(responseBody);
                            logDTO.setRequestMethod(method);
                            if (Objects.nonNull(httpStatus)) {
                                logDTO.setStatus(httpStatus.value());
                            }
                            logDTO.setHandleTime(handleTime);
                            logDTO.setRequestId(requestId);
                            logDTO.setIp(IpUtils.getClientIp(request));
                            exchange.getSession().subscribe(webSession -> {
                                logDTO.setSessionId(webSession.getId());
                            });
    
                            log.info("url:{},method:{},请求内容:{},响应内容:{},status:{},handleTime:{},requestId:{}",
                                    url, method, requestBody.get(), responseBody, httpStatus,
                                    handleTime, requestId);
                            log.info(LogHelper.toJsonString(logDTO));
                            return bufferFactory.wrap(content);
                        }));
                    }
                    return super.writeWith(body);
                }
            };
        }
    
    }
    
    

    代码已上传到git上,需要的可以去看看。

    git代码地址:https://github.com/lifengdi/spring-cloud-gateway-demo

    原文地址:https://www.lifengdi.com/archives/article/1778

  • 相关阅读:
    软件需求与建模 复习笔记
    Autoware 笔记No.9,SSD车辆、行人(障碍物)识别(ssd vision detect)
    Autoware 笔记No.8 ENet 障碍物识别(vision segment ENet detect)
    Autoware 1.14 完整安装
    Autoware 笔记No.7, CNN障碍物检测(CNN LiDAR Baidu Object Segmenter)
    iOS 使用局部block处理接口依次调用需求
    阿里一面凉经
    Codeforces round #717 D.Cut(m询问求区间[L,R]能被至少分成多少个区间让每个小区间各数的乘积==各数的LCM)
    2018-2020 国家集训队论文选读
    GDOI 2021 游记
  • 原文地址:https://www.cnblogs.com/lifengdi/p/12524092.html
Copyright © 2020-2023  润新知