在 Spring 中使用 Logbook 记录 HTTP 请求和响应
1、概览
Logbook 是一个可扩展的 Java 库,可为不同的客户端和服务器端提供完整的请求和响应日志。它允许开发人员记录应用接收或发送的任何 HTTP 流量。这可用于日志分析、审计或分析流量问题。
本文将带了解如何在 Spring Boot 中整合 Logbook,以及如何使用 Logbook 记录 HTTP 请求和响应。
2、依赖
在 Spring Boot 中添加 logbook-spring-boot-starter
依赖:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>3.9.0</version>
</dependency>
你可以在 Maven Central 中找到最新版本的 Logbook。
3、配置
Logbook 与 Spring Boot 应用中的 logback 日志配合使用。
我们需要在 logback-spring.xml
和 application.properties
文件中添加配置。
在 pom.xml 中添加 Logbook 库后,Spring Boot 就会自动配置 Logbook 库,我们需要做的就是在 application.properties
文件中添加日志级别:
logging.level.org.zalando.logbook.Logbook=TRACE
启用 TRACE
日志级别,即可记录 HTTP 请求和响应。
此外,还要在 logback-spring.xml
文件中添加了 Logbook 配置:
<logger name="org.zalando.logbook" level="INFO" additivity="false">
<appender-ref ref="RollingFile"/>
</logger>
添加完成后,就可运行应用了。每次 HTTP 请求调用后,Logbook 都会将请求和响应记录到 logback-spring.xml
配置中指定的日志文件路径:
11:08:14.737 [http-nio-8083-exec-10] TRACE org.zalando.logbook.Logbook - Incoming Request: eac2321df47c4414
Remote: 0:0:0:0:0:0:0:1
GET http://localhost:8083/api/hello?name=James HTTP/1.1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-encoding: gzip, deflate, br, zstd
...
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
11:08:14.741 [http-nio-8083-exec-10] TRACE org.zalando.logbook.Logbook - Outgoing Response: eac2321df47c4414
Duration: 4 ms
HTTP/1.1 200 OK
...
Date: Tue, 18 Jun 2024 05:38:14 GMT
Keep-Alive: timeout=60
Hello, James!
如上,就是 Logbook 库与 Spring Boot 集成的最低配置,以记录基本的请求和响应日志记录。
4、过滤和格式化
我们可以声明 Logbook 配置 bean:
@Bean
public Logbook logbook() {
Logbook logbook = Logbook.builder()
.condition(Conditions.exclude(Conditions.requestTo("/api/welcome"),
Conditions.contentType("application/octet-stream"),
Conditions.header("X-Secret", "true")))
.sink(new DefaultSink(new DefaultHttpLogFormatter(), new DefaultHttpLogWriter()))
.build();
return logbook;
}
如上,我们声明了 Logbook
配置 Bean。在构建 Logbook
Bean 时,我们指定了条件。在 exclude()
方法中指定的请求映射将被排除在日志记录之外。在上例中,Logbook 不会记录映射到路径 "/api/welcome"
的API的请求或响应。同样地,还使用 contentType()
方法和 header()
方法对具有 Content Type 和 Header 的请求进行了过滤。
类似的,应包含的 HTTP API 则应在 include()
方法中指定。如果不使用 include()
方法,Logbook 会记录除 exclude()
方法中设置的请求之外的所有请求。
需要在 application.properties
文件中设置 filter
属性,这样过滤器配置才能生效。
# 属性 `logbook.filter.enabled` 应设置为 `true`:
logbook.filter.enabled=true
Logbook 使用 sl4j logger 记录请求和响应,默认使用 org.zalando.logbook.Logbook
类和 trace
日志级别:
sink(new DefaultSink(
new DefaultHttpLogFormatter(),
new DefaultHttpLogWriter()
))
上述默认配置启用了应用的日志记录。
GET http://localhost:8083/api/hello?name=John HTTP/1.1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
....
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 47
Content-Type: text/html;charset=UTF-8
Date: Fri, 07 Jun 2024 11:12:27 GMT
Keep-Alive: timeout=60
Hello, John
可以将这些响应记录到 System.out
或 System.err
:
Logbook logbook = Logbook.builder()
.sink(new DefaultSink(
new DefaultHttpLogFormatter(),
new StreamHttpLogWriter(System.out)
))
.build();
应该避免在生产环境中使用控制台打印,但必要时可以在开发环境中使用。
5、Sink
实现 Sink
接口可以实现更复杂的用例,例如将请求/响应写入结构化的持久存储(如数据库)。
5.1、常用的 Sink 实现
Logbook 提供了一些具有常用日志格式的 Sink 实现,即 CommonsLogFormatSink
和 ExtendedLogFormatSink
。
ChunkingSink
会将长信息分割成较小的消息块(Chunk),并单独写入,同时将它们委托给另一个 Sink:
Logbook logbook = Logbook.builder()
.sink(new ChunkingSink(sink, 1000))
.build();
5.2、Logstash Sink
Logbook 在附加库(Additional Library)中提供了一个 logstash
Encoder。
来看看 LogstashLogbackSink
的示例,首先在 pom.xml
中添加 logstash
和 logstash-encoder
依赖:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-logstash</artifactId>
<version>3.9.0</version>
</dependency>
然后,更改 logback-spring.xml
中 appender
下的 encoder
。LogstashEncoder
启用了 LogstashLogbackSink
。
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
...
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
现在,声明 LogstashLogbackSink
并将其添加到 Logbook
builder 中:
HttpLogFormatter formatter = new JsonHttpLogFormatter();
LogstashLogbackSink logstashsink = new LogstashLogbackSink(formatter);
Logbook logbook = Logbook.builder()
.sink(logstashsink)
.build();
如上,将 JsonHttpLogFormatter
与 LogstashLogbackSink
结合使用,以 JSON 格式打印日志:
{
"@timestamp": "2024-06-07T16:46:24.5673233+05:30",
"@version": "1",
"message": "200 OK GET http://localhost:8083/api/hello?name=john",
"logger_name": "org.zalando.logbook.Logbook",
"thread_name": "http-nio-8083-exec-6",
"level": "TRACE",
"http": {
...
"Content-Length": [
"12"
],
...
"body": "Hello, john!"
}
}
JSON 日志以单行打印;我们在此对其进行了格式化,以提高可读性。
我们可以在声明 LogstashLogbackSink
对象时更改日志级别:
LogstashLogbackSink logstashsink = new LogstashLogbackSink(formatter, Level.INFO);
还可以将 SplunkHttpLogFormatter
与 Logbook Sink 结合使用。它以 Key/ Value 格式打印日志:
origin=remote ... method=GET uri=http://localhost:8083/api/hello?name=John host=localhost path=/api/hello ...
5.3、CompositeSink
我们可以将多个 Sink 组合起来,形成一个 CompositeSink
(组合 Sink):
CompositeSink compsink = new CompositeSink(Arrays.asList(logstashsink, new CommonsLogFormatSink(new DefaultHttpLogWriter())));
Logbook logbook = Logbook.builder()
.sink(compsink)
.build();
如上,该配置使用 CompositeSink
中指定的所有 Sink 来记录请求的详细信息。在这里,Logbook 通过组合两个 Sink 来记录请求:
... "message":"GET http://localhost:8083/api/hello?name=John",... uri":"http://
localhost:8083/api/hello?name=John",...
... "message":"200 OK GET http://localhost:8083/api/hello?name=John",.."headers":{"Connection":["keep-alive"],...
6、总结
本文介绍了如何以最少的配置整合 Logbook 与 Spring Boot,以及如何使用 exclude()
和 include()
方法过滤请求路径,最后介绍了 LogstashLogbackSink
等 Sink 实现和 JsonHttpLogFormatter
等 Formatter 的用法。
Ref:https://www.baeldung.com/spring-logbook-http-logging