先上代码
代码结构:
具体代码
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;
}
}
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;
}
}
这个类是为了获取运行时间,也可以使用其他方式获取运行时间。
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可以解释为优先级,第一个对象(具有最低顺序值)具有最高优先级。