在 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.xmlapplication.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.outSystem.err

Logbook logbook = Logbook.builder()
  .sink(new DefaultSink(
    new DefaultHttpLogFormatter(),
    new StreamHttpLogWriter(System.out)
  ))
  .build();

应该避免在生产环境中使用控制台打印,但必要时可以在开发环境中使用。

5、Sink

实现 Sink 接口可以实现更复杂的用例,例如将请求/响应写入结构化的持久存储(如数据库)。

5.1、常用的 Sink 实现

Logbook 提供了一些具有常用日志格式的 Sink 实现,即 CommonsLogFormatSinkExtendedLogFormatSink

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 中添加 logstashlogstash-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.xmlappender 下的 encoderLogstashEncoder 启用了 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();

如上,将 JsonHttpLogFormatterLogstashLogbackSink 结合使用,以 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