Appearance
Reactor Netty 访问日志
在 Spring Cloud Gateway 中,访问日志记录了客户端请求的详细信息,这对于监控、审计和故障排查都非常重要。Reactor Netty 提供了内置的访问日志功能,可以帮助我们追踪 API 网关的请求流量。
什么是 Reactor Netty 访问日志
Reactor Netty 访问日志是基于 Netty 的非阻塞 HTTP 服务器的日志记录功能。它记录了每个 HTTP 请求的详细信息,包括:
- 客户端 IP 地址
- 请求方法和 URL
- 响应状态码
- 响应时间
- 请求和响应的字节数
业务场景
在实际的微服务架构中,访问日志通常用于以下场景:
- 性能监控:通过分析响应时间,识别性能瓶颈
- 安全审计:记录可疑的访问行为,如大量 404 错误或异常 IP
- 业务分析:统计 API 调用频率,了解业务使用情况
- 故障排查:在出现问题时,快速定位问题请求
启用 Reactor Netty 访问日志
要启用 Reactor Netty 访问日志,必须设置 Java 系统属性,而不是 Spring Boot 配置属性。
方式一:JVM 启动参数
kotlin
// 在应用启动时添加 JVM 参数
// -Dreactor.netty.http.server.accessLogEnabled=true方式二:代码中设置系统属性
kotlin
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
class GatewayApplication
fun main(args: Array<String>) {
// 在应用启动前设置系统属性
System.setProperty("reactor.netty.http.server.accessLogEnabled", "true")
SpringApplication.run(GatewayApplication::class.java, *args)
}方式三:Docker 环境变量
yaml
# docker-compose.yml
version: "3.8"
services:
gateway:
image: your-gateway-app:latest
environment:
- JAVA_OPTS=-Dreactor.netty.http.server.accessLogEnabled=true
ports:
- "8080:8080"配置访问日志输出
默认情况下,访问日志会输出到控制台。在生产环境中,我们通常需要将访问日志单独输出到文件中,便于日志收集和分析。
Logback 配置示例
创建或修改 logback-spring.xml 配置文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 访问日志文件 Appender -->
<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
<file>logs/access_log.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<!-- 异步 Appender 提高性能 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>
<!-- Reactor Netty 访问日志专用 Logger -->
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async"/>
</logger>
<!-- 应用日志配置 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>日志滚动配置
对于生产环境,建议使用滚动日志以避免单个日志文件过大:
xml
<appender name="accessLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/access_log.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天滚动 -->
<fileNamePattern>logs/access_log.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 保留30天的日志 -->
<maxHistory>30</maxHistory>
<!-- 单个文件最大100MB -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>访问日志格式详解
Reactor Netty 的访问日志使用类似 Apache 通用日志格式:
127.0.0.1 - - [08/Jun/2025:10:30:15 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "Mozilla/5.0"各字段含义:
127.0.0.1:客户端 IP 地址-:远程用户标识(通常为-)-:认证用户名(通常为-)[08/Jun/2025:10:30:15 +0800]:请求时间戳"GET /api/users HTTP/1.1":请求方法、URI 和协议版本200:HTTP 响应状态码1234:响应字节数"-":引用页面(Referer)"Mozilla/5.0":用户代理字符串
完整的 Gateway 应用示例
以下是一个完整的 Spring Cloud Gateway 应用示例,演示如何启用和配置访问日志:
kotlin
package com.example.gateway
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.gateway.route.RouteLocator
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
import org.springframework.context.annotation.Bean
@SpringBootApplication
class GatewayApplication {
/**
* 配置路由规则
*/
@Bean
fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
return builder.routes()
.route("user-service") { r ->
r.path("/api/users/**")
.uri("http://localhost:8081")
}
.route("order-service") { r ->
r.path("/api/orders/**")
.uri("http://localhost:8082")
}
.build()
}
}
fun main(args: Array<String>) {
// 启用 Reactor Netty 访问日志
System.setProperty("reactor.netty.http.server.accessLogEnabled", "true")
SpringApplication.run(GatewayApplication::class.java, *args)
}java
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class GatewayApplication {
/**
* 配置路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r ->
r.path("/api/users/**")
.uri("http://localhost:8081")
)
.route("order-service", r ->
r.path("/api/orders/**")
.uri("http://localhost:8082")
)
.build();
}
public static void main(String[] args) {
// 启用 Reactor Netty 访问日志
System.setProperty("reactor.netty.http.server.accessLogEnabled", "true");
SpringApplication.run(GatewayApplication.class, args);
}
}性能考虑和最佳实践
1. 使用异步 Appender
访问日志的写入操作应该是异步的,避免阻塞请求处理线程。
xml
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
<!-- 队列大小,根据实际负载调整 -->
<queueSize>1024</queueSize>
<!-- 不丢弃任何日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 最大刷新时间 -->
<maxFlushTime>5000</maxFlushTime>
</appender>2. 合理的日志轮转策略
xml
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/access_log.%d{yyyy-MM-dd-HH}.log</fileNamePattern>
<!-- 保留7天的日志 -->
<maxHistory>168</maxHistory>
<!-- 总大小限制 -->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>3. 监控和告警
在生产环境中,建议结合日志监控工具:
kotlin
@Component
class AccessLogMonitor {
private val errorCount = AtomicLong(0)
private val requestCount = AtomicLong(0)
@EventListener
fun handleAccessLog(event: AccessLogEvent) {
requestCount.incrementAndGet()
if (event.status >= 400) {
errorCount.incrementAndGet()
}
// 错误率过高时发送告警
if (errorCount.get() > requestCount.get() * 0.1) {
sendAlert("Error rate too high: ${errorCount.get()}/${requestCount.get()}")
}
}
private fun sendAlert(message: String) {
// 发送告警通知
println("ALERT: $message")
}
}与 ELK Stack 集成
在微服务环境中,通常使用 ELK (Elasticsearch, Logstash, Kibana) 栈来收集和分析访问日志:
yaml
# logstash 配置示例
input { file { path => "/var/log/gateway/access_log.log" start_position => "beginning" } }
filter { grok { match => { "message" => "%{IPORHOST:client_ip} - - \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:status} %{NUMBER:bytes} \"%{DATA:referer}\" \"%{DATA:user_agent}\"" } }
date { match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ] } }
output { elasticsearch { hosts => ["elasticsearch:9200"] index => "gateway-access-logs-%{+YYYY.MM.dd}" } }常见问题和解决方案
问题 1:访问日志没有输出
确保设置的是 Java 系统属性,而不是 Spring Boot 配置属性。
kotlin
// ❌ 错误的方式 - 在 application.yml 中配置
// reactor:
// netty:
// http:
// server:
// access-log-enabled: true
// ✅ 正确的方式 - 设置系统属性
System.setProperty("reactor.netty.http.server.accessLogEnabled", "true")问题 2:日志格式不符合预期
访问日志格式是固定的,不能自定义。如果需要特定格式,可以通过自定义过滤器实现:
kotlin
@Component
class CustomAccessLogFilter : GlobalFilter, Ordered {
private val logger = LoggerFactory.getLogger("custom.access.log")
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
val startTime = System.currentTimeMillis()
return chain.filter(exchange).doFinally {
val request = exchange.request
val response = exchange.response
val duration = System.currentTimeMillis() - startTime
// 自定义日志格式
val logMessage = buildString {
append("${request.remoteAddress?.address?.hostAddress} ")
append("${request.method} ")
append("${request.uri} ")
append("${response.statusCode?.value()} ")
append("${duration}ms")
}
logger.info(logMessage)
}
}
override fun getOrder(): Int = Ordered.LOWEST_PRECEDENCE
}总结
Reactor Netty 访问日志是 Spring Cloud Gateway 中重要的监控和审计功能。通过合理的配置和使用,可以帮助我们:
- 监控系统性能:及时发现性能问题
- 提升安全性:记录和分析访问行为
- 支持故障排查:快速定位问题根源
- 数据驱动决策:基于访问统计优化系统
在生产环境中,务必注意日志文件的大小和轮转策略,避免磁盘空间耗尽。同时,使用异步日志写入以确保不影响请求处理性能。