自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringCloud 微服務中網(wǎng)關如何記錄請求響應日志?

網(wǎng)絡 網(wǎng)絡管理
getOrder()方法返回的值必須要<-1,否則標準的NettyWriteResponseFilter將在您的過濾器被調(diào)用的機會之前發(fā)送響應,即不會執(zhí)行獲取后端響應參數(shù)的方法。

大家好,我是飄渺。

在基于SpringCloud開發(fā)的微服務中,我們一般會選擇在網(wǎng)關層記錄請求和響應日志,并將其收集到ELK中用作查詢和分析。

今天我們就來看看如何實現(xiàn)此功能。

日志實體類

首先我們在網(wǎng)關中定義一個日志實體,用于組裝日志對象;

@Data
public class AccessLog {

    /**用戶編號**/
    private Long userId;

    /**路由**/
    private String targetServer;

    /**協(xié)議**/
    private String schema;
    
    /**請求方法名**/
    private String requestMethod;
    
    /**訪問地址**/
    private String requestUrl;

    /**請求IP**/
    private String clientIp;

    /**查詢參數(shù)**/
    private MultiValueMap<String, String> queryParams;
    
    /**請求體**/
    private String requestBody;
    
    /**請求頭**/
    private MultiValueMap<String, String> requestHeaders;

     /**響應體**/
    private String responseBody;
    
    /**響應頭**/
    private MultiValueMap<String, String> responseHeaders;
    
     /**響應結果**/
    private HttpStatusCode httpStatusCode;
    
     /**開始請求時間**/
    private LocalDateTime startTime;
    
    /**結束請求時間**/
    private LocalDateTime endTime;
    
    /**執(zhí)行時長,單位:毫秒**/
    private Integer duration;

}

網(wǎng)關日志過濾器

接下來我們在網(wǎng)關中定義一個Filter,用于收集日志信息。

@Component
public class AccessLogFilter implements GlobalFilter, Ordered {

    private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    /**
     * 打印日志
     * @param accessLog 網(wǎng)關日志
     */
    private void writeAccessLog(AccessLog accessLog) {
        log.info("----access---- : {}", JsonUtils.obj2StringPretty(accessLog));
    }

    /**
     * 順序必須是<-1,否則標準的NettyWriteResponseFilter將在您的過濾器得到一個被調(diào)用的機會之前發(fā)送響應
     * 也就是說如果不小于 -1 ,將不會執(zhí)行獲取后端響應的邏輯
     * @return
     */
    @Override
    public int getOrder() {
        return -100;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 將 Request 中可以直接獲取到的參數(shù),設置到網(wǎng)關日志
        ServerHttpRequest request = exchange.getRequest();

        AccessLog gatewayLog = new AccessLog();
        gatewayLog.setTargetServer(WebUtils.getGatewayRoute(exchange).getId());
        gatewayLog.setSchema(request.getURI().getScheme());
        gatewayLog.setRequestMethod(request.getMethod().name());
        gatewayLog.setRequestUrl(request.getURI().getRawPath());
        gatewayLog.setQueryParams(request.getQueryParams());
        gatewayLog.setRequestHeaders(request.getHeaders());
        gatewayLog.setStartTime(LocalDateTime.now());
        gatewayLog.setClientIp(WebUtils.getClientIP(exchange));

        // 繼續(xù) filter 過濾
        MediaType mediaType = request.getHeaders().getContentType();
        if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)
                || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) { // 適合 JSON 和 Form 提交的請求
            return filterWithRequestBody(exchange, chain, gatewayLog);
        }
        return filterWithoutRequestBody(exchange, chain, gatewayLog);
    }


    /**
     * 沒有請求體的請求只需要記錄日志
     */
    private Mono<Void> filterWithoutRequestBody(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog accessLog) {
        // 包裝 Response,用于記錄 Response Body
        ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);

        return chain.filter(exchange.mutate().response(decoratedResponse).build())
                .then(Mono.fromRunnable(() -> writeAccessLog(accessLog)));
    }

    /**
     * 需要讀取請求體
     * 參考 {@link ModifyRequestBodyGatewayFilterFactory} 實現(xiàn)
     */
    private Mono<Void> filterWithRequestBody(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog gatewayLog) {
        // 設置 Request Body 讀取時,設置到網(wǎng)關日志
        ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);

        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            gatewayLog.setRequestBody(body);
            return Mono.just(body);
        });

        // 通過 BodyInserter 插入 body(支持修改body), 避免 request body 只能獲取一次
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        // the new content type will be computed by bodyInserter
        // and then set in the request decorator
        headers.remove(HttpHeaders.CONTENT_LENGTH);

        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

        // 通過 BodyInserter 將 Request Body 寫入到 CachedBodyOutputMessage 中
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            // 重新封裝請求
            ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);
            // 記錄響應日志
            ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);
            // 記錄普通的
            return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())
                    .then(Mono.fromRunnable(() -> writeAccessLog(gatewayLog))); // 打印日志

        }));
    }

    /**
     * 記錄響應日志
     * 通過 DataBufferFactory 解決響應體分段傳輸問題。
     */
    private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, AccessLog accessLog) {
        ServerHttpResponse response = exchange.getResponse();

        return new ServerHttpResponseDecorator(response) {

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    // 計算執(zhí)行時間
                    accessLog.setEndTime(LocalDateTime.now());
                    accessLog.setDuration((int) (LocalDateTimeUtil.between(accessLog.getStartTime(),
                            accessLog.getEndTime()).toMillis()));
                    accessLog.setResponseHeaders(response.getHeaders());
                    accessLog.setHttpStatusCode(response.getStatusCode());

                    // 獲取響應類型,如果是 json 就打印
                    String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);

                    if (StrUtil.isNotBlank(originalResponseContentType)
                            && originalResponseContentType.contains("application/json")) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            // 設置 response body 到網(wǎng)關日志
                            byte[] content = readContent(dataBuffers);
                            String responseResult = new String(content, StandardCharsets.UTF_8);
                            accessLog.setResponseBody(responseResult);

                            // 響應
                            return bufferFactory.wrap(content);
                        }));
                    }
                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
    }


    /**
     * 請求裝飾器,支持重新計算 headers、body 緩存
     *
     * @param exchange 請求
     * @param headers 請求頭
     * @param outputMessage body 緩存
     * @return 請求裝飾器
     */
    private ServerHttpRequestDecorator requestDecorate(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 {
                    // TODO: this causes a 'HTTP/1.1 411 Length Required' // on
                    // httpbin.org
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

    /**
     * 從dataBuffers中讀取數(shù)據(jù)
     * @author jam
     * @date 2024/5/26 22:31
     */
    private byte[] readContent(List<? extends DataBuffer> dataBuffers) {
        // 合并多個流集合,解決返回體分段傳輸
        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
        DataBuffer join = dataBufferFactory.join(dataBuffers);
        byte[] content = new byte[join.readableByteCount()];
        join.read(content);
        // 釋放掉內(nèi)存
        DataBufferUtils.release(join);
        return content;
    }

}

代碼較長建議直接拷貝到編輯器,只要注意下面一個關鍵點:

getOrder()方法返回的值必須要<-1,否則標準的NettyWriteResponseFilter將在您的過濾器被調(diào)用的機會之前發(fā)送響應,即不會執(zhí)行獲取后端響應參數(shù)的方法。

通過上面的兩步我們已經(jīng)可以獲取到請求的輸入輸出參數(shù)了,在 writeAccessLog()中將其打印到日志文件,方便通過ELK進行收集。

在實際項目中,網(wǎng)關日志量一般會非常大,不建議使用數(shù)據(jù)庫進行存儲。

實際效果

服務正常響應:

圖片

服務異常響應:

圖片圖片

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

2021-03-26 06:01:45

日志MongoDB存儲

2021-03-09 09:33:42

網(wǎng)關授權微服務

2021-08-11 05:00:48

Spring 日志手段

2021-08-13 07:52:35

微服務網(wǎng)關數(shù)據(jù)

2021-05-14 09:15:32

SpringCloud微服務日志

2022-05-12 08:21:13

項目網(wǎng)關模塊

2021-06-09 09:42:50

SpringCloud微服務灰度發(fā)布

2024-03-18 08:48:52

Spring多端認證微服務

2022-04-14 08:51:49

微服務Redisson分布式鎖

2011-08-01 13:57:20

iPhone 網(wǎng)絡

2024-03-06 08:36:36

2019-09-24 08:44:09

OpenrestyAPI網(wǎng)關

2022-05-16 08:22:11

網(wǎng)關過濾器路由

2017-03-09 19:39:54

微服務架構重構

2022-09-01 08:17:15

Gateway微服務網(wǎng)關

2023-06-09 14:46:36

2021-01-25 15:00:44

微服務分布式日志

2024-08-05 10:03:53

2024-10-29 08:44:18

2020-11-18 11:26:45

SpringCloudZuulJava
點贊
收藏

51CTO技術棧公眾號