gateway、webflux、reactor-netty请求日志输出的方式是什么
gateway、webflux、reactor-netty请求日志输出的方式是什么
本篇内容介绍了“gateway、webflux、reactor-netty请求日志输出的方式是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
gateway、webflux、reactor-netty请求日志输出
场景
在使用spring cloud gateway时想要输出请求日志,考虑到两种实现方案
方案一
官网中使用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
优点:简单方便
缺点:格式固定,信息量少
方案二
创建一个logfilter,在logfilter中解析request,并输出请求信息
优点:可以自定义日志格式和内容,可以获取body信息
缺点:返回信息需要再写一个filter,没有匹配到路由时无法进入到logfilter中
思路
对方案一进行改造,使其满足需求。对reactor-netty源码分析,主要涉及
AccessLog
:日志工具,日志结构体AccessLogHandler
:http1.1协议日志控制,我们主要使用这个。AccessLogHandler2
:http2协议日志控制
代码如下:
packagereactor.netty.http.server;importreactor.util.Logger;importreactor.util.Loggers;importjava.time.ZonedDateTime;importjava.time.format.DateTimeFormatter;importjava.util.Locale;importjava.util.Objects;finalclassAccessLog{staticfinalLoggerlog=Loggers.getLogger("reactor.netty.http.server.AccessLog");staticfinalDateTimeFormatterDATE_TIME_FORMATTER=DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ssZ",Locale.US);staticfinalStringCOMMON_LOG_FORMAT="{}-{}[{}]\"{}{}{}\"{}{}{}{}ms";staticfinalStringMISSING="-";finalStringzonedDateTime;Stringaddress;CharSequencemethod;CharSequenceuri;Stringprotocol;Stringuser=MISSING;CharSequencestatus;longcontentLength;booleanchunked;longstartTime=System.currentTimeMillis();intport;AccessLog(){this.zonedDateTime=ZonedDateTime.now().format(DATE_TIME_FORMATTER);}AccessLogaddress(Stringaddress){this.address=Objects.requireNonNull(address,"address");returnthis;}AccessLogport(intport){this.port=port;returnthis;}AccessLogmethod(CharSequencemethod){this.method=Objects.requireNonNull(method,"method");returnthis;}AccessLoguri(CharSequenceuri){this.uri=Objects.requireNonNull(uri,"uri");returnthis;}AccessLogprotocol(Stringprotocol){this.protocol=Objects.requireNonNull(protocol,"protocol");returnthis;}AccessLogstatus(CharSequencestatus){this.status=Objects.requireNonNull(status,"status");returnthis;}AccessLogcontentLength(longcontentLength){this.contentLength=contentLength;returnthis;}AccessLogincreaseContentLength(longcontentLength){if(chunked){this.contentLength+=contentLength;}returnthis;}AccessLogchunked(booleanchunked){this.chunked=chunked;returnthis;}longduration(){returnSystem.currentTimeMillis()-startTime;}voidlog(){if(log.isInfoEnabled()){log.info(COMMON_LOG_FORMAT,address,user,zonedDateTime,method,uri,protocol,status,(contentLength>-1?contentLength:MISSING),port,duration());}}}
AccessLogHandler
:日志控制
packagereactor.netty.http.server;importio.netty.buffer.ByteBuf;importio.netty.buffer.ByteBufHolder;importio.netty.channel.ChannelDuplexHandler;importio.netty.channel.ChannelHandlerContext;importio.netty.channel.ChannelPromise;importio.netty.channel.socket.SocketChannel;importio.netty.handler.codec.http.HttpRequest;importio.netty.handler.codec.http.HttpResponse;importio.netty.handler.codec.http.HttpResponseStatus;importio.netty.handler.codec.http.HttpUtil;importio.netty.handler.codec.http.LastHttpContent;/***@authorVioletaGeorgieva*/finalclassAccessLogHandlerextendsChannelDuplexHandler{AccessLogaccessLog=newAccessLog();@OverridepublicvoidchannelRead(ChannelHandlerContextctx,Objectmsg){if(msginstanceofHttpRequest){finalHttpRequestrequest=(HttpRequest)msg;finalSocketChannelchannel=(SocketChannel)ctx.channel();accessLog=newAccessLog().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")publicvoidwrite(ChannelHandlerContextctx,Objectmsg,ChannelPromisepromise){if(msginstanceofHttpResponse){finalHttpResponseresponse=(HttpResponse)msg;finalHttpResponseStatusstatus=response.status();if(status.equals(HttpResponseStatus.CONTINUE)){//"FutureReturnValueIgnored"thisisdeliberatectx.write(msg,promise);return;}finalbooleanchunked=HttpUtil.isTransferEncodingChunked(response);accessLog.status(status.codeAsText()).chunked(chunked);if(!chunked){accessLog.contentLength(HttpUtil.getContentLength(response,-1));}}if(msginstanceofLastHttpContent){accessLog.increaseContentLength(((LastHttpContent)msg).content().readableBytes());ctx.write(msg,promise.unvoid()).addListener(future->{if(future.isSuccess()){accessLog.log();}});return;}if(msginstanceofByteBuf){accessLog.increaseContentLength(((ByteBuf)msg).readableBytes());}if(msginstanceofByteBufHolder){accessLog.increaseContentLength(((ByteBufHolder)msg).content().readableBytes());}//"FutureReturnValueIgnored"thisisdeliberatectx.write(msg,promise);}}
执行顺序
AccessLogHandler.channelRead > GlobalFilter.filter > AbstractLoadBalance.choose >response.writeWith >AccessLogHandler.write
解决方案
对AccessLog和AccessLogHandler进行重写,输出自己想要的内容和样式。
AccessLogHandler中重写了ChannelDuplexHandler中的channelRead和write方法,还可以对ChannelInboundHandler和ChannelOutboundHandler中的方法进行重写,覆盖请求的整个生命周期。
spring-webflux、gateway、springboot-start-web问题
Spring-webflux
当两者一起时配置的并不是webflux web application, 仍然时一个spring mvc web application。
官方文档中有这么一段注解:
很多开发者添加spring-boot-start-webflux到他们的spring mvc web applicaiton去是为了使用reactive WebClient. 如果希望更改webApplication 类型需要显示的设置,如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>
结论一:
当两者一起时配置的并不是webflux web application, 仍然时一个spring mvc web application。但是启动不会报错,可以正常使用,但是webflux功能失效
Spring-gateway
因为gateway和zuul不一样,gateway用的是长连接,netty-webflux,zuul1.0用的就是同步webmvc。
所以你的非gateway子项目启动用的是webmvc,你的gateway启动用的是webflux. spring-boot-start-web和spring-boot-start-webflux相见分外眼红。
不能配置在同一pom.xml,或者不能在同一项目中出现,不然就会启动报错
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>
结论二:
当spring-cloud-gateway和spring-boot-starer-web两者一起时配置的时候, 启动直接报错,依赖包冲突不兼容
“gateway、webflux、reactor-netty请求日志输出的方式是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注恰卡编程网网站,小编将为大家输出更多高质量的实用文章!