Logbook HTTP日志框架

Logbook HTTP日志框架LogbookHTTP 日志框架 GitHub 文档及代码地址 https github com zalando logbookSprin 使用 Logbook 记录 HTTP 请求响应日志 https mp weixin com s 9LITBfpGqTDT 的 httptrace 端口能够记录每次访问的请求和响应信息 但是不能记录 body 这样在出问题时就不方便排查 而且 httptrace 不方便在原有的基础上进行扩展 所以只能寻求其他方式进行

Logbook HTTP日志框架

  1. GitHub 文档及代码地址:https://github.com/zalando/logbook
  2. SpringBoot使用Logbook记录HTTP请求响应日志:https://mp.weixin..com/s/9LITBfpGqTDTLbpfzp7tfA

Spring Boot的httptrace端口能够记录每次访问的请求和响应信息,但是不能记录body,这样在出问题时就不方便排查,而且httptrace不方便在原有的基础上进行扩展,所以只能寻求其他方式进行记录。

  • 允许web应用记录程序接收或发送的所有HTTP通信
  • 易于保留和进行分析

Logbook在大部分情况下是开箱即用的,即使对于一些不常用的技术或者应用,实现它们也非常简单。

特性简介

  • 日志记录:HTTP请求和响应,包含body;未授权的请求会记录部分日志(不包含body)
  • 自定义:能够自定义记录格式、记录方式以及请求记录的条件
  • 支持框架:Servlet容器、Apache’s HTTP client、Square’s OkHttp等
  • 混淆敏感数据
  • Spring Boot自动配置
  • 兼容 Scalyr
  • 合理的默认值

快速开始

Logbook为SpringBoot用户提供了很方便的自动配置功能,即我们所熟悉的starter。它使用了合理的默认值自动配置了以下功能:

  • Servlet filter
  • 适用于未授权请求的Servlet filter(如果检测到项目中使用Spring Security)
  • Header过滤器、Parameter过滤器、Body过滤器
  • HTTP格式化器、JSON格式化器
  • 日志写入方式

引入starter模块后SpringBooot会自动装配

<dependency> <groupId>org.zalando 
      groupId> <artifactId>logbook-spring-boot-starter 
       artifactId> <version>2.14.0 
        version>  
        
         
         dependency> 

日志记录器必须配置为trace才能记录请求和响应。 SpringBoot可以通过将以下行添加到 application.properties 来实现

logging.level.org.zalando.logbook = trace 

默认配置下,输出的日志为JSON格式:Request、Response

{ 
     "origin":"remote", "type":"request", "correlation":"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b", "protocol":"HTTP/1.1", "sender":"127.0.0.1", "method":"GET", "path":"http://example.org/test", "headers":{ 
     "Accept":[ "application/json" ], "Content-Type":[ "text/plain" ] }, "body":"Hello world!" } 
{ 
     "origin":"local", "type":"response", "correlation":"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b", "duration":25, "protocol":"HTTP/1.1", "status":200, "headers":{ 
     "Content-Type":[ "text/plain" ] }, "body":"Hello world!" } 

配置选项

下面的展示了SpringBoot中可配置的选项:

配置项 描述 默认值
logbook.include 仅包含某些URL(如果设置的话) []
logbook.exclude 排除某些URL(会覆盖logbook.include)设置了exclude的url是不会触发logbook []
logbook.filter.enabled 是否启用LogbookFilter true
logbook.filter.form-request-mode 如何处理表单请求 body
logbook.secure-filter.enabled 是否启用SecureLogbookFilter(同时项目中使用Spring Security才会生效) true
logbook.format.style 格式化样式(http,json,curl,splunk) json
logbook.strategy 策略(default,status-at-least, body-only-if-status-at-least,without-body) default
logbook.minimum-status 启用日志记录的最小HTTP响应状态值,当策略值为status-at-least或body-only-if-status-at-least时设置 400
logbook.obfuscate.headers 需要混淆的HTTP Header集合,默认脱敏符为X [Authorization]
logbook.obfuscate.paths 需要混淆的path集合,默认脱敏符为X []
logbook.obfuscate.parameters 需要混淆的parameter集合,默认脱敏符为X [access_token]
logbook.write.chunk-size 日志拆分块的大小,默认不拆分 0 (禁用)
logbook.write.max-body-size 截取Body的最大长度,后面使用...拼接
# 使用logbook需要注意的配置,设置org.zalando.logbook包的日志输出级别为trace,不然无法输出日志 logging: level: org.zalando.logbook: trace # 如下为logbook的配置 logbook: include: - /api/ exclude: - /actuator/ filter: enabled: true secure-filter: enabled: true format: style: json minimum-status: 400 obfuscate: headers: - Authorization - X-Secret parameters: - access_token - password paths: - user_id write: chunk-size: 1000 max-body-size: 10000 

详细用法

所有的功能集成都需要一个Logbook实例来完成,它保存了所有的配置并将所有需要的组件连接在一起。你可以使用所有的默认值创建一个实例:

Logbook logbook = Logbook.create(); 

或使用以下命令创建自定义版本:LogbookBuilder

Logbook logbook = Logbook.builder() .condition(new CustomCondition()) .queryFilter(new CustomQueryFilter()) .pathFilter(new CustomPathFilter()) .headerFilter(new CustomHeaderFilter()) .bodyFilter(new CustomBodyFilter()) .requestFilter(new CustomRequestFilter()) .responseFilter(new CustomResponseFilter()) .sink(new DefaultSink( new CustomHttpLogFormatter(), new CustomHttpLogWriter() )) .build(); 

策略(Strategy)

Logbook使用一个非常硬性的策略来执行请求/响应日志记录:

  • 请求/响应分开记录
  • 请求/响应尽快记录
  • 请求/响应一起记录或不记录(即没有部分流量记录)

其中一些限制可以通过自定义HttpLogWriter实现来缓解,但都不是理想。从2.0版本开始,Logbook引入了一个新的策略模式为核心,它内置了部分策略:

  • BodyOnlyIfStatusAtLeastStrategy
  • StatusAtLeastStrategy
  • WithoutBodyStrategy

阶段(Phases)

Logbook工作在几个不同的阶段:

  1. 条件(Conditional)
  2. 过滤(Filtering)
  3. 格式化(Formatting)
  4. 记录(Writing)

每个阶段都由一个或多个可以自定义的接口完成。每个阶段都有一个合理的默认值。

条件(Conditional)

记录HTTP消息并且包含其body代价是非常大的,所以禁用某些请求的日志记录非常有意义。常见情景就是忽略一些不必要的请求,比如SpringBoot的Actuator端点。定义一个条件非常简单,只需要编写一个Predicate来决定请求是否需要记录。当然,你也可以组合预定义的 Predicate:

注意关键类:org.zalando.logbook.Conditions、org.zalando.logbook.HttpRequest

import static org.zalando.logbook.Conditions.*; @Bean public Logbook logbook() { 
     return Logbook.builder() .condition(exclude( requestTo("/health"), requestTo("/admin/"), contentType("application/octet-stream"), header("X-Secret", Stream.of("1", "X-Secret").collect(Collectors.toSet())::contains))) .build(); } 
package com.xyz.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.zalando.logbook.HttpRequest; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.zalando.logbook.Conditions.*; @Configuration public class LogbookConfig { 
     @Bean public Predicate<HttpRequest> requestCondition() { 
     return exclude( requestTo("/health"), requestTo("/admin/"), contentType("application/octet-stream"), header("X-Secret", Stream.of("1", "X-Secret").collect(Collectors.toSet())::contains)); } } 

对于路径的包含和排除也可以通过设置 logbook.include和 logbook.exclude属性实现。并且能使用通配符:例如 /admin/ 松散地遵循 Ant 的路径模式风格,而不考虑 URL 的查询字符串。

过滤(Filtering)

过滤的目的是防止记录HTTP请求和响应的某些敏感数据。通常包括Authorization请求头,但也可以用于某些明文查询或表单参数,如access_token和password

Logbook支持不同类型的过滤器:

类型 作用于 适用于 默认值
QueryFilter 请求参数 request access_token
PathFilter 路径 request
HeaderFilter 请求头 request/response Authorization
BodyFilter Content-Type and body request/response json格式:access_token和refresh_token,form表单:client_secret和password
RequestFilter HttpRequest request 替换二进制、文件上传和流
ResponseFilter HttpResponse response 替换二进制、文件上传和流

QueryFilter, PathFilter, HeaderFilter 和 BodyFilter能够满足绝大多数情况下的需求,对于更复杂的需求,可以使用 RequestFilter 和 ResponseFilter(与ForwardingHttpRequest、ForwardingHttpResponse结合使用)

import static org.zalando.logbook.HeaderFilters.authorization; import static org.zalando.logbook.HeaderFilters.eachHeader; import static org.zalando.logbook.QueryFilters.accessToken; import static org.zalando.logbook.QueryFilters.replaceQuery; Logbook logbook = Logbook.builder() .requestFilter(RequestFilters.replaceBody(message -> contentType("audio/*").test(message) ? "mmh mmh mmh" : null)) .responseFilter(ResponseFilters.replaceBody(message -> contentType("*/*-stream").test(message) ? "keeps going" : null)) .queryFilter(accessToken()) .queryFilter(replaceQuery("password", " 
    
      " 
    )) .headerFilter(authorization()) .headerFilter(eachHeader("X-Secret"::equalsIgnoreCase, " 
    
      " 
    )) .build(); 
package com.xyz.config; import org.springframework.context.annotation.Bean; import org.zalando.logbook.*; import org.zalando.logbook.json.JacksonJsonFieldBodyFilter; import org.zalando.logbook.json.JsonBodyFilters; import java.util.Collections; public class LogbookConfig { 
     @Bean public QueryFilter queryFilter() { 
     // 当前Query中的参数access_token字段被混淆成了* return QueryFilters.replaceQuery("access_token", "*"); } @Bean public PathFilter pathFilter() { 
     // 当前Path中的参数userId字段被混淆成了* return PathFilters.replace("userId", "*"); } @Bean public HeaderFilter headerFilter() { 
     // 替换单个字段 HeaderFilters.replaceHeaders("Authorization", "*"); HeaderFilter.merge(HeaderFilters.defaultValue(),HeaderFilters.replaceHeaders("filed","*")); // 替换多个字段,用Set集合 HeaderFilter.merge(HeaderFilters.defaultValue(),HeaderFilters.replaceHeaders(Collections.EMPTY_SET,"*")); return HeaderFilters.replaceHeaders(Collections.singleton("secret"), "*"); } @Bean public BodyFilter bodyFilter() { 
     // 方式一 BodyFilter.merge(BodyFilters.defaultValue(), new JacksonJsonFieldBodyFilter(Collections.EMPTY_SET, "*")); // 方式二 return BodyFilter.merge(BodyFilters.defaultValue(), JsonBodyFilters.replaceJsonStringProperty(Collections.singleton("secret"), "*")); } @Bean public RequestFilter requestFilter () { 
     return RequestFilters.replaceBody( BodyReplacers.replaceBody(Conditions.contentType("audio/*"), " 
    )); } @Bean public ResponseFilter responseFilter () { 
     return ResponseFilters.replaceBody( BodyReplacers.replaceBody(Conditions.contentType("*/*-stream"), " 
    
      " 
    )); } } 

您可以根据需要配置任意数量的过滤器 – 它们将连续运行。


JsonPath body 过滤(实验),您可以将 JSONPath 过滤应用于 JSON 正文。 这里有些例子:

import static org.zalando.logbook.json.JsonPathBodyFilters.jsonPath; import static java.util.regex.Pattern.compile; Logbook logbook = Logbook.builder() .bodyFilter(jsonPath("$.password").delete()) .bodyFilter(jsonPath("$.active").replace("unknown")) .bodyFilter(jsonPath("$.address").replace("X")) .bodyFilter(jsonPath("$.name").replace(compile("^(\\w).+"), "$1.")) .bodyFilter(jsonPath("$.friends.*.name").replace(compile("^(\\w).+"), "$1.")) .bodyFilter(jsonPath("$.grades.*").replace(1.0)) .build(); 

在应用过滤之前和之后:

{ 
      "id": 1, "name": "Alice", "password": "s3cr3t", "active": true, "address": "Anhalter Straße 17 13, 67278 Bockenheim an der Weinstraße", "friends": [ { 
      "id": 2, "name": "Bob" }, { 
      "id": 3, "name": "Charlie" } ], "grades": { 
      "Math": 1.0, "English": 2.2, "Science": 1.9, "PE": 4.0 } } 
{ 
      "id": 1, "name": "Alice", "active": "unknown", "address": "XXX", "friends": [ { 
      "id": 2, "name": "B." }, { 
      "id": 3, "name": "C." } ], "grades": { 
      "Math": 1.0, "English": 1.0, "Science": 1.0, "PE": 1.0 } } 
格式化(Formatting)

格式化是把请求和响应转换为字符串。格式化不会指定请求和响应的记录位置,这是由Writer完成的。Logbook有两种默认格式化:HTTP 和 JSON

HTTP

HTTP 是默认的格式化样式,由DefaultHttpLogFormatter提供。 它主要用于本地开发和调试,而不是用于生产用途。 这是因为它不像JSON那样易于读取SpringBoot只需要配置:logbook.format.style=http,如下是 Request 和 Response

Incoming Request: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b GET http://example.org/test HTTP/1.1 Accept: application/json Host: localhost Content-Type: text/plain Hello world! 
Outgoing Response: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b Duration: 25 ms HTTP/1.1 200 Content-Type: application/json { 
     "value":"Hello world!"} 
JSON

JSON 是另一种格式样式,由 JsonHttpLogFormatter 提供。 与 HTTP 不同,它主要是为生产使用而设计的——解析器和日志消费者可以轻松地使用它。SpringBoot只需要配置:logbook.format.style=json(默认也是JSON),如下是 Request 和 Response

{ 
      "origin":"remote", "type":"request", "correlation":"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b", "protocol":"HTTP/1.1", "sender":"127.0.0.1", "method":"GET", "path":"http://example.org/test", "headers":{ 
      "Accept":[ "application/json" ], "Content-Type":[ "text/plain" ] }, "body":"Hello world!" } 
{ 
      "origin":"local", "type":"response", "correlation":"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b", "duration":25, "protocol":"HTTP/1.1", "status":200, "headers":{ 
      "Content-Type":[ "text/plain" ] }, "body":"Hello world!" } 
Common Log Format

通用日志格式 (CLF) 是 Web 服务器在生成服务器日志文件时使用的标准化文本文件格式。 通过 CommonsLogFormatSink 支持该格式:

185.85.220.253 - - [02/Aug/2019:08:16:41 0000] "GET /search?q=zalando HTTP/1.1" 200 - 
cURL

cURL 是另一种格式样式,由 CurlHttpLogFormatter 提供,它将请求呈现为可执行的 cURL 命令。 与 JSON 不同,它主要是为人类设计的。SpringBoot只需要配置:logbook.format.style=curl,如下是 Request 和 Response

curl -v -X GET 'http://localhost/test' -H 'Accept: application/json' 
Outgoing Response: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b Duration: 25 ms HTTP/1.1 200 Content-Type: application/json { 
     "value":"Hello world!"} 
Splunk

Splunk 是另一种格式样式,由 SplunkHttpLogFormatter 提供,它将请求和响应呈现为键值对。SpringBoot只需要配置:logbook.format.style=splunk,如下是 Request 和 Response

origin=remote type=request correlation=2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b protocol=HTTP/1.1 sender=127.0.0.1 method=POST uri=http://example.org/test host=example.org scheme=http port=null path=/test headers={ 
     Accept=[application/json], Content-Type=[text/plain]} body=Hello world! 
origin=local type=response correlation=2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b duration=25 protocol=HTTP/1.1 status=200 headers={ 
     Content-Type=[text/plain]} body=Hello world! 
记录(Writer)

Writer定义了格式化后的请求和响应写入的位置。Logbook内置了三种实现:Logger、Stream、Chunking

Logger

默认情况下,使用 org.zalando.logbook.Logbook 类别和日志级别 trace 的 slf4j 记录器记录请求和响应。 也可以自定义:

默认情况下,请求和响应使用了slf4j来进行日志记录,日志的级别为 trace。也可以自定义:

Logbook logbook = Logbook.builder() .sink(new DefaultSink( new DefaultHttpLogFormatter(), new DefaultHttpLogWriter() )) .build(); 
Stream

另一种实现是将请求和响应记录到 PrintStream,例如 System.out 或 System.err。 在生产环境中这是一个糟糕的选择,但有时对短期本地开发和/或调查很有用

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

ChunkingSink 会把长的消息分割成较小的块,并且会委托给另一个sink将它们写入,只需要设置 logbook.write.chunk-size属性即可

Logbook logbook = Logbook.builder() .sink(new ChunkingSink(sink, 1000)) .build(); 
关联ID(Correlation)

在SpringCloud应用中一般会集成Zipkin进行链路追踪,此时可以使用TraceId来关联请求和响应日志记录。

Logbook使用一个id来关联请求和响应,因为请求和响应通常位于日志文件中的不同位置(默认ID是16位随机字符组成)如默认不满足可以自定义实现:

@Bean public org.zalando.logbook.CorrelationId correlationId () { 
      return request -> UUID.randomUUID().toString(); } Logbook logbook = Logbook.builder().correlationId(new CustomCorrelationId()).build(); 
Sink

HttpLogFormatter 和 HttpLogWriter 的组合能够适用于大部分场合,但也有一些局限性。 实现 Sink 接口允许更复杂的用例,例如把请求和响应持久化到数据库。你可以使用 CompositeSink 将多个Sink合并为一个。

其他框架支持

Servlet

在Servlet环境中,Logbook是通过 LogbookFilter来实现的。默认情况下,对于application/x-www-form-urlencoded请求会同等对待,即你会在日志中看到请求body。这种方法的缺点是下游代码将无法使用任何 HttpServletRequest.getParameter*(…)方法。

从Logbook 1.5.0开始,可使用 logbook.servlet.form-request系统属性(System Property)指定三种策略之一,这些策略定义Logbook如何处理这种情况(可以从源码中查看:(org.zalando.logbook.servlet.FormRequestMode)

属性值 优点 缺点
body(默认) body会被记录 下游代码不能使用getParameter()
parameter body会被记录 下游代码不能使用getInputStream()
off 下游代码可以使用getParameter()或getInputStream() body不会被记录

Logbook默认还提供了对:Servlet、HTTP Client、JAX-RS、Netty、OkHttp v2.x、OkHttp v3.x的支持,具体使用方法可以参考官方文档

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207353.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午2:01
下一篇 2026年3月19日 下午2:01


相关推荐

  • SSL证书与Https应用部署小结

    SSL证书与Https应用部署小结

    2021年8月30日
    61
  • linux指令_linux最常用命令

    linux指令_linux最常用命令基本命令关机:shutdown-hhaltinit0poweroff重启:shutdown-rrebootinit6pwd:查看工作目录ls:查看指定目录的内容-l:列表显示-a:显示所有,包括隐藏文件-h:人性化的显示-d:只显示目录,不查看内容cd:切换工作目录.:当前目录..:上一级目录~:用户家目录-:上次切过来的目录目录结构:linux目录…

    2026年4月13日
    5
  • 职业规划-IT方向(超详细,超具体)

    职业规划-IT方向(超详细,超具体)前言今天是周五 本来想好好休息 前天写了一篇博文 说出我的故事 献给正在迷茫的你 https blog csdn net weixin article details 不少读者留言不知该如何做职业规划 于是继续拖着忙碌了一周疲倦的身体 坐在电脑前 吹着电扇 提笔写下这篇 职业规划 IT 方向 如果你是应届生 或者准备转行 IT 我想以我的个人经验

    2026年3月20日
    2
  • ingress什么意思_k8s kong

    ingress什么意思_k8s kongk8sIngress介绍Http代理Https代理Ingress介绍我们已经知道,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式,都有一定的缺点:NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显。LoadBalancer的缺点是每个Service都需要一个LB,浪费,麻烦,并且需要kubernetes之外的设备的支持。基于这种现状,kubernetes提供了Ingress资源对象,I

    2022年8月9日
    4
  • EasyPlayer简介

    EasyPlayer简介EasyPlayer简介EasyPlayer是一款基于EasyRTSPClient实现的AndroidRTSP播放器。目前实现的功能有:播放视频视频画面放大,拖动(类似360小水滴的拖动功能)开启、关闭声音抓拍录像保存视频缩略图

    2022年6月16日
    71
  • 2022年美赛A题思路分析[通俗易懂]

    2022年美赛A题思路分析[通俗易懂]2022美赛A题

    2022年5月11日
    43

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号