先上代码

代码结构:

具体代码

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
package com.litianyi.supermall.gateway.filters;

import com.litianyi.supermall.gateway.context.BaseContextHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.util.stream.Collectors;

/**
* @author litianyi
* @version 1.0
* @date 2022/1/7 12:19 PM
*/
@Component
@Slf4j
public class GlobalStartLoggerFilter implements GlobalFilter, Ordered {

private void startLogInfo(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();

String url = request.getURI().toString();
String params = request.getQueryParams().toString();
String method = request.getMethodValue();
String headers = request.getHeaders().entrySet().stream()
.map(entry -> entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
.collect(Collectors.joining("\n\t"));

StringBuilder builder = new StringBuilder();
builder.append("****************************************请求路径:[{}]请求开始****************************************\n");
builder.append("url: " + url + "\n");
builder.append("gateway_request_url: " + exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR) + "\n");
builder.append("method: " + method + "\n");
builder.append("params: " + params + "\n");
builder.append("headers: \n{ \n" + headers + " \n}\n");
log.info(String.valueOf(builder), url);
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

//前置过滤
BaseContextHandler.getContextMap().put("beginTime", Instant.now());
this.startLogInfo(exchange);

return chain.filter(exchange);
}

@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}

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
package com.litianyi.supermall.gateway.filters;

import com.litianyi.supermall.gateway.context.BaseContextHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.Instant;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

/**
* @author litianyi
* @version 1.0
* @date 2022/1/7 12:19 PM
*/
@Component
@Slf4j
public class GlobalEndLoggerFilter implements GlobalFilter, Ordered {

private void endLogInfo(ServerWebExchange exchange, long time) {
ServerHttpRequest request = exchange.getRequest();
String url = request.getURI().toString();
String attribute = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR).toString();

StringBuilder builder = new StringBuilder();
builder.append("****************************************请求路径:[{}]请求结束, 耗时:[{}]ms****************************************\n");
builder.append("gateway_request_url: " + attribute + "\n");
log.info(String.valueOf(builder), url, time);
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

//后置过滤
Instant instant = (Instant) BaseContextHandler.getContextMap().get("beginTime");
endLogInfo(exchange, Duration.between(instant, Instant.now()).toMillis());
BaseContextHandler.remove();

return chain.filter(exchange);
}

@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
}

这个类是为了获取运行时间,也可以使用其他方式获取运行时间。

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
package com.litianyi.supermall.gateway.context;

import java.util.HashMap;
import java.util.Map;

/**
* @author litianyi
*/
public class BaseContextHandler {

private static final ThreadLocal<Map<String, Object>> threadContext = ThreadLocal.withInitial(() -> new HashMap<>());

private BaseContextHandler() {

}

/**
* 取得thread context Map的实例。
*
* @return thread context Map的实例
*/
public static Map<String, Object> getContextMap() {
return threadContext.get();
}

public static void remove() {
getContextMap().clear();
}

}

运行效果

解决方法

在使用 Gateway 的时候,不确定自己写的 predicates 和 filters 是否正确,想要知道经过 Gateway 过滤器后真正的请求是什么,有两种方法,一种是debug查看真实的url,另一种就是使用日志,把转发后的 url 打印出来。

阅读源码:

对 RouteToRequestUrlFilter 进行 debug

从 GatewayAutoConfiguration 可以看到他运行了 RouteToRequestUrlFilter 类,这个类就是 Gateway 确定路由到哪个服务地址的类。

进入这个类,进行debug

lb:load balancer 负载均衡

可以看到,RouteToRequestUrlFilter 把确定了服务的 url 使用 GATEWAY_REQUEST_URL_ATTR 这个 key 保存到了 ServerWebExchange 对象的 Attributes 里。

接下来进入负载均衡过滤器

对 LoadBalancerClientFilter 进行debug

经过负载均衡过滤器确定了真正要请求的url,然后又使用 GATEWAY_REQUEST_URL_ATTR 这个 key 存到了 ServerWebExchange 对象的 Attributes 里。

经过这一波操作,写日志的思路就有了。

写日志:

Gateway 的过滤器有两种,一种是全局过滤器,另一种是针对于 Router 的过滤器(Gateway提供了丰富的 router 过滤器,本文不涉及自定义这种过滤器)。
全局过滤器不需要工厂,也不需要配置,直接对所有的路由都生效。可以使用全局过滤器实现统一的权限验证、日志记录等希望对所有代理的项目都生效的内容,可以配置在全局过滤器中。
且在项目中可以配置多个全局过滤器的实现类,都可以自动执行。

自定义的全局过滤器需要实现 GlobalFilter 和 Ordered。

主要的日志逻辑可以写在需要实现的 GlobalFilter 下的 public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) 方法内。

ServerWebExchange 类的解释: Contract for an HTTP request-response interaction. Provides access to the HTTP request and response and also exposes additional server-side processing related properties and features such as request attributes.
HTTP 请求-响应交互的契约。 提供对 HTTP 请求和响应的访问,还公开其他与服务器端处理相关的属性和功能,例如请求属性。

Ordered 需要实现 public int getOrder() 方法。改方法返回一个int,表示这个filter的执行顺序。

Ordered 类的解释: Ordered is an interface that can be implemented by objects that should be orderable, for example in a Collection.
The actual order can be interpreted as prioritization, with the first object (with the lowest order value) having the highest priority.
实际order可以解释为优先级,第一个对象(具有最低顺序值)具有最高优先级。