SpringCloud Gateway --- 获取请求体数据并记录日志

SpringCloud Gateway 中想要获取请求体数据,这里介绍一种优雅的处理方法,就是使用 框架自带的 ModifyRequestBodyGatewayFilterFactory

使用

新建类 RequestLogFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Slf4j
@Component
@AllArgsConstructor
public class RequestLogFilter implements GlobalFilter, Ordered {

private final ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory;

private final AsyncRequestHandler asyncRequestHandle;

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 不解析 body
if (Utils.isUploadRequest(exchange)) {
process(exchange, null);
return chain.filter(exchange);
}
ModifyRequestBodyGatewayFilterFactory.Config modifyRequestConfig =
new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, (e, bytes) -> {
process(e, bytes);
return Mono.justOrEmpty(bytes);
});
return modifyRequestBodyGatewayFilterFactory.apply(modifyRequestConfig).filter(exchange, chain);
}

@Override
public int getOrder() {
return -100;
}

private void process(ServerWebExchange exchange, byte[] bytes) {
// 设置当前请求时间
exchange.getAttributes().put(Constant.REQUEST_START_TIME_ATTR, System.currentTimeMillis());
exchange.getAttributes().put(Constant.ACCESS_LOG_REQUEST_FUTURE_ATTR,
asyncRequestHandle.handle(exchange, bytes));
}
}
1
2
3
4
5
6
7
8
9
  /**
* 判断是否文件上传请求
*
* @param exchange ServerWebExchange
* @return boolean
*/
public static boolean isUploadRequest(ServerWebExchange exchange){
return MediaType.MULTIPART_FORM_DATA.isCompatibleWith(requestContentType(exchange));
}

新建 AsyncRequestHandler 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@Component
@Slf4j
@AllArgsConstructor
public class AsyncRequestHandler {
@Async("logTaskPool")
public Future<AccessLog> handle(ServerWebExchange exchange, byte[] bytes) {
return new AsyncResult<>(wrapperAccessLog(exchange, bytes));
}

/**
* 保证访问日志请求体
*
* @param exchange ServerWebExchange
* @param bytes data to send
* @return log
*/
private AccessLog wrapperAccessLog(ServerWebExchange exchange, byte[] bytes) {
ServerHttpRequest request = exchange.getRequest();
AccessLog accessLog = new AccessLog();
accessLog.setToken(Utils.getToken(exchange));
accessLog.setTime(LocalDateTime.now());
accessLog.setApplication(getApplicationName(exchange));
accessLog.setIp(Utils.getIp(exchange));
accessLog.setUri(request.getURI().toString());
accessLog.setHttpMethod(HttpMethod.valueOf(request.getMethodValue()));
// 临时设置当前时间,后面替换成真正耗时
accessLog.setTakenTime((long) exchange.getAttributes().get(Constant.REQUEST_START_TIME_ATTR));
accessLog.setRequestHeaders(request.getHeaders().toSingleValueMap().toString());
if(Utils.isUploadRequest(exchange)) {
accessLog.setRequest("二进制文件");
}
Optional.ofNullable(bytes).ifPresent(bs -> {
if (bytes.length <= DataSize.ofKilobytes(256).toBytes()) {
// 小于指定大小的报文进行转化(考虑到文件上传的请求报文)
accessLog.setRequest(new String(bytes, StandardCharsets.UTF_8));
} else {
accessLog.setRequest("报文过长");
}
});
return accessLog;
}

/**
* 获取服务名称
*
* @param exchange ServerWebExchange
* @return name of the application
*/
private String getApplicationName(ServerWebExchange exchange) {
String routingId =
(String) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);
// 自动注册的微服务
if (routingId.startsWith(Constant.MODULE_SUB_PREFIX)) {
return routingId.substring(Constant.MODULE_SUB_PREFIX.length());
} else {
return routingId;
}
}

}

AccessLog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
public class AccessLog extends AbstractLog {

/**
* 所属服务
*/
private String application;

/**
* ip
*/
private String ip;

/**
* uri
*/
private String uri;

/**
* 请求方法
*/
private HttpMethod httpMethod;

/**
* 消耗时间,单位毫秒
*/
private Long takenTime;

/**
* http 状态码
*/
private Integer httpCode;

/**
* 请求报文
*/
private String request;

/**
* 响应报文
*/
private String response;

/**
* 请求头信息
*/
private String requestHeaders;

/**
* 响应头信息
*/
private String responseHeaders;


public String getApplication() {
return application;
}

public void setApplication(String application) {
this.application = application;
}

public String getIp() {
return ip;
}

public void setIp(String ip) {
this.ip = ip;
}

public String getUri() {
return uri;
}

public void setUri(String uri) {
this.uri = uri;
}

public HttpMethod getHttpMethod() {
return httpMethod;
}

public void setHttpMethod(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
}

public Long getTakenTime() {
return takenTime;
}

public void setTakenTime(Long takenTime) {
this.takenTime = takenTime;
}

public Integer getHttpCode() {
return httpCode;
}

public void setHttpCode(Integer httpCode) {
this.httpCode = httpCode;
}

public String getRequest() {
return request;
}

public void setRequest(String request) {
this.request = request;
}

public String getResponse() {
return response;
}

public void setResponse(String response) {
this.response = response;
}

public String getRequestHeaders() {
return requestHeaders;
}

public void setRequestHeaders(String requestHeaders) {
this.requestHeaders = requestHeaders;
}

public String getResponseHeaders() {
return responseHeaders;
}

public void setResponseHeaders(String responseHeaders) {
this.responseHeaders = responseHeaders;
}
}

AbstractLog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public abstract class AbstractLog {

/**
* 日志 id
*/
private String id;

/**
* 访问 token
*/
private String token;

/**
* 用户 id
*/
private String userid;

/**
* 用户名(用户账号)
*/
private String username;

/**
* 用户姓名
*/
private String name;

/**
* 操作时间
*/
private LocalDateTime time;

/**
* 操作来源
*/
private OriginType originType;


public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}

public String getUserid() {
return userid;
}

public void setUserid(String userid) {
this.userid = userid;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public LocalDateTime getTime() {
return time;
}

public void setTime(LocalDateTime time) {
this.time = time;
}

public OriginType getOriginType() {
return originType;
}

public void setOriginType(OriginType originType) {
this.originType = originType;
}
}

注意:
这里对请求的数据进行了拦截并包装成日志对象存储在 exchange 的一个 ACCESS_LOG_REQUEST_FUTURE_ATTR 属性中,后面再拿到响应时,在从 exchange 拿回请求数据和响应数据一并使用