博客
关于我
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/

    你可能感兴趣的文章
    MySQL-数据页的结构
    查看>>
    MySQL-架构篇
    查看>>
    MySQL-索引的分类(聚簇索引、二级索引、联合索引)
    查看>>
    Mysql-触发器及创建触发器失败原因
    查看>>
    MySQL-连接
    查看>>
    mysql-递归查询(二)
    查看>>
    MySQL5.1安装
    查看>>
    mysql5.5和5.6版本间的坑
    查看>>
    mysql5.5最简安装教程
    查看>>
    mysql5.6 TIME,DATETIME,TIMESTAMP
    查看>>
    mysql5.6.21重置数据库的root密码
    查看>>
    Mysql5.6主从复制-基于binlog
    查看>>
    MySQL5.6忘记root密码(win平台)
    查看>>
    MySQL5.6的Linux安装shell脚本之二进制安装(一)
    查看>>
    MySQL5.6的zip包安装教程
    查看>>
    mysql5.7 for windows_MySQL 5.7 for Windows 解压缩版配置安装
    查看>>
    Webpack 基本环境搭建
    查看>>
    mysql5.7 安装版 表不能输入汉字解决方案
    查看>>
    MySQL5.7.18主从复制搭建(一主一从)
    查看>>
    MySQL5.7.19-win64安装启动
    查看>>