先上代码 代码结构:
具体代码 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;@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;@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;public class BaseContextHandler { private static final ThreadLocal<Map<String, Object>> threadContext = ThreadLocal.withInitial(() -> new HashMap <>()); private BaseContextHandler () { } 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可以解释为优先级,第一个对象(具有最低顺序值)具有最高优先级。