Существительное бортового журнала , /lɑɡ bʊk/: книга, в которой записываются измерения из корабельного журнала, а также другие важные детали путешествия.
Logbook — это расширяемая библиотека Java, позволяющая осуществлять полное ведение журнала запросов и ответов для различных клиентских и серверных технологий. Он удовлетворяет особые потребности, а) позволяя разработчикам веб-приложений регистрировать любой HTTP-трафик, который приложение получает или отправляет; б) таким образом, чтобы его можно было легко сохранить и проанализировать позже. Это может быть полезно для традиционного анализа журналов, выполнения требований аудита или исследования отдельных исторических проблем с трафиком.
Журнал готов к использованию «из коробки» для большинства распространенных настроек. Даже для необычных приложений и технологий должно быть просто реализовать необходимые интерфейсы для подключения библиотеки/фреймворка/т. д. к этому.
Добавьте в свой проект следующую зависимость:
< 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, в основе журнала теперь лежит шаблон стратегии. Обязательно прочтите документацию интерфейса 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
}
}
Журнал использует идентификатор корреляции для корреляции запросов и ответов. Это позволяет выполнять запросы и ответы, связанные с совпадением, которые обычно располагаются в разных местах файла журнала.
Если реализация идентификатора корреляции по умолчанию недостаточна для вашего варианта использования, вы можете предоставить собственную реализацию:
Logbook logbook = Logbook . builder ()
. correlationId ( new CustomCorrelationId ())
. build ();
Форматирование определяет, как запросы и ответы будут преобразованы в строки. Форматтеры не указывают, где регистрируются запросы и ответы — эту работу выполняют авторы.
Журнал поставляется с двумя разными форматтерами по умолчанию: 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) — это стандартизированный формат текстового файла, используемый веб-серверами при создании файлов журналов сервера. Формат поддерживается через CommonsLogFormatSink
:
185.85.220.253 - - [02/Aug/2019:08:16:41 0000] "GET /search?q=zalando HTTP/1.1" 200 -
Расширенный формат журнала (ELF) — это стандартизированный формат текстового файла, такой как общий формат журнала (CLF), который используется веб-серверами при создании файлов журнала, но файлы 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 будет действовать в этой ситуации, используя системное свойство logbook.servlet.form-request
:
Ценить | Плюсы | Минусы |
---|---|---|
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
содержит как HttpRequestInterceptor
, так и HttpResponseInterceptor
для использования с HttpClient
:
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
содержит ExecHandler
для использования с HttpClient
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addExecInterceptorFirst ( "Logbook" , new LogbookHttpExecHandler ( logbook ))
. build ();
Сначала следует добавить обработчик, чтобы сжатие выполнялось после регистрации, а распаковка выполнялась перед записью.
Чтобы избежать критических изменений, существуют также 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
Поддержка JAX-RS 2.x (устаревшая) была прекращена в журнале 3.0 до версии 3.6.
Начиная с версии журнала 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 ));
LogbookServerFilter
для использования с HTTP-серверами.
resourceConfig . register ( new LogbookServerFilter ( logbook ));
Модуль logbook-jdkserver
обеспечивает поддержку HTTP-сервера JDK и содержит:
LogbookFilter
для использования со встроенным сервером.
httpServer . createContext ( path , handler ). getFilters (). add ( new LogbookFilter ( logbook ))
Модуль logbook-netty
содержит:
LogbookClientHandler
для использования с HttpClient
:
HttpClient httpClient =
HttpClient . create ()
. doOnConnected (
( connection -> connection . addHandlerLast ( new LogbookClientHandler ( logbook )))
);
LogbookServerHandler
для использования с HttpServer
:
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
содержит Interceptor
для использования с OkHttpClient
версии 2.x:
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
Если вы ожидаете ответов, сжатых с помощью gzip, вам необходимо дополнительно зарегистрировать наш GzipInterceptor
. Поддержка прозрачного gzip, встроенная в OkHttp, будет работать после любого сетевого перехватчика, который заставляет журнал регистрации регистрировать сжатые двоичные ответы.
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
client . networkInterceptors (). add ( new GzipInterceptor ());
Модуль logbook-okhttp
содержит Interceptor
для использования с OkHttpClient
версии 3.x:
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. build ();
Если вы ожидаете ответов, сжатых с помощью gzip, вам необходимо дополнительно зарегистрировать наш GzipInterceptor
. Поддержка прозрачного gzip, встроенная в OkHttp, будет работать после любого сетевого перехватчика, который заставляет журнал регистрации регистрировать сжатые двоичные ответы.
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. addNetworkInterceptor ( new GzipInterceptor ())
. build ();
Модуль logbook-ktor-client
содержит:
LogbookClient
для использования с HttpClient
:
private val client = HttpClient ( CIO ) {
install( LogbookClient ) {
logbook = logbook
}
}
Модуль logbook-ktor-server
содержит:
LogbookServer
для использования с Application
:
private val server = embeddedServer( CIO ) {
install( LogbookServer ) {
logbook = logbook
}
}
В качестве альтернативы вы можете использовать logbook-ktor
, который поставляется как с модулями logbook-ktor-client
так и logbook-ktor-server
.
Модуль logbook-spring
содержит ClientHttpRequestInterceptor
для использования с RestTemplate
:
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
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
Некоторые классы из logbook-spring
включены в автоконфигурацию.
Вы можете автоматически подключить LogbookClientHttpRequestInterceptor
с помощью такого кода:
private final RestTemplate restTemplate ;
MyClient ( RestTemplateBuilder builder , LogbookClientHttpRequestInterceptor interceptor ){
this . restTemplate = builder
. additionalInterceptors ( interceptor )
. build ();
}
В следующих таблицах показаны доступные конфигурации (отсортированные в алфавитном порядке):
Конфигурация | Описание | По умолчанию |
---|---|---|
logbook.attribute-extractors | Список AttributeExtractor, включая такие конфигурации, как 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 | Включает/отключает дополнительный процесс распаковки для HttpClient с телом, закодированным gzip (только для целей ведения журнала). Это означает дополнительную декомпрессию и возможное влияние на производительность. | 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*()
. Дополнительные сведения см. в разделе «Сервлет».ERROR
. Настоятельно рекомендуется не использовать его для получения ошибочных ответов. Если у вас есть вопросы, замечания, отчеты об ошибках и т. д., сообщите о проблеме в систему отслеживания ошибок этого репозитория.
Чтобы внести свой вклад, просто сделайте запрос на включение и добавьте краткое описание (1–2 предложения) вашего дополнения или изменения. Для получения более подробной информации ознакомьтесь с правилами внесения взносов.
Grand Turk, точная копия трехмачтового фрегата 6-го ранга времен Нельсона - бортовой журнал и карты Джоджана, распространяется по лицензии Creative Commons (Attribution-Share Alike 3.0 Unported).