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

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

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

背景与目标

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

实现目标

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

    1. 定义日志模型

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

    @Data@Documentpublic 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 作为数据存储接口:

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

    3. 实现日志收集逻辑

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

  • 获取请求的输入输出参数。
  • 将日志封装到 GatewayLog 对象中。
  • 将日志存储到MongoDB。
  • @Slf4j@Componentpublic 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数据和Redis缓存一致性方案详解
    查看>>
    Mysql数据库 InnoDB存储引擎中Master Thread的执行流程
    查看>>
    MySQL数据库 范式
    查看>>
    Mysql数据库B-Tree索引
    查看>>
    mysql数据库io空闲_mysql数据库磁盘io高的排查
    查看>>
    mysql数据库root密码忘记,查看或修改的解决方法
    查看>>
    MySQL数据库SQL注入靶场sqli通关实战(附靶场安装包)
    查看>>
    MYSQL数据库下载安装(Windows版本)
    查看>>
    MySQL数据库与Informix:能否创建同名表?
    查看>>
    mysql数据库中的数据如何加密呢?mysql8.0自带新特性
    查看>>
    MySQL数据库优化
    查看>>
    MySQL数据库优化总结
    查看>>
    Mysql数据库函数contac_函数:函数删除操作语法&使用例——《mysql 从入门到内卷再到入土》...
    查看>>
    mysql数据库命令备份还原
    查看>>
    mysql数据库基础教程
    查看>>
    MySQL数据库备份
    查看>>
    mysql数据库备份与恢复
    查看>>
    MySQL数据库备份实战
    查看>>
    Mysql数据库备份的问题:mysqldump: Got error: 1049: Unknown_无需整理
    查看>>
    mysql数据库如何重置密码是多少钱_MySQL数据库忘记root密码如何重置修改
    查看>>