【SpringCloud-Gateway】给Gateway加全局过滤器,添加请求日志

先上代码

代码结构:

在这里插入图片描述

具体代码

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可以解释为优先级,第一个对象(具有最低顺序值)具有最高优先级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值