溫馨提示×

溫馨提示×

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

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

gateway、webflux、reactor-netty請求日志輸出的方式是什么

發(fā)布時(shí)間:2022-03-09 16:10:47 來源:億速云 閱讀:220 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“gateway、webflux、reactor-netty請求日志輸出的方式是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

gateway、webflux、reactor-netty請求日志輸出

場景

在使用spring cloud gateway時(shí)想要輸出請求日志,考慮到兩種實(shí)現(xiàn)方案

方案一

官網(wǎng)中使用Reactor Netty Access Logs方案,配置“-Dreactor.netty.http.server.accessLogEnabled=true”開啟日志記錄。

輸出如下:

reactor.netty.http.server.AccessLog      :
10.2.20.177 - - [02/Dec/2020:16:41:57 +0800] "GET /fapi/gw/hi/login HTTP/1.1" 200 319 8080 626 ms

  • 優(yōu)點(diǎn):簡單方便

  • 缺點(diǎn):格式固定,信息量少

方案二

創(chuàng)建一個(gè)logfilter,在logfilter中解析request,并輸出請求信息

  • 優(yōu)點(diǎn):可以自定義日志格式和內(nèi)容,可以獲取body信息

  • 缺點(diǎn):返回信息需要再寫一個(gè)filter,沒有匹配到路由時(shí)無法進(jìn)入到logfilter中

思路

對(duì)方案一進(jìn)行改造,使其滿足需求。對(duì)reactor-netty源碼分析,主要涉及

  • AccessLog:日志工具,日志結(jié)構(gòu)體

  • AccessLogHandler:http1.1協(xié)議日志控制,我們主要使用這個(gè)。

  • AccessLogHandler2:http2協(xié)議日志控制

代碼如下:

package reactor.netty.http.server; 
import reactor.util.Logger;
import reactor.util.Loggers;
 
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
 
final class AccessLog {
    static final Logger log = Loggers.getLogger("reactor.netty.http.server.AccessLog");
    static final DateTimeFormatter DATE_TIME_FORMATTER =
            DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z", Locale.US);
    static final String COMMON_LOG_FORMAT =
            "{} - {} [{}] \"{} {} {}\" {} {} {} {} ms";
    static final String MISSING = "-"; 
    final String zonedDateTime;
 
    String address;
    CharSequence method;
    CharSequence uri;
    String protocol;
    String user = MISSING;
    CharSequence status;
    long contentLength;
    boolean chunked;
    long startTime = System.currentTimeMillis();
    int port;
 
    AccessLog() {
        this.zonedDateTime = ZonedDateTime.now().format(DATE_TIME_FORMATTER);
    }
 
    AccessLog address(String address) {
        this.address = Objects.requireNonNull(address, "address");
        return this;
    }
 
    AccessLog port(int port) {
        this.port = port;
        return this;
    }
 
    AccessLog method(CharSequence method) {
        this.method = Objects.requireNonNull(method, "method");
        return this;
    }
 
    AccessLog uri(CharSequence uri) {
        this.uri = Objects.requireNonNull(uri, "uri");
        return this;
    }
 
    AccessLog protocol(String protocol) {
        this.protocol = Objects.requireNonNull(protocol, "protocol");
        return this;
    }
 
    AccessLog status(CharSequence status) {
        this.status = Objects.requireNonNull(status, "status");
        return this;
    }
 
    AccessLog contentLength(long contentLength) {
        this.contentLength = contentLength;
        return this;
    }
 
    AccessLog increaseContentLength(long contentLength) {
        if (chunked) {
            this.contentLength += contentLength;
        }
        return this;
    }
 
    AccessLog chunked(boolean chunked) {
        this.chunked = chunked;
        return this;
    }
 
    long duration() {
        return System.currentTimeMillis() - startTime;
    }
 
    void log() {
        if (log.isInfoEnabled()) {
            log.info(COMMON_LOG_FORMAT, address, user, zonedDateTime,
                    method, uri, protocol, status, (contentLength > -1 ? contentLength : MISSING), port, duration());
        }
    }
}
  • AccessLogHandler:日志控制

package reactor.netty.http.server; 
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
 
/**
 * @author Violeta Georgieva
 */
final class AccessLogHandler extends ChannelDuplexHandler {
 
    AccessLog accessLog = new AccessLog();
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof HttpRequest) {
            final HttpRequest request = (HttpRequest) msg;
            final SocketChannel channel = (SocketChannel) ctx.channel();
 
            accessLog = new AccessLog()
                    .address(channel.remoteAddress().getHostString())
                    .port(channel.localAddress().getPort())
                    .method(request.method().name())
                    .uri(request.uri())
                    .protocol(request.protocolVersion().text());
        }
        ctx.fireChannelRead(msg);
    }
 
    @Override
    @SuppressWarnings("FutureReturnValueIgnored")
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        if (msg instanceof HttpResponse) {
            final HttpResponse response = (HttpResponse) msg;
            final HttpResponseStatus status = response.status();
 
            if (status.equals(HttpResponseStatus.CONTINUE)) {
                //"FutureReturnValueIgnored" this is deliberate
                ctx.write(msg, promise);
                return;
            }
 
            final boolean chunked = HttpUtil.isTransferEncodingChunked(response);
            accessLog.status(status.codeAsText())
                     .chunked(chunked);
            if (!chunked) {
                accessLog.contentLength(HttpUtil.getContentLength(response, -1));
            }
        }
        if (msg instanceof LastHttpContent) {
            accessLog.increaseContentLength(((LastHttpContent) msg).content().readableBytes());
            ctx.write(msg, promise.unvoid())
               .addListener(future -> {
                   if (future.isSuccess()) {
                       accessLog.log();
                   }
               });
            return;
        }
        if (msg instanceof ByteBuf) {
            accessLog.increaseContentLength(((ByteBuf) msg).readableBytes());
        }
        if (msg instanceof ByteBufHolder) {
            accessLog.increaseContentLength(((ByteBufHolder) msg).content().readableBytes());
        }
        //"FutureReturnValueIgnored" this is deliberate
        ctx.write(msg, promise);
    }
}

執(zhí)行順序

AccessLogHandler.channelRead > GlobalFilter.filter > AbstractLoadBalance.choose >response.writeWith >AccessLogHandler.write

解決方案

對(duì)AccessLog和AccessLogHandler進(jìn)行重寫,輸出自己想要的內(nèi)容和樣式。

AccessLogHandler中重寫了ChannelDuplexHandler中的channelRead和write方法,還可以對(duì)ChannelInboundHandler和ChannelOutboundHandler中的方法進(jìn)行重寫,覆蓋請求的整個(gè)生命周期。

spring-webflux、gateway、springboot-start-web問題

Spring-webflux

當(dāng)兩者一起時(shí)配置的并不是webflux web application, 仍然時(shí)一個(gè)spring mvc web application。

官方文檔中有這么一段注解:

很多開發(fā)者添加spring-boot-start-webflux到他們的spring mvc web applicaiton去是為了使用reactive WebClient. 如果希望更改webApplication 類型需要顯示的設(shè)置,如SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE).

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

結(jié)論一:

當(dāng)兩者一起時(shí)配置的并不是webflux web application, 仍然時(shí)一個(gè)spring mvc web application。但是啟動(dòng)不會(huì)報(bào)錯(cuò),可以正常使用,但是webflux功能失效

gateway、webflux、reactor-netty請求日志輸出的方式是什么

Spring-gateway

因?yàn)間ateway和zuul不一樣,gateway用的是長連接,netty-webflux,zuul1.0用的就是同步webmvc。

所以你的非gateway子項(xiàng)目啟動(dòng)用的是webmvc,你的gateway啟動(dòng)用的是webflux. spring-boot-start-web和spring-boot-start-webflux相見分外眼紅。

不能配置在同一pom.xml,或者不能在同一項(xiàng)目中出現(xiàn),不然就會(huì)啟動(dòng)報(bào)錯(cuò)

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

結(jié)論二:

當(dāng)spring-cloud-gateway和spring-boot-starer-web兩者一起時(shí)配置的時(shí)候, 啟動(dòng)直接報(bào)錯(cuò),依賴包沖突不兼容

“gateway、webflux、reactor-netty請求日志輸出的方式是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

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

AI