Journal de bord nom, /lɑɡ bʊk/ : Un livre dans lequel les mesures du journal de bord du navire sont enregistrées, ainsi que d'autres détails importants du voyage.
Logbook est une bibliothèque Java extensible permettant une journalisation complète des demandes et des réponses pour différentes technologies côté client et côté serveur. Il répond à un besoin particulier en a) permettant aux développeurs d'applications Web de consigner tout trafic HTTP qu'une application reçoit ou envoie b) d'une manière qui facilite sa conservation et son analyse ultérieure. Cela peut être utile pour l'analyse traditionnelle des journaux, pour répondre aux exigences d'audit ou pour enquêter sur des problèmes de trafic historiques individuels.
Logbook est prêt à l’emploi pour les configurations les plus courantes. Même pour des applications et des technologies peu courantes, il devrait être simple d'implémenter les interfaces nécessaires pour connecter une bibliothèque/un framework/etc. à cela.
Ajoutez la dépendance suivante à votre projet :
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-core</ artifactId >
< version >${logbook.version}</ version >
</ dependency >
Pour la rétrocompatibilité Spring 5 / Spring Boot 2, veuillez ajouter l'importation suivante :
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-servlet</ artifactId >
< version >${logbook.version}</ version >
< classifier >javax</ classifier >
</ dependency >
Les modules/artefacts supplémentaires de Logbook partagent toujours le même numéro de version.
Alternativement, vous pouvez importer notre nomenclature ...
< 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 >
L'enregistreur de journal de bord doit être configuré au niveau de trace afin d'enregistrer les demandes et les réponses. Avec Spring Boot 2 (en utilisant Logback), cela peut être accompli en ajoutant la ligne suivante à votre application.properties
logging.level.org.zalando.logbook: TRACE
Toutes les intégrations nécessitent une instance de Logbook
qui contient toute la configuration et connecte toutes les pièces nécessaires ensemble. Vous pouvez soit en créer un en utilisant toutes les valeurs par défaut :
Logbook logbook = Logbook . create ();
ou créez une version personnalisée à l'aide du 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 ();
Le journal de bord avait autrefois une stratégie très rigide pour effectuer la journalisation des demandes/réponses :
Certaines de ces restrictions pourraient être atténuées grâce à des implémentations personnalisées HttpLogWriter
, mais elles n'ont jamais été idéales.
À partir de la version 2.0, Logbook est désormais livré avec un modèle de stratégie en son cœur. Assurez-vous de lire la documentation de l’interface Strategy
pour comprendre les implications.
Logbook est livré avec quelques stratégies intégrées :
BodyOnlyIfStatusAtLeastStrategy
StatusAtLeastStrategy
WithoutBodyStrategy
À partir de la version 3.4.0, Logbook est équipé d'une fonctionnalité appelée Attribute Extractor . Les attributs sont essentiellement une liste de paires clé/valeur qui peuvent être extraites d'une requête et/ou d'une réponse et enregistrées avec elles. L'idée est née du numéro 381, où une fonctionnalité était demandée pour extraire la revendication du sujet des jetons JWT dans l'en-tête d'autorisation.
L'interface AttributeExtractor
dispose de deux méthodes extract
: une qui peut extraire les attributs de la requête uniquement et une autre qui dispose à la fois de la requête et de la réponse. Les deux renvoient une instance de la classe HttpAttributes
, qui est essentiellement un Map<String, Object>
sophistiqué. Notez que puisque les valeurs de la carte sont de type Object
, elles doivent avoir une méthode toString()
appropriée pour qu'elles apparaissent dans les journaux de manière significative. Les formateurs de journaux peuvent également contourner ce problème en implémentant leur propre logique de sérialisation. Par exemple, le formateur de journaux intégré JsonHttpLogFormatter
utilise ObjectMapper
pour sérialiser les valeurs.
Voici un exemple :
final class OriginExtractor implements AttributeExtractor {
@ Override
public HttpAttributes extract ( final HttpRequest request ) {
return HttpAttributes . of ( "origin" , request . getOrigin ());
}
}
Le journal de bord doit ensuite être créé en enregistrant cet extracteur d'attribut :
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new OriginExtractor ())
. build ();
Cela entraînera des journaux de requêtes incluant quelque chose comme :
"attributes":{"origin":"LOCAL"}
Pour des exemples plus avancés, consultez les classes JwtFirstMatchingClaimExtractor
et JwtAllMatchingClaimsExtractor
. Le premier extrait la première revendication correspondant à une liste de noms de revendications du jeton JWT de la demande. Ce dernier extrait toutes les revendications correspondant à une liste de noms de revendications du jeton JWT de la requête.
Si vous devez incorporer plusieurs AttributeExtractor
, vous pouvez utiliser la classe CompositeAttributeExtractor
:
final List < AttributeExtractor > extractors = List . of (
extractor1 ,
extractor2 ,
extractor3
);
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new CompositeAttributeExtractor ( extractors ))
. build ();
Le journal de bord fonctionne en plusieurs phases différentes :
Chaque phase est représentée par une ou plusieurs interfaces pouvant être utilisées pour la personnalisation. Chaque phase a un défaut raisonnable.
La journalisation des messages HTTP et l'inclusion de leur corps est une tâche plutôt coûteuse, il est donc tout à fait logique de désactiver la journalisation pour certaines requêtes. Un cas d'utilisation courant consisterait à ignorer les demandes de vérification de l'état d'un équilibreur de charge ou toute demande adressée aux points de terminaison de gestion généralement émise par les développeurs.
Définir une condition est aussi simple que d'écrire un Predicate
spécial qui décide si une requête (et sa réponse correspondante) doit être enregistrée ou non. Vous pouvez également utiliser et combiner des prédicats prédéfinis :
Logbook logbook = Logbook . builder ()
. condition ( exclude (
requestTo ( "/health" ),
requestTo ( "/admin/**" ),
contentType ( "application/octet-stream" ),
header ( "X-Secret" , newHashSet ( "1" , "true" ):: contains )))
. build ();
Les modèles d'exclusion, par exemple /admin/**
, suivent vaguement le style de modèles de chemin d'Ant sans prendre en compte la chaîne de requête de l'URL.
L'objectif du filtrage est d'empêcher la journalisation de certaines parties sensibles des requêtes et réponses HTTP. Cela inclut généralement l'en-tête Authorization , mais peut également s'appliquer à certains paramètres de requête ou de formulaire en texte brut, par exemple password .
Logbook prend en charge différents types de filtres :
Taper | Fonctionne sur | S'applique à | Défaut |
---|---|---|---|
QueryFilter | Chaîne de requête | demande | access_token |
PathFilter | Chemin | demande | n / A |
HeaderFilter | En-tête (une seule paire clé-valeur) | les deux | Authorization |
BodyFilter | Type de contenu et corps | les deux | json : access_token et refresh_token formulaire : client_secret , password et refresh_token |
RequestFilter | HttpRequest | demande | Remplacez les corps binaires, multiparts et stream. |
ResponseFilter | HttpResponse | réponse | Remplacez les corps binaires, multiparts et stream. |
QueryFilter
, PathFilter
, HeaderFilter
et BodyFilter
sont de niveau relativement élevé et devraient couvrir tous les besoins dans environ 90 % des cas. Pour des configurations plus compliquées, il faut recourir aux variantes de bas niveau, c'est-à-dire respectivement RequestFilter
et ResponseFilter
(en conjonction avec ForwardingHttpRequest
/ ForwardingHttpResponse
).
Vous pouvez configurer des filtres comme ceci :
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 ();
Vous pouvez configurer autant de filtres que vous le souhaitez : ils s'exécuteront consécutivement.
Vous pouvez appliquer le filtrage de chemin JSON aux corps JSON. Voici quelques exemples :
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 ();
Jetez un œil à l'exemple suivant, avant et après l'application du filtrage :
{
"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 utilise un identifiant de corrélation pour corréler les demandes et les réponses. Cela permet des requêtes et des réponses liées à la correspondance qui seraient généralement situées à différents endroits dans le fichier journal.
Si l'implémentation par défaut de l'ID de corrélation est insuffisante pour votre cas d'utilisation, vous pouvez fournir une implémentation personnalisée :
Logbook logbook = Logbook . builder ()
. correlationId ( new CustomCorrelationId ())
. build ();
Le formatage définit essentiellement la manière dont les demandes et les réponses seront transformées en chaînes. Les formateurs ne précisent pas où les demandes et les réponses sont enregistrées : les rédacteurs font ce travail.
Logbook est livré avec deux formateurs par défaut différents : HTTP et JSON .
HTTP est le style de formatage par défaut, fourni par DefaultHttpLogFormatter
. Il est principalement conçu pour être utilisé pour le développement et le débogage locaux, et non pour une utilisation en production. C'est parce qu'il n'est pas aussi facilement lisible par machine que 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 est un style de formatage alternatif, fourni par JsonHttpLogFormatter
. Contrairement à HTTP, il est principalement conçu pour une utilisation en production : les analyseurs et les consommateurs de journaux peuvent facilement l'utiliser.
Nécessite la dépendance suivante :
< 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! "
}
Remarque : Les corps de type application/json
(et application/*+json
) seront intégrés dans l'arborescence JSON résultante. Autrement dit, un corps de réponse JSON ne sera pas échappé et représenté sous forme de chaîne :
{
"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! "
}
}
Le Common Log Format (CLF) est un format de fichier texte standardisé utilisé par les serveurs Web lors de la génération de fichiers journaux du serveur. Le format est supporté via le CommonsLogFormatSink
:
185.85.220.253 - - [02/Aug/2019:08:16:41 0000] "GET /search?q=zalando HTTP/1.1" 200 -
Le format de journal étendu (ELF) est un format de fichier texte standardisé, comme le format de journal commun (CLF), utilisé par les serveurs Web lors de la génération de fichiers journaux, mais les fichiers ELF fournissent plus d'informations et de flexibilité. Le format est pris en charge via ExtendedLogFormatSink
. Voir également le document W3C.
Champs par défaut :
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)
Exemple de sortie de journal par défaut :
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"
Les utilisateurs peuvent remplacer les champs par défaut par leurs champs personnalisés via le constructeur de ExtendedLogFormatSink
:
new ExtendedLogFormatSink ( new DefaultHttpLogWriter (), "date time cs(Custom-Request-Header) sc(Custom-Response-Header)" )
Pour les champs d'en-tête Http : cs(Any-Header)
et sc(Any-Header)
, les utilisateurs peuvent spécifier les en-têtes qu'ils souhaitent extraire de la requête.
Les autres champs pris en charge sont répertoriés dans la valeur de ExtendedLogFormatSink.Field
, qui peut être placée dans l'expression de champ personnalisé.
cURL est un style de formatage alternatif, fourni par CurlHttpLogFormatter
qui rendra les requêtes sous forme de commandes cURL
exécutables. Contrairement à JSON, il est principalement conçu pour les humains.
curl -v -X GET ' http://localhost/test ' -H ' Accept: application/json '
Consultez HTTP ou fournissez votre propre solution de secours pour les réponses :
new CurlHttpLogFormatter ( new JsonHttpLogFormatter ());
Splunk est un style de formatage alternatif, fourni par SplunkHttpLogFormatter
qui restituera les requêtes et les réponses sous forme de paires clé-valeur.
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!
L'écriture définit l'endroit où les demandes et les réponses formatées sont écrites. Logbook est livré avec trois implémentations : Logger, Stream et Chunking.
Par défaut, les demandes et les réponses sont enregistrées avec un enregistreur slf4j qui utilise la catégorie org.zalando.logbook.Logbook
et la trace
du niveau de journalisation. Cela peut être personnalisé :
Logbook logbook = Logbook . builder ()
. sink ( new DefaultSink (
new DefaultHttpLogFormatter (),
new DefaultHttpLogWriter ()
))
. build ();
Une implémentation alternative consiste à enregistrer les requêtes et les réponses dans un PrintStream
, par exemple System.out
ou System.err
. Il s’agit généralement d’un mauvais choix pour une mise en production, mais cela peut parfois s’avérer utile pour le développement local et/ou l’investigation à court terme.
Logbook logbook = Logbook . builder ()
. sink ( new DefaultSink (
new DefaultHttpLogFormatter (),
new StreamHttpLogWriter ( System . err )
))
. build ();
Le ChunkingSink
divisera les messages longs en morceaux plus petits et les écrira individuellement tout en les déléguant à un autre récepteur :
Logbook logbook = Logbook . builder ()
. sink ( new ChunkingSink ( sink , 1000 ))
. build ();
La combinaison de HttpLogFormatter
et HttpLogWriter
convient bien à la plupart des cas d'utilisation, mais elle présente des limites. L'implémentation directe de l'interface Sink
permet des cas d'utilisation plus sophistiqués, par exemple l'écriture de requêtes/réponses dans un stockage persistant structuré comme une base de données.
Plusieurs éviers peuvent être combinés en un seul à l'aide du CompositeSink
.
Vous devrez enregistrer le LogbookFilter
en tant que Filter
dans votre chaîne de filtres — soit dans votre fichier web.xml
(veuillez noter que l'approche XML utilisera toutes les valeurs par défaut et n'est pas configurable) :
< 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 >
ou par programmation, via le ServletContext
:
context . addFilter ( "LogbookFilter" , new LogbookFilter ( logbook ))
. addMappingForUrlPatterns ( EnumSet . of ( REQUEST , ASYNC ), true , "/*" );
Attention : Le dispatch ERROR
n'est pas supporté. Il est fortement conseillé de produire des réponses d'erreur dans le cadre de l'envoi REQUEST
ou ASNYC
.
Le LogbookFilter
traitera, par défaut, les requêtes avec un corps application/x-www-form-urlencoded
non différent de toute autre requête, c'est-à-dire que vous verrez le corps de la requête dans les journaux. L'inconvénient de cette approche est que vous ne pourrez utiliser aucune des méthodes HttpServletRequest.getParameter*(..)
. Voir le numéro 94 pour plus de détails.
Depuis Logbook 1.5.0, vous pouvez désormais spécifier l'une des trois stratégies qui définissent la manière dont Logbook gère cette situation en utilisant la propriété système logbook.servlet.form-request
:
Valeur | Avantages | Inconvénients |
---|---|---|
body (par défaut) | Le corps est enregistré | Le code en aval ne peut pas utiliser getParameter*() |
parameter | Le corps est enregistré (mais il est reconstruit à partir des paramètres) | Le code en aval ne peut pas utiliser getInputStream() |
off | Le code en aval peut décider d'utiliser getInputStream() ou getParameter*() | Le corps n'est pas enregistré |
Les applications sécurisées nécessitent généralement une configuration légèrement différente. Vous devez généralement éviter d'enregistrer les requêtes non autorisées, en particulier le corps, car cela permet aux attaquants d'inonder rapidement votre fichier journal et, par conséquent, votre précieux espace disque. En supposant que votre application gère l'autorisation dans un autre filtre, vous avez deux choix :
Vous pouvez facilement obtenir l'ancienne configuration en plaçant le LogbookFilter
après votre filtre de sécurité. Ce dernier est un peu plus sophistiqué. Vous aurez besoin de deux instances LogbookFilter
: une avant votre filtre de sécurité et une après :
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 , "/*" );
Le premier filtre du journal enregistrera uniquement les demandes non autorisées. Le deuxième filtre enregistrera les demandes autorisées, comme toujours.
Le module logbook-httpclient
contient à la fois un HttpRequestInterceptor
et un HttpResponseInterceptor
à utiliser avec le HttpClient
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. addInterceptorFirst ( new LogbookHttpResponseInterceptor ())
. build ();
Étant donné que LogbookHttpResponseInterceptor
est incompatible avec HttpAsyncClient
il existe une autre façon de consigner les réponses :
CloseableHttpAsyncClient client = HttpAsyncClientBuilder . create ()
. addInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. build ();
// and then wrap your response consumer
client . execute ( producer , new LogbookHttpAsyncResponseConsumer <>( consumer ), callback )
Le module logbook-httpclient5
contient un ExecHandler
à utiliser avec le HttpClient
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addExecInterceptorFirst ( "Logbook" , new LogbookHttpExecHandler ( logbook ))
. build ();
Le gestionnaire doit être ajouté en premier, de sorte qu'une compression soit effectuée après la journalisation et une décompression avant la journalisation.
Pour éviter un changement radical, il existe également un HttpRequestInterceptor
et un HttpResponseInterceptor
à utiliser avec le HttpClient
, qui fonctionne correctement tant que la compression (ou d'autres ExecHandlers) n'est pas utilisée :
CloseableHttpClient client = HttpClientBuilder . create ()
. addRequestInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. addResponseInterceptorFirst ( new LogbookHttpResponseInterceptor ())
. build ();
Étant donné que LogbookHttpResponseInterceptor
est incompatible avec HttpAsyncClient
il existe une autre façon de consigner les réponses :
CloseableHttpAsyncClient client = HttpAsyncClientBuilder . create ()
. addRequestInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. build ();
// and then wrap your response consumer
client . execute ( producer , new LogbookHttpAsyncResponseConsumer <>( consumer ), callback )
Note
Prise en charge de JAX-RS 2.x
La prise en charge de JAX-RS 2.x (héritée) a été abandonnée dans Logbook 3.0 à 3.6.
Depuis Logbook 3.7, la prise en charge de JAX-RS 2.x est de retour.
Cependant, vous devez ajouter le classificateur javax
pour utiliser le module Logbook approprié :
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-jaxrs</ artifactId >
< version >${logbook.version}</ version >
< classifier >javax</ classifier >
</ dependency >
Vous devez également vous assurer que les dépendances suivantes se trouvent sur votre chemin de classe. Par défaut, logbook-jaxrs
importe jersey-client 3.x
, qui n'est pas compatible avec JAX-RS 2.x :
Le module logbook-jaxrs
contient :
Un LogbookClientFilter
à utiliser pour les applications effectuant des requêtes HTTP
client . register ( new LogbookClientFilter ( logbook ));
Un LogbookServerFilter
à utiliser avec les serveurs HTTP
resourceConfig . register ( new LogbookServerFilter ( logbook ));
Le module logbook-jdkserver
prend en charge le serveur HTTP JDK et contient :
Un LogbookFilter
à utiliser avec le serveur intégré
httpServer . createContext ( path , handler ). getFilters (). add ( new LogbookFilter ( logbook ))
Le module logbook-netty
contient :
Un LogbookClientHandler
à utiliser avec un HttpClient
:
HttpClient httpClient =
HttpClient . create ()
. doOnConnected (
( connection -> connection . addHandlerLast ( new LogbookClientHandler ( logbook )))
);
Un LogbookServerHandler
à utiliser avec un HttpServer
:
HttpServer httpServer =
HttpServer . create ()
. doOnConnection (
connection -> connection . addHandlerLast ( new LogbookServerHandler ( logbook ))
);
Les utilisateurs de Spring WebFlux peuvent choisir l'une des options suivantes :
NettyWebServer
(en passant un HttpServer
)NettyServerCustomizer
personnaliséReactorClientHttpConnector
(en passant un HttpClient
)WebClientCustomizer
personnalisélogbook-spring-webflux
Les utilisateurs de Micronaut peuvent suivre la documentation officielle sur la façon d'intégrer Logbook à Micronaut.
Le module logbook-okhttp2
contient un Interceptor
à utiliser avec la version 2.x de OkHttpClient
:
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
Si vous attendez des réponses compressées au format gzip, vous devez également enregistrer notre GzipInterceptor
. La prise en charge transparente de gzip intégrée à OkHttp s'exécutera après tout intercepteur de réseau qui force le journal à enregistrer les réponses binaires compressées.
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
client . networkInterceptors (). add ( new GzipInterceptor ());
Le module logbook-okhttp
contient un Interceptor
à utiliser avec la version 3.x de OkHttpClient
:
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. build ();
Si vous attendez des réponses compressées au format gzip, vous devez également enregistrer notre GzipInterceptor
. La prise en charge transparente de gzip intégrée à OkHttp s'exécutera après tout intercepteur de réseau qui force le journal à enregistrer les réponses binaires compressées.
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. addNetworkInterceptor ( new GzipInterceptor ())
. build ();
Le module logbook-ktor-client
contient :
Un LogbookClient
à utiliser avec un HttpClient
:
private val client = HttpClient ( CIO ) {
install( LogbookClient ) {
logbook = logbook
}
}
Le module logbook-ktor-server
contient :
Un LogbookServer
à utiliser avec une Application
:
private val server = embeddedServer( CIO ) {
install( LogbookServer ) {
logbook = logbook
}
}
Alternativement, vous pouvez utiliser logbook-ktor
, qui fournit à la fois les modules logbook-ktor-client
et logbook-ktor-server
.
Le module logbook-spring
contient un ClientHttpRequestInterceptor
à utiliser avec RestTemplate
:
LogbookClientHttpRequestInterceptor interceptor = new LogbookClientHttpRequestInterceptor ( logbook );
RestTemplate restTemplate = new RestTemplate ();
restTemplate . getInterceptors (). add ( interceptor );
Logbook est livré avec une configuration automatique pratique pour les utilisateurs de Spring Boot. Il configure automatiquement toutes les parties suivantes avec des valeurs par défaut raisonnables :
Au lieu de déclarer une dépendance à logbook-core
déclarez-en une au Spring Boot Starter :
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-spring-boot-starter</ artifactId >
< version >${logbook.version}</ version >
</ dependency >
Chaque bean peut être remplacé et personnalisé si nécessaire, par exemple comme ceci :
@ Bean
public BodyFilter bodyFilter () {
return merge (
defaultValue (),
replaceJsonStringProperty ( singleton ( "secret" ), "XXX" ));
}
Veuillez vous référer à LogbookAutoConfiguration
ou au tableau suivant pour voir une liste des points d'intégration possibles :
Taper | Nom | Défaut |
---|---|---|
FilterRegistrationBean | secureLogbookFilter | Basé sur LogbookFilter |
FilterRegistrationBean | logbookFilter | Basé sur LogbookFilter |
Logbook | Basé sur la condition, les filtres, le formateur et l'écrivain | |
Predicate<HttpRequest> | requestCondition | Pas de filtre ; est ensuite combiné avec logbook.exclude et logbook.exclude |
HeaderFilter | Basé sur logbook.obfuscate.headers | |
PathFilter | Basé sur logbook.obfuscate.paths | |
QueryFilter | Basé sur logbook.obfuscate.parameters | |
BodyFilter | BodyFilters.defaultValue() , voir filtrage | |
RequestFilter | RequestFilters.defaultValue() , voir filtrage | |
ResponseFilter | ResponseFilters.defaultValue() , voir filtrage | |
Strategy | DefaultStrategy | |
AttributeExtractor | NoOpAttributeExtractor | |
Sink | DefaultSink | |
HttpLogFormatter | JsonHttpLogFormatter | |
HttpLogWriter | DefaultHttpLogWriter |
Plusieurs filtres sont fusionnés en un seul.
logbook-spring
Certaines classes de logbook-spring
sont incluses dans la configuration automatique.
Vous pouvez câbler automatiquement LogbookClientHttpRequestInterceptor
avec un code tel que :
private final RestTemplate restTemplate ;
MyClient ( RestTemplateBuilder builder , LogbookClientHttpRequestInterceptor interceptor ){
this . restTemplate = builder
. additionalInterceptors ( interceptor )
. build ();
}
Les tableaux suivants montrent la configuration disponible (triés par ordre alphabétique) :
Configuration | Description | Défaut |
---|---|---|
logbook.attribute-extractors | Liste des AttributeExtractors, y compris des configurations telles que type (actuellement JwtFirstMatchingClaimExtractor ou JwtAllMatchingClaimsExtractor ), claim-names et claim-key . | [] |
logbook.filter.enabled | Activer le LogbookFilter | true |
logbook.filter.form-request-mode | Détermine la façon dont les demandes de formulaire sont traitées | body |
logbook.filters.body.default-enabled | Active/désactive les filtres de corps par défaut collectés par java.util.ServiceLoader | true |
logbook.format.style | Style de formatage ( http , json , curl ou splunk ) | json |
logbook.httpclient.decompress-response | Active/désactive un processus de décompression supplémentaire pour HttpClient avec un corps codé en gzip (à des fins de journalisation uniquement). Cela signifie une décompression supplémentaire et un impact possible sur les performances. | false (désactivé) |
logbook.minimum-status | Statut minimum pour activer la journalisation ( status-at-least et body-only-if-status-at-least ) | 400 |
logbook.obfuscate.headers | Liste des noms d'en-tête nécessitant un obscurcissement | [Authorization] |
logbook.obfuscate.json-body-fields | Liste des champs de corps JSON à masquer | [] |
logbook.obfuscate.parameters | Liste des noms de paramètres nécessitant un obscurcissement | [access_token] |
logbook.obfuscate.paths | Liste des chemins qui nécessitent un obscurcissement. Vérifiez Filtrage pour la syntaxe. | [] |
logbook.obfuscate.replacement | Une valeur à utiliser au lieu d'une valeur obscurcie | XXX |
logbook.predicate.include | Inclure uniquement certains chemins et méthodes (si définis) | [] |
logbook.predicate.exclude | Exclure certains chemins et méthodes (remplace logbook.predicate.include ) | [] |
logbook.secure-filter.enabled | Activer le SecureLogbookFilter | true |
logbook.strategy | Stratégie ( default , status-at-least , body-only-if-status-at-least , without-body ) | default |
logbook.write.chunk-size | Divise les lignes de journal en petits morceaux de taille allant jusqu'à chunk-size . | 0 (désactivé) |
logbook.write.max-body-size | Tronque le corps jusqu'à max-body-size caractères et ajoute ... .logbook.write.max-body-size | -1 (désactivé) |
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" ]
Pour la configuration de base de Logback
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
configurer le journal de bord avec un LogstashLogbackSink
HttpLogFormatter formatter = new JsonHttpLogFormatter();
LogstashLogbackSink sink = new LogstashLogbackSink(formatter);
pour des sorties comme
{
"@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
}
}
Vous avez la possibilité de personnaliser le niveau de journalisation par défaut en initialisant LogstashLogbackSink
avec un niveau spécifique. Par exemple:
LogstashLogbackSink sink = new LogstashLogbackSink(formatter, Level.INFO);
getWriter
et/ou getParameter*()
. Voir Servlet pour plus de détails.ERROR
. Il est fortement déconseillé de l'utiliser pour produire des réponses d'erreur. Si vous avez des questions, des préoccupations, des rapports de bogues, etc., veuillez signaler un problème dans le suivi des problèmes de ce référentiel.
Pour contribuer, faites simplement une pull request et ajoutez une brève description (1 à 2 phrases) de votre ajout ou modification. Pour plus de détails, consultez les directives de contribution.
Grand Turk, une réplique d'une frégate à trois mâts de 6e rang de l'époque de Nelson - journal de bord et cartes de JoJan est sous licence Creative Commons (Attribution-Partage dans les mêmes conditions 3.0 Unported).