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

    你可能感兴趣的文章
    PHP的json_encode函数应用到微信接口问题(include \uxxxx will create fail)
    查看>>
    php的web路径获取
    查看>>
    php的一些小笔记--字符串
    查看>>
    php的几种运行模式CLI、CGI、FastCGI、mod_php
    查看>>
    php的四大特性八大优势
    查看>>
    RabbitMQ
    查看>>
    PHP的威胁函数与PHP代码审计实战
    查看>>
    PHP的引用举例
    查看>>
    PHP相关代码
    查看>>
    RabbitMQ
    查看>>
    php知识点记录
    查看>>
    PHP类数组式访问(ArrayAccess接口)
    查看>>
    PHP系列:浅谈PHP中isset()和empty() 函数的区别
    查看>>
    PHP索引数组unset的坑-array_values解决方案
    查看>>
    PHP索引数组排序方法整理(冒泡、选择、插入、快速)
    查看>>
    PHP线程安全和非线程安全
    查看>>
    R3LIVE开源项目常见问题解决方案
    查看>>
    php缃戠珯,www.wfzwz.com
    查看>>
    php缓存查询函数
    查看>>
    php编写TCP服务端和客户端程序
    查看>>