聊聊reactor-netty的AccessLog

go4it發表於2019-04-04

本文主要研究一下reactor-netty的AccessLog

開啟access log

  • 對於使用tomcat的spring boot應用,可以server.tomcat.accesslog.enabled=true來開啟
  • 對於使用jetty的spring boot應用,可以server.jetty.accesslog.enabled=true來開啟
  • 對於使用undertow的spring boot應用,可以server.undertow.accesslog.enabled=true來開啟

對於使用webflux的應用,沒有這麼對應的配置,但是可以通過-Dreactor.netty.http.server.accessLogEnabled=true來開啟

ReactorNetty

reactor-netty-0.8.5.RELEASE-sources.jar!/reactor/netty/ReactorNetty.java

/**
 * Internal helpers for reactor-netty contracts
 *
 * @author Stephane Maldini
 */
public final class ReactorNetty {
	//......

	// System properties names

	/**
	 * Default worker thread count, fallback to available processor
	 * (but with a minimum value of 4)
	 */
	public static final String IO_WORKER_COUNT = "reactor.netty.ioWorkerCount";
	/**
	 * Default selector thread count, fallback to -1 (no selector thread)
	 */
	public static final String IO_SELECT_COUNT = "reactor.netty.ioSelectCount";
	/**
	 * Default worker thread count for UDP, fallback to available processor
	 * (but with a minimum value of 4)
	 */
	public static final String UDP_IO_THREAD_COUNT = "reactor.netty.udp.ioThreadCount";


	/**
	 * Default value whether the native transport (epoll, kqueue) will be preferred,
	 * fallback it will be preferred when available
	 */
	public static final String NATIVE = "reactor.netty.native";


	/**
	 * Default max connections, if -1 will never wait to acquire before opening a new
	 * connection in an unbounded fashion. Fallback to
	 * available number of processors (but with a minimum value of 16)
	 */
	public static final String POOL_MAX_CONNECTIONS = "reactor.netty.pool.maxConnections";
	/**
	 * Default acquisition timeout (milliseconds) before error. If -1 will never wait to
	 * acquire before opening a new
	 * connection in an unbounded fashion. Fallback 45 seconds
	 */
	public static final String POOL_ACQUIRE_TIMEOUT = "reactor.netty.pool.acquireTimeout";


	/**
	 * Default SSL handshake timeout (milliseconds), fallback to 10 seconds
	 */
	public static final String SSL_HANDSHAKE_TIMEOUT = "reactor.netty.tcp.sslHandshakeTimeout";
	/**
	 * Default value whether the SSL debugging on the client side will be enabled/disabled,
	 * fallback to SSL debugging disabled
	 */
	public static final String SSL_CLIENT_DEBUG = "reactor.netty.tcp.ssl.client.debug";
	/**
	 * Default value whether the SSL debugging on the server side will be enabled/disabled,
	 * fallback to SSL debugging disabled
	 */
	public static final String SSL_SERVER_DEBUG = "reactor.netty.tcp.ssl.server.debug";


	/**
	 * Specifies whether the Http Server access log will be enabled.
	 * By default it is disabled.
	 */
	public static final String ACCESS_LOG_ENABLED = "reactor.netty.http.server.accessLogEnabled";

	//......
}
複製程式碼
  • ReactorNetty定義了ACCESS_LOG_ENABLED常量,其值為reactor.netty.http.server.accessLogEnabled

HttpServerBind

reactor-netty-0.8.5.RELEASE-sources.jar!/reactor/netty/http/server/HttpServerBind.java

final class HttpServerBind extends HttpServer
		implements Function<ServerBootstrap, ServerBootstrap> {

	static final HttpServerBind INSTANCE = new HttpServerBind();

	static final Function<DisposableServer, DisposableServer> CLEANUP_GLOBAL_RESOURCE = DisposableBind::new;

	static final boolean ACCESS_LOG =
			Boolean.parseBoolean(System.getProperty(ACCESS_LOG_ENABLED, "false"));

	//......

	static final class Http1Initializer
			implements BiConsumer<ConnectionObserver, Channel>  {

		final int                                                line;
		final int                                                header;
		final int                                                chunk;
		final boolean                                            validate;
		final int                                                buffer;
		final int                                                minCompressionSize;
		final BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate;
		final boolean                                            forwarded;
		final ServerCookieEncoder                                cookieEncoder;
		final ServerCookieDecoder                                cookieDecoder;

		Http1Initializer(int line,
				int header,
				int chunk,
				boolean validate,
				int buffer,
				int minCompressionSize,
				@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate,
				boolean forwarded,
				ServerCookieEncoder encoder,
				ServerCookieDecoder decoder) {
			this.line = line;
			this.header = header;
			this.chunk = chunk;
			this.validate = validate;
			this.buffer = buffer;
			this.minCompressionSize = minCompressionSize;
			this.compressPredicate = compressPredicate;
			this.forwarded = forwarded;
			this.cookieEncoder = encoder;
			this.cookieDecoder = decoder;
		}

		@Override
		public void accept(ConnectionObserver listener, Channel channel) {
			ChannelPipeline p = channel.pipeline();

			p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec(line, header, chunk, validate, buffer));

			if (ACCESS_LOG) {
				p.addLast(NettyPipeline.AccessLogHandler, new AccessLogHandler());
			}

			boolean alwaysCompress = compressPredicate == null && minCompressionSize == 0;

			if (alwaysCompress) {
				p.addLast(NettyPipeline.CompressionHandler,
						new SimpleCompressionHandler());
			}

			p.addLast(NettyPipeline.HttpTrafficHandler,
					new HttpTrafficHandler(listener, forwarded, compressPredicate, cookieEncoder, cookieDecoder));
		}
	}

	//......
}			
複製程式碼
  • HttpServerBind有個ACCESS_LOG屬性,它讀取ReactorNetty的ACCESS_LOG_ENABLED(reactor.netty.http.server.accessLogEnabled)的屬性,讀取不到預設為false;HttpServerBind有個Http1Initializer類,它的accept方法會判斷ACCESS_LOG是否為true,如果為true則會往Channel的pipeline新增名為accessLogHandler(NettyPipeline.AccessLogHandler)的AccessLogHandler

AccessLogHandler

reactor-netty-0.8.5.RELEASE-sources.jar!/reactor/netty/http/server/AccessLogHandler.java

final class AccessLogHandler extends ChannelDuplexHandler {

	AccessLog accessLog = new AccessLog();

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		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());
		}
		super.channelRead(ctx, msg);
	}

	@Override
	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)) {
				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)
			   .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());
		}
		ctx.write(msg, promise);
	}
}
複製程式碼
  • AccessLogHandler繼承了ChannelDuplexHandler;在channelRead的時候建立了AccessLog物件,在write的時候更新AccessLog物件;當msg為LastHttpContent時,則新增了一個listener,在成功回撥時執行accessLog.log()

AccessLog

reactor-netty-0.8.5.RELEASE-sources.jar!/reactor/netty/http/server/AccessLog.java

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());
		}
	}
}
複製程式碼
  • AccessLog的log方法直接通過logger輸出日誌,其日誌格式為COMMON_LOG_FORMAT({} - {} [{}] "{} {} {}" {} {} {} {} ms),分別是address, user, zonedDateTime, method, uri, protocol, status, contentLength, port, duration

小結

  • 對於使用webflux的應用,可以通過-Dreactor.netty.http.server.accessLogEnabled=true來開啟access log
  • HttpServerBind有個ACCESS_LOG屬性,它讀取ReactorNetty的ACCESS_LOG_ENABLED(reactor.netty.http.server.accessLogEnabled)的屬性,讀取不到預設為false;HttpServerBind有個Http1Initializer類,它的accept方法會判斷ACCESS_LOG是否為true,如果為true則會往Channel的pipeline新增名為accessLogHandler(NettyPipeline.AccessLogHandler)的AccessLogHandler
  • AccessLogHandler繼承了ChannelDuplexHandler;在channelRead的時候建立了AccessLog物件,在write的時候更新AccessLog物件;當msg為LastHttpContent時,則新增了一個listener,在成功回撥時執行accessLog.log();AccessLog的log方法直接通過logger輸出日誌,其日誌格式為COMMON_LOG_FORMAT({} - {} [{}] "{} {} {}" {} {} {} {} ms),分別是address, user, zonedDateTime, method, uri, protocol, status, contentLength, port, duration

doc

相關文章