博客
关于我
SpringCloud Alibaba实战二十九 | SpringCloud Gateway 请求响应日志
阅读量:630 次
发布时间:2019-03-14

本文共 5540 字,大约阅读时间需要 18 分钟。

Spring Cloud Gateway日志收集与存储优化方案

背景与目标

在微服务架构中,日志的收集与存储对于问题定位和服务优化具有重要意义。Spring Cloud Gateway作为API网关,在处理大量请求时,日志的清晰收集和存储成为关键环节。本节将详细介绍在网关层统一日志收集的实现方案。

实现目标

  • 日志收集:获取请求的输入输出参数,封装成统一的日志格式。
  • 日志存储:将收集到的日志存储到MongoDB中,支持后续检索。
  • 核心实现步骤

    1. 定义日志模型

    首先,我们需要定义一个日志模型 GatewayLog 来存储请求的相关信息。该模型应包含以下字段:

    @Data
    @Document
    public class GatewayLog {
    @Id
    private String id;
    /** 访问实例 */
    private String targetServer;
    /** 请求路径 */
    private String requestPath;
    /** 请求方法 */
    private String requestMethod;
    /** 协议 */
    private String schema;
    /** 请求体 */
    private String requestBody;
    /** 响应体 */
    private String responseData;
    /** 请求IP */
    private String ip;
    /** 请求时间 */
    private Date requestTime;
    /** 响应时间 */
    private Date responseTime;
    /** 执行时间 */
    private long executeTime;
    }

    2. 定义日志存储接口

    接下来,定义一个 AccessLogRepository 作为数据存储接口:

    @Repository
    public interface AccessLogRepository extends ReactiveMongoRepository
    {
    @Query("{ gatewayLog.ip : ?0 }")
    Flux
    findByIp(String ip);
    }

    3. 实现日志收集逻辑

    AccessLogFilter 中,实现日志收集逻辑。该过滤器需要:

  • 获取请求的输入输出参数。
  • 将日志封装到 GatewayLog 对象中。
  • 将日志存储到MongoDB。
  • @Slf4j
    @Component
    public class AccessLogFilter implements GlobalFilter, Ordered {
    @Autowired
    private AccessLogService accessLogService;
    private final List
    messageReaders = HandlerStrategies.withDefaults().messageReaders();
    @Override
    public int getOrder() {
    return -100;
    }
    @Override
    public Mono
    filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String requestPath = request.getPath().pathWithinApplication().value();
    Route route = getGatewayRoute(exchange);
    String ipAddress = WebUtils.getServerHttpRequestIpAddress(request);
    GatewayLog gatewayLog = new GatewayLog();
    gatewayLog.setSchema(request.getURI().getScheme());
    gatewayLog.setRequestMethod(request.getMethodValue());
    gatewayLog.setRequestPath(requestPath);
    gatewayLog.setTargetServer(route.getId());
    gatewayLog.setRequestTime(new Date());
    gatewayLog.setIp(ipAddress);
    if (isBodyReadable(request)) {
    return writeBodyLog(exchange, chain, gatewayLog);
    } else {
    return writeBasicLog(exchange, chain, gatewayLog);
    }
    }
    private boolean isBodyReadable(ServerWebExchange request) {
    return request.getHeaders().getContentType().isCompatibleWith(
    MediaType.APPLICATION_FORM_URLENCODED) ||
    request.getHeaders().getContentType().isCompatibleWith(MediaType.APPLICATION_JSON);
    }
    private Mono
    writeBasicLog(...) {
    // 收集请求参数并封装到日志
    // 获取响应体并记录日志
    return chain.filter(exchange.mutate().response(decoratedResponse).build())
    .then(Mono.fromRunnable(() -> writeAccessLog(gatewayLog)));
    }
    private Mono
    writeBodyLog(...) {
    // 处理请求体,避免只能读取一次的问题
    return bodyInserter.insert(outputMessage, ...)
    .then(Mono.fromRunnable(() -> {
    decoratedRequest = requestDecorate(exchange, headers, outputMessage);
    decoratedResponse = recordResponseLog(exchange, gatewayLog);
    return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())
    .then(Mono.fromRunnable(() -> writeAccessLog(gatewayLog)));
    }));
    }
    private void writeAccessLog(GatewayLog gatewayLog) {
    log.info(gatewayLog.toString());
    accessLogService.saveAccessLog(gatewayLog);
    }
    private Route getGatewayRoute(ServerWebExchange exchange) {
    return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    }
    private ServerHttpRequestDecorator requestDecorate(...) {
    // 重新封装请求,处理分段传输问题
    return new ServerHttpRequestDecorator(exchange.getRequest()) {
    @Override
    public HttpHeaders getHeaders() {
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.putAll(super.getHeaders());
    if (contentLength > 0) {
    httpHeaders.setContentLength(contentLength);
    } else {
    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
    }
    return httpHeaders;
    }
    @Override
    public Flux
    .getBody() {
    return outputMessage.getBody();
    }
    };
    }
    private ServerHttpResponseDecorator recordResponseLog(...) {
    // 处理响应体分段传输问题
    return new ServerHttpResponseDecorator(response) {
    @Override
    public Mono
    writeWith(Publisher
    body) { // 处理响应体,生成执行时间和存储日志 return super.writeWith(body); } }; } }

    4. 引入MongoDB

    在项目中引入MongoDB Reactive版本:

    org.springframework.boot
    spring-boot-starter-data-mongodb-reactive

    5. 配置MongoDB

    在配置中心(如Nacos)中添加MongoDB配置:

    spring:
    data:
    mongodb:
    host: xxx.xx.x.xx
    port: 27017
    database: accesslog
    username: accesslog
    password: xxxx

    6. 测试与验证

  • 启动MongoDB服务。
  • 发送请求,通过Postman或其他工具验证日志是否正确存储在MongoDB中。
  • 检查日志内容,确保所有必要信息都已正确收集和存储。
  • 注意事项

  • 过滤器顺序getOrder() 方法返回值必须为 -1,否则标准过滤器会在获取后端响应之前发送响应。
  • 分段传输:处理大体积请求时,需要注意分段传输的问题,避免占用过多内存。
  • 日志格式:确保日志格式符合要求,方便后续分析和检索。
  • 通过以上优化方案,我们可以在网关层统一收集和存储日志,支持后续问题定位和服务监控。

    转载地址:http://cycoz.baihongyu.com/

    你可能感兴趣的文章
    Nginx运维与实战(二)-Https配置
    查看>>
    Nginx配置Https证书
    查看>>
    Nginx配置ssl实现https
    查看>>
    Nginx配置TCP代理指南
    查看>>
    Nginx配置——不记录指定文件类型日志
    查看>>
    nginx配置一、二级域名、多域名对应(api接口、前端网站、后台管理网站)
    查看>>
    Nginx配置代理解决本地html进行ajax请求接口跨域问题
    查看>>
    nginx配置全解
    查看>>
    Nginx配置参数中文说明
    查看>>
    nginx配置域名和ip同时访问、开放多端口
    查看>>
    Nginx配置好ssl,但$_SERVER[‘HTTPS‘]取不到值
    查看>>
    Nginx配置如何一键生成
    查看>>
    Nginx配置实例-负载均衡实例:平均访问多台服务器
    查看>>
    Nginx配置文件nginx.conf中文详解(总结)
    查看>>
    Nginx配置负载均衡到后台网关集群
    查看>>
    ngrok | 内网穿透,支持 HTTPS、国内访问、静态域名
    查看>>
    NHibernate学习[1]
    查看>>
    NHibernate异常:No persister for的解决办法
    查看>>
    NIFI1.21.0_Mysql到Mysql增量CDC同步中_日期类型_以及null数据同步处理补充---大数据之Nifi工作笔记0057
    查看>>
    NIFI1.21.0_NIFI和hadoop蹦了_200G集群磁盘又满了_Jps看不到进程了_Unable to write in /tmp. Aborting----大数据之Nifi工作笔记0052
    查看>>