航海日志名词,/lɑɡ bʊk/:记录船舶航海日志测量结果以及航程其他重要细节的书。
Logbook是一个可扩展的 Java 库,可为不同的客户端和服务器端技术启用完整的请求和响应日志记录。它通过以下方式满足了特殊需求:a) 允许 Web 应用程序开发人员记录应用程序接收或发送的任何 HTTP 流量 b) 以便以后轻松保留和分析它。这对于传统日志分析、满足审计要求或调查单个历史流量问题非常有用。
日志可立即用于大多数常见设置。即使对于不常见的应用程序和技术,实现连接库/框架等所需的接口也应该很简单。到它。
将以下依赖项添加到您的项目中:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-core</ artifactId >
< version >${logbook.version}</ version >
</ dependency >
为了 Spring 5 / Spring Boot 2 向后兼容,请添加以下导入:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-servlet</ artifactId >
< version >${logbook.version}</ version >
< classifier >javax</ classifier >
</ dependency >
日志的其他模块/工件始终共享相同的版本号。
或者,您可以导入我们的物料清单...
< dependencyManagement >
< dependencies >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-bom</ artifactId >
< version >${logbook.version}</ version >
< type >pom</ type >
< scope >import</ scope >
</ dependency >
</ dependencies >
</ dependencyManagement >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-core</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-httpclient</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-jaxrs</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-json</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-netty</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-okhttp</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-okhttp2</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-servlet</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-spring-boot-starter</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor-common</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor-client</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor-server</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-logstash</ artifactId >
</ dependency >
日志记录器必须配置为跟踪级别才能记录请求和响应。对于 Spring Boot 2(使用 Logback),这可以通过将以下行添加到您的application.properties
来完成
logging.level.org.zalando.logbook: TRACE
所有集成都需要一个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 ();
日志曾经有一个非常严格的策略来记录请求/响应:
其中一些限制可以通过自定义HttpLogWriter
实现来缓解,但它们从来都不是理想的。
从 2.0 版开始,Logbook 现在以策略模式为核心。请务必阅读Strategy
接口的文档以了解其含义。
日志附带一些内置策略:
BodyOnlyIfStatusAtLeastStrategy
StatusAtLeastStrategy
WithoutBodyStrategy
从 3.4.0 版本开始,Logbook 配备了名为Attribute Extractor 的功能。属性基本上是可以从请求和/或响应中提取并用它们记录的键/值对列表。这个想法源于问题 381,其中请求一个功能从授权标头中的 JWT 令牌中提取主题声明。
AttributeExtractor
接口有两种extract
方法:一种只能从请求中提取属性,另一种可以同时提取请求和响应。两者都返回HttpAttributes
类的实例,它基本上是一个奇特的Map<String, Object>
。请注意,由于映射值的类型为Object
,因此它们应该具有适当的toString()
方法,以便它们以有意义的方式出现在日志中。或者,日志格式化程序可以通过实现自己的序列化逻辑来解决此问题。例如,内置日志格式化程序JsonHttpLogFormatter
使用ObjectMapper
来序列化值。
这是一个例子:
final class OriginExtractor implements AttributeExtractor {
@ Override
public HttpAttributes extract ( final HttpRequest request ) {
return HttpAttributes . of ( "origin" , request . getOrigin ());
}
}
然后必须通过注册此属性提取器来创建日志:
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new OriginExtractor ())
. build ();
这将导致请求日志包含以下内容:
"attributes":{"origin":"LOCAL"}
有关更高级的示例,请查看JwtFirstMatchingClaimExtractor
和JwtAllMatchingClaimsExtractor
类。前者从请求 JWT 令牌中提取与声明名称列表匹配的第一个声明。后者从请求 JWT 令牌中提取与声明名称列表匹配的所有声明。
如果您需要合并多个AttributeExtractor
,您可以使用CompositeAttributeExtractor
类:
final List < AttributeExtractor > extractors = List . of (
extractor1 ,
extractor2 ,
extractor3
);
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new CompositeAttributeExtractor ( extractors ))
. build ();
日志的工作分为几个不同的阶段:
每个阶段都由一个或多个可用于定制的接口表示。每个阶段都有一个合理的默认值。
记录 HTTP 消息并包含其正文是一项相当昂贵的任务,因此禁用某些请求的日志记录非常有意义。一个常见的用例是忽略来自负载均衡器的运行状况检查请求,或通常由开发人员向管理端点发出的任何请求。
定义条件就像编写一个特殊的Predicate
一样简单,该谓词决定是否应记录请求(及其相应的响应)。或者,您可以使用并组合预定义谓词:
Logbook logbook = Logbook . builder ()
. condition ( exclude (
requestTo ( "/health" ),
requestTo ( "/admin/**" ),
contentType ( "application/octet-stream" ),
header ( "X-Secret" , newHashSet ( "1" , "true" ):: contains )))
. build ();
排除模式,例如/admin/**
,松散地遵循 Ant 的路径模式风格,而不考虑 URL 的查询字符串。
过滤的目标是防止记录 HTTP 请求和响应的某些敏感部分。这通常包括授权标头,但也可以应用于某些纯文本查询或表单参数 - 例如密码。
日志支持不同类型的过滤器:
类型 | 运行于 | 适用于 | 默认 |
---|---|---|---|
QueryFilter | 查询字符串 | 要求 | access_token |
PathFilter | 小路 | 要求 | 不适用 |
HeaderFilter | 标头(单个键值对) | 两个都 | Authorization |
BodyFilter | 内容类型和正文 | 两个都 | json: access_token 和refresh_token 形式: client_secret , password 和refresh_token |
RequestFilter | HttpRequest | 要求 | 替换二进制、多部分和流主体。 |
ResponseFilter | HttpResponse | 回复 | 替换二进制、多部分和流主体。 |
QueryFilter
、 PathFilter
、 HeaderFilter
和BodyFilter
是相对较高级别的,应该能够满足大约 90% 的情况下的所有需求。对于更复杂的设置,应该回退到低级变体,即分别为RequestFilter
和ResponseFilter
(与ForwardingHttpRequest
/ ForwardingHttpResponse
结合使用)。
您可以像这样配置过滤器:
import static org . zalando . logbook . core . HeaderFilters . authorization ;
import static org . zalando . logbook . core . HeaderFilters . eachHeader ;
import static org . zalando . logbook . core . QueryFilters . accessToken ;
import static org . zalando . logbook . core . QueryFilters . replaceQuery ;
Logbook logbook = Logbook . builder ()
. requestFilter ( RequestFilters . replaceBody ( message -> contentType ( "audio/*" ). test ( message ) ? "mmh mmh mmh mmh" : null ))
. responseFilter ( ResponseFilters . replaceBody ( message -> contentType ( "*/*-stream" ). test ( message ) ? "It just keeps going and going..." : null ))
. queryFilter ( accessToken ())
. queryFilter ( replaceQuery ( "password" , "<secret>" ))
. headerFilter ( authorization ())
. headerFilter ( eachHeader ( "X-Secret" :: equalsIgnoreCase , "<secret>" ))
. build ();
您可以根据需要配置任意数量的过滤器 - 它们将连续运行。
您可以将 JSON 路径过滤应用于 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
}
}
日志使用关联 ID来关联请求和响应。这允许与匹配相关的请求和响应通常位于日志文件中的不同位置。
如果相关 ID 的默认实现不足以满足您的用例,您可以提供自定义实现:
Logbook logbook = Logbook . builder ()
. correlationId ( new CustomCorrelationId ())
. build ();
格式基本上定义了请求和响应如何转换为字符串。格式化程序不指定请求和响应的记录位置——编写器负责这项工作。
Logbook 附带两种不同的默认格式化程序: HTTP和JSON 。
HTTP是默认格式样式,由DefaultHttpLogFormatter
提供。它主要设计用于本地开发和调试,而不是用于生产用途。这是因为它不像 JSON 那样容易被机器读取。
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是一种替代格式样式,由JsonHttpLogFormatter
提供。与 HTTP 不同,它主要是为生产用途而设计的——解析器和日志使用者可以轻松使用它。
需要以下依赖:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-json</ artifactId >
</ dependency >
{
"origin" : " remote " ,
"type" : " request " ,
"correlation" : " 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b " ,
"protocol" : " HTTP/1.1 " ,
"sender" : " 127.0.0.1 " ,
"method" : " GET " ,
"uri" : " http://example.org/test " ,
"host" : " example.org " ,
"path" : " /test " ,
"scheme" : " http " ,
"port" : null ,
"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! "
}
注意: application/json
(和application/*+json
)类型的主体将内联到生成的 JSON 树中。即,JSON 响应正文不会被转义并表示为字符串:
{
"origin" : " local " ,
"type" : " response " ,
"correlation" : " 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b " ,
"duration" : 25 ,
"protocol" : " HTTP/1.1 " ,
"status" : 200 ,
"headers" : {
"Content-Type" : [ " application/json " ]
},
"body" : {
"greeting" : " Hello, world! "
}
}
通用日志格式 (CLF) 是 Web 服务器在生成服务器日志文件时使用的标准化文本文件格式。该格式通过CommonsLogFormatSink
支持:
185.85.220.253 - - [02/Aug/2019:08:16:41 0000] "GET /search?q=zalando HTTP/1.1" 200 -
扩展日志格式 (ELF) 是一种标准化文本文件格式,与通用日志格式 (CLF) 一样,Web 服务器在生成日志文件时使用该格式,但 ELF 文件提供了更多信息和灵活性。该格式通过ExtendedLogFormatSink
支持。另请参阅 W3C 文档。
默认字段:
date time c-ip s-dns cs-method cs-uri-stem cs-uri-query sc-status sc-bytes cs-bytes time-taken cs-protocol cs(User-Agent) cs(Cookie) cs(Referrer)
默认日志输出示例:
2019-08-02 08:16:41 185.85.220.253 localhost POST /search ?q=zalando 200 21 20 0.125 HTTP/1.1 "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0" "name=value" "https://example.com/page?q=123"
用户可以通过ExtendedLogFormatSink
的构造函数使用自定义字段覆盖默认字段:
new ExtendedLogFormatSink ( new DefaultHttpLogWriter (), "date time cs(Custom-Request-Header) sc(Custom-Response-Header)" )
对于 Http 标头字段: cs(Any-Header)
和sc(Any-Header)
,用户可以指定他们想要从请求中提取的任何标头。
其他支持的字段列在ExtendedLogFormatSink.Field
的值中,可以将其放入自定义字段表达式中。
cURL是一种替代格式样式,由CurlHttpLogFormatter
提供,它将请求呈现为可执行的cURL
命令。与 JSON 不同,它主要是为人类设计的。
curl -v -X GET ' http://localhost/test ' -H ' Accept: application/json '
请参阅 HTTP 或为响应提供自己的后备:
new CurlHttpLogFormatter ( new JsonHttpLogFormatter ());
Splunk是一种替代格式样式,由SplunkHttpLogFormatter
提供,它将请求和响应呈现为键值对。
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!
写入定义了格式化请求和响应的写入位置。 Logbook 提供了三种实现:Logger、Stream 和 Chunking。
默认情况下,请求和响应是使用slf4j记录器记录的,该记录器使用org.zalando.logbook.Logbook
类别和日志级别trace
。这可以定制:
Logbook logbook = Logbook . builder ()
. sink ( new DefaultSink (
new DefaultHttpLogFormatter (),
new DefaultHttpLogWriter ()
))
. build ();
另一种实现是将请求和响应记录到PrintStream
中,例如System.out
或System.err
。对于在生产中运行来说,这通常是一个糟糕的选择,但有时对于短期本地开发和/或调查可能很有用。
Logbook logbook = Logbook . builder ()
. sink ( new DefaultSink (
new DefaultHttpLogFormatter (),
new StreamHttpLogWriter ( System . err )
))
. build ();
ChunkingSink
会将长消息分割成较小的块,并在委托给另一个接收器时单独写入它们:
Logbook logbook = Logbook . builder ()
. sink ( new ChunkingSink ( sink , 1000 ))
. build ();
HttpLogFormatter
和HttpLogWriter
的组合非常适合大多数用例,但它也有局限性。直接实现Sink
接口可以实现更复杂的用例,例如将请求/响应写入数据库等结构化持久存储。
可以使用CompositeSink
将多个接收器合并为一个。
您必须将LogbookFilter
注册为过滤器链中的Filter
- 或者在您的web.xml
文件中(请注意,xml 方法将使用所有默认值并且不可配置):
< filter >
< filter-name >LogbookFilter</ filter-name >
< filter-class >org.zalando.logbook.servlet.LogbookFilter</ filter-class >
</ filter >
< filter-mapping >
< filter-name >LogbookFilter</ filter-name >
< url-pattern >/*</ url-pattern >
< dispatcher >REQUEST</ dispatcher >
< dispatcher >ASYNC</ dispatcher >
</ filter-mapping >
或者以编程方式,通过ServletContext
:
context . addFilter ( "LogbookFilter" , new LogbookFilter ( logbook ))
. addMappingForUrlPatterns ( EnumSet . of ( REQUEST , ASYNC ), true , "/*" );
注意:不支持ERROR
调度。强烈建议您在REQUEST
或ASNYC
调度中生成错误响应。
默认情况下, LogbookFilter
将处理带有application/x-www-form-urlencoded
主体的请求,该主体与任何其他请求没有不同,即您将在日志中看到请求主体。这种方法的缺点是您将无法使用任何HttpServletRequest.getParameter*(..)
方法。有关更多详细信息,请参阅问题 #94。
从 Logbook 1.5.0 开始,您现在可以使用logbook.servlet.form-request
系统属性指定定义 Logbook 如何处理这种情况的三种策略之一:
价值 | 优点 | 缺点 |
---|---|---|
body (默认) | 正文已记录 | 下游代码不能使用getParameter*() |
parameter | 主体已记录(但它是根据参数重建的) | 下游代码不能使用getInputStream() |
off | 下游代码可以决定是使用getInputStream() 还是getParameter*() | 正文未记录 |
安全应用程序通常需要稍微不同的设置。通常,您应该避免记录未经授权的请求,尤其是正文,因为它很快就会让攻击者淹没您的日志文件,从而占用您宝贵的磁盘空间。假设您的应用程序在另一个过滤器内处理授权,您有两种选择:
通过将LogbookFilter
放置在安全过滤器之后,您可以轻松实现前一种设置。后者稍微复杂一些。您将需要两个LogbookFilter
实例 - 一个在安全过滤器之前,一个在安全过滤器之后:
context . addFilter ( "SecureLogbookFilter" , new SecureLogbookFilter ( logbook ))
. addMappingForUrlPatterns ( EnumSet . of ( REQUEST , ASYNC ), true , "/*" );
context . addFilter ( "securityFilter" , new SecurityFilter ())
. addMappingForUrlPatterns ( EnumSet . of ( REQUEST ), true , "/*" );
context . addFilter ( "LogbookFilter" , new LogbookFilter ( logbook ))
. addMappingForUrlPatterns ( EnumSet . of ( REQUEST , ASYNC ), true , "/*" );
第一个日志过滤器将仅记录未经授权的请求。第二个过滤器将一如既往地记录授权请求。
logbook-httpclient
模块包含与HttpClient
一起使用的HttpRequestInterceptor
和HttpResponseInterceptor
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. addInterceptorFirst ( new LogbookHttpResponseInterceptor ())
. build ();
由于LogbookHttpResponseInterceptor
与HttpAsyncClient
不兼容,因此还有另一种方法来记录响应:
CloseableHttpAsyncClient client = HttpAsyncClientBuilder . create ()
. addInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. build ();
// and then wrap your response consumer
client . execute ( producer , new LogbookHttpAsyncResponseConsumer <>( consumer ), callback )
logbook-httpclient5
模块包含一个与HttpClient
一起使用的ExecHandler
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addExecInterceptorFirst ( "Logbook" , new LogbookHttpExecHandler ( logbook ))
. build ();
应首先添加Handler,以便在记录后进行压缩,在记录前进行解压。
为了避免重大更改,还有一个HttpRequestInterceptor
和HttpResponseInterceptor
与HttpClient
一起使用,只要不使用压缩(或其他 ExecHandlers),它们就可以正常工作:
CloseableHttpClient client = HttpClientBuilder . create ()
. addRequestInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. addResponseInterceptorFirst ( new LogbookHttpResponseInterceptor ())
. build ();
由于LogbookHttpResponseInterceptor
与HttpAsyncClient
不兼容,因此还有另一种方法来记录响应:
CloseableHttpAsyncClient client = HttpAsyncClientBuilder . create ()
. addRequestInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. build ();
// and then wrap your response consumer
client . execute ( producer , new LogbookHttpAsyncResponseConsumer <>( consumer ), callback )
笔记
支持 JAX-RS 2.x
Logbook 3.0 至 3.6 中删除了 JAX-RS 2.x(旧版)支持。
从 Logbook 3.7 开始,JAX-RS 2.x 支持又回来了。
但是,您需要添加javax
分类器才能使用正确的日志模块:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-jaxrs</ artifactId >
< version >${logbook.version}</ version >
< classifier >javax</ classifier >
</ dependency >
您还应该确保以下依赖项位于您的类路径上。默认情况下, logbook-jaxrs
导入jersey-client 3.x
,它与 JAX-RS 2.x 不兼容:
logbook-jaxrs
模块包含:
LogbookClientFilter
用于发出 HTTP 请求的应用程序
client . register ( new LogbookClientFilter ( logbook ));
用于 HTTP 服务器的LogbookServerFilter
resourceConfig . register ( new LogbookServerFilter ( logbook ));
logbook-jdkserver
模块提供对 JDK HTTP 服务器的支持,并包含:
与内置服务器一起使用的LogbookFilter
httpServer . createContext ( path , handler ). getFilters (). add ( new LogbookFilter ( logbook ))
logbook-netty
模块包含:
与HttpClient
一起使用的LogbookClientHandler
:
HttpClient httpClient =
HttpClient . create ()
. doOnConnected (
( connection -> connection . addHandlerLast ( new LogbookClientHandler ( logbook )))
);
与HttpServer
一起使用的LogbookServerHandler
:
HttpServer httpServer =
HttpServer . create ()
. doOnConnection (
connection -> connection . addHandlerLast ( new LogbookServerHandler ( logbook ))
);
Spring WebFlux 的用户可以选择以下任意选项:
NettyWebServer
(传递HttpServer
)NettyServerCustomizer
ReactorClientHttpConnector
(传递HttpClient
)WebClientCustomizer
logbook-spring-webflux
Micronaut 用户可以按照官方文档了解如何将 Logbook 与 Micronaut 集成。
logbook-okhttp2
模块包含一个与OkHttpClient
2.x 版本一起使用的Interceptor
:
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
如果您期望 gzip 压缩的响应,您需要另外注册我们的GzipInterceptor
。 OkHttp 内置的透明 gzip 支持将在任何网络拦截器之后运行,强制日志记录压缩的二进制响应。
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
client . networkInterceptors (). add ( new GzipInterceptor ());
logbook-okhttp
模块包含一个与OkHttpClient
3.x 版本一起使用的Interceptor
:
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. build ();
如果您期望 gzip 压缩的响应,您需要另外注册我们的GzipInterceptor
。 OkHttp 内置的透明 gzip 支持将在任何网络拦截器之后运行,强制日志记录压缩的二进制响应。
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. addNetworkInterceptor ( new GzipInterceptor ())
. build ();
logbook-ktor-client
模块包含:
与HttpClient
一起使用的LogbookClient
:
private val client = HttpClient ( CIO ) {
install( LogbookClient ) {
logbook = logbook
}
}
logbook-ktor-server
模块包含:
与Application
一起使用的LogbookServer
服务器:
private val server = embeddedServer( CIO ) {
install( LogbookServer ) {
logbook = logbook
}
}
或者,您可以使用logbook-ktor
,它附带logbook-ktor-client
和logbook-ktor-server
模块。
logbook-spring
模块包含一个与RestTemplate
一起使用的ClientHttpRequestInterceptor
:
LogbookClientHttpRequestInterceptor interceptor = new LogbookClientHttpRequestInterceptor ( logbook );
RestTemplate restTemplate = new RestTemplate ();
restTemplate . getInterceptors (). add ( interceptor );
Logbook 为 Spring Boot 用户提供了方便的自动配置。它会使用合理的默认值自动设置以下所有部分:
不要声明对logbook-core
依赖,而是向 Spring Boot Starter 声明一个依赖:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-spring-boot-starter</ artifactId >
< version >${logbook.version}</ version >
</ dependency >
如果需要,每个 bean 都可以被覆盖和定制,例如:
@ Bean
public BodyFilter bodyFilter () {
return merge (
defaultValue (),
replaceJsonStringProperty ( singleton ( "secret" ), "XXX" ));
}
请参阅LogbookAutoConfiguration
或下表以查看可能的集成点列表:
类型 | 姓名 | 默认 |
---|---|---|
FilterRegistrationBean | secureLogbookFilter | 基于LogbookFilter |
FilterRegistrationBean | logbookFilter | 基于LogbookFilter |
Logbook | 基于条件、过滤器、格式化程序和编写器 | |
Predicate<HttpRequest> | requestCondition | 无过滤器;稍后与logbook.exclude 和logbook.exclude 结合使用 |
HeaderFilter | 基于logbook.obfuscate.headers | |
PathFilter | 基于logbook.obfuscate.paths | |
QueryFilter | 基于logbook.obfuscate.parameters | |
BodyFilter | BodyFilters.defaultValue() ,参见过滤 | |
RequestFilter | RequestFilters.defaultValue() ,参见过滤 | |
ResponseFilter | ResponseFilters.defaultValue() ,参见过滤 | |
Strategy | DefaultStrategy | |
AttributeExtractor | NoOpAttributeExtractor | |
Sink | DefaultSink | |
HttpLogFormatter | JsonHttpLogFormatter | |
HttpLogWriter | DefaultHttpLogWriter |
多个过滤器合并为一个。
logbook-spring
自动配置的 bean logbook-spring
中的一些类包含在自动配置中。
您可以使用以下代码自动装配LogbookClientHttpRequestInterceptor
:
private final RestTemplate restTemplate ;
MyClient ( RestTemplateBuilder builder , LogbookClientHttpRequestInterceptor interceptor ){
this . restTemplate = builder
. additionalInterceptors ( interceptor )
. build ();
}
下表显示了可用的配置(按字母顺序排序):
配置 | 描述 | 默认 |
---|---|---|
logbook.attribute-extractors | AttributeExtractors 列表,包括type (当前为JwtFirstMatchingClaimExtractor 或JwtAllMatchingClaimsExtractor )、 claim-names 和claim-key 等配置。 | [] |
logbook.filter.enabled | 启用LogbookFilter 器 | true |
logbook.filter.form-request-mode | 确定如何处理表单请求 | body |
logbook.filters.body.default-enabled | 启用/禁用 java.util.ServiceLoader 收集的默认主体过滤器 | true |
logbook.format.style | 格式化样式( http 、 json 、 curl 或splunk ) | json |
logbook.httpclient.decompress-response | 启用/禁用带有 gzip 编码正文的 HttpClient 的附加解压缩过程(仅用于日志记录目的)。这意味着额外的减压和可能的性能影响。 | false (禁用) |
logbook.minimum-status | 启用日志记录的最低状态( status-at-least 和body-only-if-status-at-least ) | 400 |
logbook.obfuscate.headers | 需要混淆的标头名称列表 | [Authorization] |
logbook.obfuscate.json-body-fields | 要混淆的 JSON 正文字段列表 | [] |
logbook.obfuscate.parameters | 需要混淆的参数名称列表 | [access_token] |
logbook.obfuscate.paths | 需要混淆的路径列表。检查语法过滤。 | [] |
logbook.obfuscate.replacement | 要使用的值而不是混淆的值 | XXX |
logbook.predicate.include | 仅包含某些路径和方法(如果已定义) | [] |
logbook.predicate.exclude | 排除某些路径和方法(覆盖logbook.predicate.include ) | [] |
logbook.secure-filter.enabled | 启用SecureLogbookFilter 器 | true |
logbook.strategy | 策略( default 、 status-at-least 、 body-only-if-status-at-least 、 without-body ) | default |
logbook.write.chunk-size | 将日志行分割成大小不超过chunk-size 的较小块。 | 0 (禁用) |
logbook.write.max-body-size | 将正文截断至max-body-size 个字符并附加... 。logbook.write.max-body-size 值如何,日志仍将缓冲完整正文 | -1 (禁用) |
logbook :
predicate :
include :
- path : /api/**
methods :
- GET
- POST
- path : /actuator/**
exclude :
- path : /actuator/health
- path : /api/admin/**
methods :
- POST
filter.enabled : true
secure-filter.enabled : true
format.style : http
strategy : body-only-if-status-at-least
minimum-status : 400
obfuscate :
headers :
- Authorization
- X-Secret
parameters :
- access_token
- password
write :
chunk-size : 1000
attribute-extractors :
- type : JwtFirstMatchingClaimExtractor
claim-names : [ "sub", "subject" ]
claim-key : Principal
- type : JwtAllMatchingClaimsExtractor
claim-names : [ "sub", "iat" ]
对于基本的 Logback 配置
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
使用LogstashLogbackSink
配置日志
HttpLogFormatter formatter = new JsonHttpLogFormatter();
LogstashLogbackSink sink = new LogstashLogbackSink(formatter);
对于像这样的输出
{
"@timestamp" : "2019-03-08T09:37:46.239+01:00",
"@version" : "1",
"message" : "GET http://localhost/test?limit=1",
"logger_name" : "org.zalando.logbook.Logbook",
"thread_name" : "main",
"level" : "TRACE",
"level_value" : 5000,
"http" : {
// logbook request/response contents
}
}
您可以通过使用特定级别初始化LogstashLogbackSink
来灵活地自定义默认日志记录级别。例如:
LogstashLogbackSink sink = new LogstashLogbackSink(formatter, Level.INFO);
getWriter
和/或getParameter*()
干扰下游代码。有关详细信息,请参阅 Servlet。ERROR
调度。强烈建议您不要使用它来生成错误响应。 如果您有疑问、疑虑、错误报告等,请在此存储库的问题跟踪器中提交问题。
要做出贡献,只需提出拉取请求并添加有关添加或更改的简短描述(1-2 句话)。有关更多详细信息,请查看贡献指南。
Grand Turk 是纳尔逊时代一艘三桅六级护卫舰的复制品 - JoJan 的航海日志和图表已获得知识共享许可(署名-相同方式共享 3.0 未移植)。