首页 > 其他分享 >Spring Cloud Gateway 设置全局接口访问日志

Spring Cloud Gateway 设置全局接口访问日志

时间:2023-07-09 23:32:11浏览次数:38  
标签:return String exchange Spring request append sb Gateway Cloud

Spring Cloud Gateway 设置全局接口访问日志

虽然网关只做转发,但是对于每个转发的请求,我们都希望能够在日志中打印出请求的信息,网上版本很多,踩了很多坑,目前没找到完美的解决方案,最后我这个应该是大成版。希望对大家有用。

先贴代码,再说遇到什么坑吧。

/**
 * @author chenzhangx
 * @date 2021/11/30 15:09
 */
@Component
public class AccessFilter extends AbstractFilter implements GlobalFilter, Ordered {

    private static Logger logger = LoggerFactory.getLogger(AccessFilter.class.getSimpleName());

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

        return cacheRequestBody(exchange, (serverHttpRequest) -> {
            // don't mutate and build if same request object
            if (serverHttpRequest == exchange.getRequest()) {
                return chain.filter(exchange);
            }
            return chain.filter(exchange.mutate().request(serverHttpRequest).build());
        });
    }

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

    //---------------------------------------------- private ---------------------------------------------------------

    private void logInfo(ServerHttpRequest request, String body) {

        String uri = request.getPath().value();
        String params = request.getQueryParams().toString();
        String method = request.getMethodValue();
        String ip = request.getRemoteAddress().toString();
        String headers = request.getHeaders().entrySet()
                .stream()
                .map(entry -> entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                .collect(Collectors.joining("\n"));
        long accessDate = System.currentTimeMillis();

        StringBuilder sb = new StringBuilder();
        sb.append("\n==================================[API_CALL]==================================\n");
        sb.append("uri : " + uri + "\n");
        sb.append("method : " + method + "\n");
        sb.append("ip : " + ip + "\n");
        sb.append("params : " + params + "\n");
        sb.append("body : " + body + "\n");
        sb.append("accessDate : " + accessDate + "\n");
        sb.append("headers : { \n" + headers + "  }\n");
        sb.append("==============================================================================\n");
        logger.info(String.valueOf(sb));
    }

    private Mono<Void> cacheRequestBody(ServerWebExchange exchange, Function<ServerHttpRequest, Mono<Void>> function) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
        // Join all the DataBuffers so we have a single DataBuffer for the body
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .defaultIfEmpty(factory.wrap(new EmptyByteBuf(factory.getByteBufAllocator())))
                .map((dataBuffer) -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    String bodyString = new String(bytes, StandardCharsets.UTF_8);
                    logInfo(request, bodyString);
                    // 这里下面的代码我原先没写,后续的转发直接失效,因为body数据被拿出来了
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer = exchange.getResponse().bufferFactory()
                                .wrap(bytes);
                        return Mono.just(buffer);
                    });

                    return (ServerHttpRequest) new ServerHttpRequestDecorator(
                            exchange.getRequest()) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return cachedFlux;
                        }
                    };
                }).switchIfEmpty(Mono.just(exchange.getRequest())).flatMap(function);
    }

}

思路就是定义一个全局Filter,因为是接入日志的打印,所以order小点。

代码就这些,复制即用,说下遇到的坑吧

  • 1、日志打印完后,body消失转发失败 这个是因为流传输中,你把buffer里面的数据拿出来了,但是不再塞进去,他就没了哦,所以需要再自己包装一个request
  • 2、如果是post请求,但是没有请求体,就会出现过滤器不通过的情况(如果不知道我说啥忽略 这个是真的坑,看网上的方法是判断是否是post或者header中的content length来判断是否有请求体,但是post可以没有请求体,contentLength也可以不传)
  • 3、如果将获取body的过程提取为方法,可能是webflux异步的原因(我猜的,我太菜了,如果知道的大佬评论说一下),会先return,然后body信息就拿不到为null,所以网上的方法都是将提取body的代码写在return中的。

希望有帮到大家!

标签:return,String,exchange,Spring,request,append,sb,Gateway,Cloud
From: https://blog.51cto.com/u_16167766/6670205

相关文章

  • spring中的@Transactional声明式事务
     1与编程式事务区别1.1声明式事务使用@Transactional注解来实现事务创建的,spring会为加了事务配置的类创建一个代理对象,基于动态代理,通过其中参数来控制事务的传播、事务回滚等。加在类上相当于给类中所有方法都添加事务。使用声明式事务的好处是使用简单,减少很多像是开......
  • springcloudalibaba -nacos config 配置中心以及服务发现和注册
    springcloud-config-nacos-client3377pom<!--nacos-config--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>&l......
  • spring-data-redis2.3.9不支持redis6.2提供的GEOSEARCH命令
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId>......
  • springcloud - zipkin链路调用
     通过sleuth,开启zipkin可通过访问localhost:9411/zipkin开启web界面查看链路调用traceid就是一个服务idparentid就是调用者的id原始服务parentid=null服务提供者provider导入依赖     <!--包含了sleuth+zipkin-->     <dependency>   ......
  • 1-快速上手SpringBoot
    1.SpringBoot入门程序制作(一)【idea联网版】步骤①:创建新模块,选择SpringInitializr,并配置模块相关基础信息特别关注:第3步点击Next时,Idea需要联网状态才可以进入到后面那一页,如果不能正常联网,就无法正确到达右面那个设置页了,会一直联网转圈特别关注:第5步选择java......
  • 【HDC.Cloud 2023】新鲜速递:从多元生态、开源到人才培养,让开发者成为决定性力量
    摘要:华为云开发者联盟邀您一起回顾大会精彩时刻。本文分享自华为云社区《【HDC.Cloud2023】新鲜速递:从多元生态、开源到人才培养,让开发者成为决定性力量》,作者:华为云社区精选。华为开发者大会2023(Cloud)7月7日在中国东莞正式揭开帷幕,邀请全球开发者共聚一堂,就AI浪潮之下的产业......
  • SpringBoot整合Sharding-JDBC水平分表
    本文使用Sharding-JDBC完成对订单表的水平分表,通过快速入门程序的开发,快速体验Sharding-JDBC的使用方法。首先创建两张表,t_order_1和t_order_2,这两张表是订单表拆分后的表,通过Sharding-Jdbc向订单表插入数据,按照一定的分片规则,主键为偶数的进入t_order_1,另一部分数据进入t_order_......
  • Spring单例的解决方案
    1spring单例V.S设计模式的单例设计模式单例,在整个应用中只有一个实例spring单例,在一个IoC容器中只有一个实例Spring框架对单例的支持是采用单例注册表但spring中的单例也不影响应用并发访问。大多数时候客户端都在访问我们应用中的业务对象,为减少并发控制,不应在业务对象中设置那......
  • springcloud - 通过消息总线bus进行刷新
    修改3344服务pom文件 <!--添加消息总线RabbitMQ支持--> <dependency>   <groupId>org.springframework.cloud</groupId>   <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>yaml文件 #rabbitmq相关配置 spring: ......
  • springcloud -config配置中心 整合github 或者gitee 单个刷新配置
    配置中心,通过从开源仓库上拉去配置,而不是在本地修改服务端配置cloud-config-center-3344     <dependency>       <groupId>org.springframework.cloud</groupId>       <artifactId>spring-cloud-config-server</artifactId>   ......