gateway、webflux、reactor-netty请求日志输出方式
作者:lizz666 发布时间:2022-11-14 23:39:43
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协议日志控制
代码如下:
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中的方法进行重写,覆盖请求的整个生命周期。
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两者一起时配置的时候, 启动直接报错,依赖包冲突不兼容
来源:https://lizz6.blog.csdn.net/article/details/110489737


猜你喜欢
- 读取文件流时,经常会遇到乱码的现象,造成乱码的原因当然不可能是一个,这里主要介绍因为文件编码格式而导致的乱码的问题。首先,明确一点,文本文件
- ***Source URL: http://i.yesky.com/bbs/jsp/view.jsp?articleID=889992&am
- GradientTextViewGithub点我一个非常好用的库,使用kotlin实现,用于设置TexView的字体 渐变颜色、渐变方向 和
- 一、前言在SpringBoot工程(注意不是SpringCloud)下使OpenFeign的大坑。为什么不用SpringCloud中的Fei
- 本文实例为大家分享了Unity实现仿3D轮转图效果的具体代码,供大家参考,具体内容如下一、效果演示二、实现思路—&
- 本文为大家分享了NancyFx框架检测任务管理器的具体方法,供大家参考,具体内容如下先建一个空的项目和之前的NancyFx系列一样的步骤然后
- 本文实例为大家分享了Java Socket聊天室功能的具体代码,供大家参考,具体内容如下Client.javaimport java.io.
- 不知道大家对千篇一律的404 Not Found的错误页面是否感到腻歪了?其实通过很简单的配置就能够让Spring MVC显示您自定义的40
- Apache Commons包含了很多开源的工具,用于解决平时编程经常会遇到的问题,减少重复劳动。下面是我这几年做开发过程中自己用过的工具类
- 实现如下边框效果:虚线画效果,可以使用Android中的xml来做。下面话不多说,直接上代码:<RelativeLayout &nbs
- 在1.zip中增加一张新图片StorageFile jpg = await KnownFolders.PicturesLibrary.Get
- 1 背景去年有很多人私信告诉我让说说自定义控件,其实通观网络上的很多博客都在讲各种自定义控件,但是大多数都是授之以鱼,却很少有较
- SpringMVC一般使用MultipartFile来做文件的上传,通过MultipartFile的getContentType()方法判定
- 基于servlet+jsp+jdbc的后台管理系统,包含5个模块:汽车账户部管理、租车账户部管理、汽车信息管理表、租车记录表、租车租聘表。功
- 本文实例为大家分享了C#实现俄罗斯方块的具体代码,供大家参考,具体内容如下1.调色板代码namespace Tetris{ class Pa
- 比如,现在有一些图形,需要计算他们的面积,计算面积的方法都不一样,可以这么做声明一个抽象类//基类 abstrac
- 百度是个好东西,这篇调用了百度的接口(当然大牛也可以自己写),人脸检测技术,所以使用的前提是有网的情况下。当然大家也可以去参考百度的文档。话
- 一、系统自动抛出异常当程序语句出现一些逻辑错误、主义错误或者类型转换错误时,系统会自动抛出异常例一public static void ma
- 如下所示:JSONArray jsonArray1 = jsonObject.getJSONArray("result"
- private void btnSetOk_Click(object sender, EventArgs e) &nb