gateway、webflux、reactor-netty请求日志输出
发布日期:2021-06-30 21:31:37 浏览次数:2 分类:技术文章

本文共 5391 字,大约阅读时间需要 17 分钟。

场景

在使用spring cloud gateway时想要输出请求日志,考虑到两种实现方案

方案一

官网中使用方案,配置“-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
  • 优点:简单方便
  • 缺点:格式固定,信息量少

方案二

创建一个logfilter,在logfilter中解析request,并输出请求信息

  • 优点:可以自定义日志格式和内容,可以获取body信息
  • 缺点:返回信息需要再写一个filter,没有匹配到路由时无法进入到logfilter中

思路

对方案一进行改造,使其满足需求。对reactor-netty源码分析,主要涉及

  • AccessLog:日志工具,日志结构体
  • AccessLogHandler:http1.1协议日志控制,我们主要使用这个。
  • AccessLogHandler2:http2协议日志控制

代码如下:

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);	}}
  • 执行顺序

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

 

解决方案

对AccessLog和AccessLogHandler进行重写,输出自己想要的内容和样式。

AccessLogHandler中重写了ChannelDuplexHandler中的channelRead和write方法,还可以对ChannelInboundHandler和ChannelOutboundHandler中的方法进行重写,覆盖请求的整个生命周期。

 

转载地址:https://lizz6.blog.csdn.net/article/details/110489737 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:reactor-netty源码:AccessLog访问日志
下一篇:修改Ribbon默认负载规则rule

发表评论

最新留言

很好
[***.229.124.182]2024年04月17日 09时07分38秒