SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷(下)

可以从日志中看出下面比较重要的信息:

  • 从抓包日志从可以得出 HTTP 请求与响应包中的所有内容。
  • 这里的日志,由于没被 spring-cloud-sleuth 包装,所以日志本身的占位符没有相关链路信息
  • 但是从包中,可以看到 spring-cloud-sleuth 的链路信息,这里是: "X-B3-Parentspanid": "ef9653a0003ed68e", "X-B3-Sampled": "0", "X-B3-Spanid": "82b6a0202dc1566b", "X-B3-Traceid": "ef9653a0003ed68e",
  • netty 本身有 traceId,即这里的 6666c1d11277d54e (分别是作为 HttpServer 接受请求和作为 HttpClient 转发请求后台微服务),这样其实就可以将 spring-cloud-sleuth 的链路信息与 netty 的链路信息结合在一起

这些日志很全面,但是有下面一些不好用的问题:

  • 只能输出所有请求响应的包内容,并不能定制输出哪些内容。例如对于文件上传请求,我们其实并不想看他的请求体,但是这里无法定制化。
  • 日志太多了,会影响我们的性能。

HttpWebHandlerAdapter 的 TRACE 日志

在系列前面文章的源码分析中,我们知道在入口的 HttpWebHandlerAdapter 中有请求与响应日志,可以通过下面的配置进行启用:

<AsyncLogger name="org.springframework.web.server.adapter.HttpWebHandlerAdapter" level="trace" additivity="false" includeLocation="true">
    <appender-ref ref="console" />
</AsyncLogger>

这样就能启用类似于下面的日志:

2021-11-27 01:25:28,472 TRACE [sports,,] [16188] [reactor-http-nio-2][org.springframework.core.log.LogFormatUtils:88]:[8d5138d1-3] HTTP GET "/test-ss/anything", headers={masked}
2021-11-27 01:25:28,696 TRACE [sports,,] [16188] [reactor-http-nio-2][org.springframework.core.log.LogFormatUtils:88]:[8d5138d1-3] Completed 200 OK, headers={masked}

如何让 headers 不要被掩码,可以通过下面的配置实现:

spring.codec:
  log-request-details: true

这个配置对应的配置类是:CodecProperties,通过源码我们也可以发现,我们还可以在这里限制 body 的大小:

CodecProperties.java

@ConfigurationProperties(prefix = "spring.codec")
public class CodecProperties {
  private boolean logRequestDetails;
  private DataSize maxInMemorySize;
}

加上配置后,日志如下:

2021-11-27 01:32:50,501 TRACE [sports,,] [17668] [reactor-http-nio-2][org.springframework.core.log.LogFormatUtils:88]:[ecf9f55a-1] HTTP GET "/test-ss/anything", headers=[Host:"127.0.0.1:8181", Connection:"keep-alive", Cache-Control:"max-age=0", Upgrade-Insecure-Requests:"1", User-Agent:"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3879.400 QQBrowser/10.8.4552.400", Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", Accept-Encoding:"gzip, deflate, br", Accept-Language:"zh-CN,zh;q=0.9"]
2021-11-27 01:32:51,212 TRACE [sports,,] [17668] [reactor-http-nio-2][org.springframework.core.log.LogFormatUtils:88]:[ecf9f55a-1] Completed 200 OK, headers=[Date:"Sat, 27 Nov 2021 01:32:48 GMT", Content-Type:"application/json", Server:"gunicorn/19.9.0", Access-Control-Allow-Origin:"*", Access-Control-Allow-Credentials:"true", content-length:"1084"]

但是这个日志内容太少,只包含请求和响应头,并且没有 spring-cloud-sleuth 相关的链路信息。

AccessLog

Spring Cloud Gateway 使用基于 reactor-netty 的 http 服务器,在 spring-boot 的封装中,可以通过添加 NettyServerCustomizer Bean 来实现添加自定义 AccessLog。AccessLog 包含的元素有:

AccessLogArgProvider.java

public interface AccessLogArgProvider {
  @Nullable
  @Deprecated
  String zonedDateTime();
  @Nullable
  ZonedDateTime accessDateTime();
  @Nullable
  SocketAddress remoteAddress();
  @Nullable
  CharSequence method();
  @Nullable
  CharSequence uri();
  @Nullable
  String protocol();
  @Nullable
  String user();
  @Nullable
  CharSequence status();
  long contentLength();
  long duration();
  @Nullable
  CharSequence requestHeader(CharSequence name);
  @Nullable
  CharSequence responseHeader(CharSequence name);
  @Nullable
  Map<CharSequence, Set<Cookie>> cookies();
}

可以看出里面没有 spring cloud sleuth 相关的链路信息,但是我们可以通过实现 Global Filter 将链路信息加入 Response 的 Header 中,并且通过 AccessLogArgProviderrequestHeader 将这些 Header 输出到 accesslog:

package com.github.jojotech.spring.cloud.apigateway.filter;
import java.util.List;
import lombok.extern.log4j.Log4j2;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
@Log4j2
@Component
public class CommonTraceFilter implements GlobalFilter, Ordered {
  public static final String TRACE_ID = "traceId";
  public static final String SPAN_ID = "spanId";
  @Autowired
  private Tracer tracer;
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    Span span = tracer.currentSpan();
    if (span == null) {
      span = tracer.nextSpan();
    }
    TraceContext context = span.context();
    ServerHttpResponse response = exchange.getResponse();
    HttpHeaders headers = response.getHeaders();
    headers.put(TRACE_ID, List.of(context.traceId()));
    headers.put(SPAN_ID, List.of(context.spanId()));
    return chain.filter(exchange);
  }
  @Override
  public int getOrder() {
    //需要在所有的 Filter 之前,拿到 trace 信息
    return Ordered.HIGHEST_PRECEDENCE;
  }
}

然后,通过实现 NettyServerCustomizer 注册一个 Bean 来配置 Access Log。

package com.github.jojotech.spring.cloud.apigateway.filter;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.AccessLog;
import reactor.netty.http.server.logging.AccessLogFactory;
import reactor.util.annotation.Nullable;
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
import org.springframework.stereotype.Component;
@Component
public class AccessLogNettyServerCustomizer implements NettyServerCustomizer {
  static final String DEFAULT_LOG_FORMAT =
      "{},{} -> {} - {} [{}] \"{} {} {}\" {} {} {} ms";
  static final String MISSING = "-";
  @Override
  public HttpServer apply(HttpServer httpServer) {
    httpServer = httpServer.accessLog(true, AccessLogFactory.createFilter(
        accessLogArgProvider -> {
          //所有的都打印
          return true;
        }, accessLogArgProvider -> {
          return AccessLog.create(DEFAULT_LOG_FORMAT, accessLogArgProvider
                  .responseHeader(CommonTraceFilter.TRACE_ID) == null ? MISSING : accessLogArgProvider
                  .responseHeader(CommonTraceFilter.TRACE_ID), accessLogArgProvider
                  .responseHeader(CommonTraceFilter.SPAN_ID) == null ? MISSING : accessLogArgProvider
                  .responseHeader(CommonTraceFilter.SPAN_ID), applyAddress(accessLogArgProvider
                  .remoteAddress()), accessLogArgProvider.user(),
              accessLogArgProvider.zonedDateTime(), accessLogArgProvider.method(), accessLogArgProvider
                  .uri(), accessLogArgProvider.protocol(), accessLogArgProvider.status(),
              accessLogArgProvider.contentLength() > -1 ? accessLogArgProvider
                  .contentLength() : MISSING, accessLogArgProvider.duration());
        })
    );
    return httpServer;
  }
  static String applyAddress(@Nullable SocketAddress socketAddress) {
    if (socketAddress instanceof InetSocketAddress) {
      InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
      return inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort();
    }
    else {
      return MISSING;
    }
  }
}

这样,我们就可以得到像下面的 access log:

2021-11-27 03:56:10,948  INFO [sports,,] [8536] [reactor-http-nio-2][reactor.util.Loggers$Slf4JLogger:269]:baa69b779a8497eb,baa69b779a8497eb -> 127.0.0.1:64196 - - [2021-11-27T03:56:10.720844200Z[Etc/GMT]] "GET /test-ss/anything HTTP/1.1" 200 1084 228 ms

accesslog 的也不能输出 body,所以我们还是需要定制自己的日志 Filter。

我们在下一节,会开始实现我们自己定制的日志 Filter

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
16天前
|
机器学习/深度学习 人工智能 运维
智能日志分析:用AI点亮运维的未来
智能日志分析:用AI点亮运维的未来
126 15
|
1月前
|
SQL 关系型数据库 MySQL
MySQL事务日志-Undo Log工作原理分析
事务的持久性是交由Redo Log来保证,原子性则是交由Undo Log来保证。如果事务中的SQL执行到一半出现错误,需要把前面已经执行过的SQL撤销以达到原子性的目的,这个过程也叫做"回滚",所以Undo Log也叫回滚日志。
MySQL事务日志-Undo Log工作原理分析
|
2月前
|
存储 运维 监控
Linux--深入理与解linux文件系统与日志文件分析
深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
56 7
|
2月前
|
监控 安全 Linux
启用Linux防火墙日志记录和分析功能
为iptables启用日志记录对于监控进出流量至关重要
|
2月前
|
监控 应用服务中间件 定位技术
要统计Nginx的客户端IP,可以通过分析Nginx的访问日志文件来实现
要统计Nginx的客户端IP,可以通过分析Nginx的访问日志文件来实现
197 3
|
3月前
|
存储 SQL 监控
|
3月前
|
运维 监控 安全
|
5月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
|
27天前
|
人工智能 安全 Java
AI 时代:从 Spring Cloud Alibaba 到 Spring AI Alibaba
本次分享由阿里云智能集团云原生微服务技术负责人李艳林主讲,主题为“AI时代:从Spring Cloud Alibaba到Spring AI Alibaba”。内容涵盖应用架构演进、AI agent框架发展趋势及Spring AI Alibaba的重磅发布。分享介绍了AI原生架构与传统架构的融合,强调了API优先、事件驱动和AI运维的重要性。同时,详细解析了Spring AI Alibaba的三层抽象设计,包括模型支持、工作流智能体编排及生产可用性构建能力,确保安全合规、高效部署与可观测性。最后,结合实际案例展示了如何利用私域数据优化AI应用,提升业务价值。
112 4
|
2月前
|
SpringCloudAlibaba 负载均衡 Dubbo
【SpringCloud Alibaba系列】Dubbo高级特性篇
本章我们介绍Dubbo的常用高级特性,包括序列化、地址缓存、超时与重试机制、多版本、负载均衡。集群容错、服务降级等。
【SpringCloud Alibaba系列】Dubbo高级特性篇