Logbuch- Substantiv, /lɑɡ bʊk/: Ein Buch, in dem Messungen aus dem Schiffstagebuch zusammen mit anderen wichtigen Details der Reise aufgezeichnet werden.
Logbook ist eine erweiterbare Java-Bibliothek, die eine vollständige Anforderungs- und Antwortprotokollierung für verschiedene client- und serverseitige Technologien ermöglicht. Es erfüllt einen besonderen Bedarf, indem es a) Webanwendungsentwicklern ermöglicht, den gesamten HTTP-Verkehr zu protokollieren, den eine Anwendung empfängt oder sendet, b) auf eine Weise, die es einfach macht, ihn später beizubehalten und zu analysieren. Dies kann für die herkömmliche Protokollanalyse, die Erfüllung von Prüfanforderungen oder die Untersuchung einzelner historischer Verkehrsprobleme nützlich sein.
Logbook ist für die meisten gängigen Setups sofort einsatzbereit. Auch für ungewöhnliche Anwendungen und Technologien sollte es einfach sein, die notwendigen Schnittstellen zur Anbindung einer Bibliothek/Framework etc. zu implementieren. dazu.
Fügen Sie Ihrem Projekt die folgende Abhängigkeit hinzu:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-core</ artifactId >
< version >${logbook.version}</ version >
</ dependency >
Für die Abwärtskompatibilität mit Spring 5/Spring Boot 2 fügen Sie bitte den folgenden Import hinzu:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-servlet</ artifactId >
< version >${logbook.version}</ version >
< classifier >javax</ classifier >
</ dependency >
Zusätzliche Module/Artefakte von Logbook haben immer die gleiche Versionsnummer.
Alternativ können Sie auch unsere Stückliste importieren ...
< 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 >
Der Logbuch-Logger muss auf Trace-Ebene konfiguriert sein, um die Anfragen und Antworten zu protokollieren. Mit Spring Boot 2 (mit Logback) kann dies erreicht werden, indem Sie die folgende Zeile zu Ihren application.properties
hinzufügen
logging.level.org.zalando.logbook: TRACE
Für alle Integrationen ist eine Instanz von Logbook
erforderlich, die die gesamte Konfiguration enthält und alle erforderlichen Teile miteinander verbindet. Sie können entweder eines mit allen Standardeinstellungen erstellen:
Logbook logbook = Logbook . create ();
oder erstellen Sie eine angepasste Version mit dem 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 ();
Logbook hatte früher eine sehr strenge Strategie für die Protokollierung von Anfragen/Antworten:
Einige dieser Einschränkungen könnten mit benutzerdefinierten HttpLogWriter
-Implementierungen gemildert werden, aber sie waren nie ideal.
Ab Version 2.0 verfügt Logbook nun über ein Strategiemuster im Kern. Lesen Sie unbedingt die Dokumentation der Strategy
Schnittstelle, um die Auswirkungen zu verstehen.
Logbook verfügt über einige integrierte Strategien:
BodyOnlyIfStatusAtLeastStrategy
StatusAtLeastStrategy
WithoutBodyStrategy
Ab Version 3.4.0 ist Logbook mit einer Funktion namens Attribute Extractor ausgestattet. Attribute sind im Grunde eine Liste von Schlüssel/Wert-Paaren, die aus einer Anfrage und/oder Antwort extrahiert und mit ihnen protokolliert werden können. Die Idee entstand aus Issue 381, wo eine Funktion zum Extrahieren des Betreffanspruchs aus JWT-Tokens im Autorisierungsheader angefordert wurde.
Die AttributeExtractor
Schnittstelle verfügt über zwei extract
: Eine, die Attribute nur aus der Anfrage extrahieren kann, und eine, die sowohl Anfrage als auch Antwort zur Verfügung hat. Beide geben eine Instanz der HttpAttributes
-Klasse zurück, die im Grunde eine schicke Map<String, Object>
ist. Beachten Sie, dass die Kartenwerte, da sie vom Typ Object
sind, über eine geeignete toString()
Methode verfügen sollten, damit sie sinnvoll in den Protokollen angezeigt werden. Alternativ können Protokollformatierer dies umgehen, indem sie ihre eigene Serialisierungslogik implementieren. Beispielsweise verwendet der integrierte Protokollformatierer JsonHttpLogFormatter
ObjectMapper
um die Werte zu serialisieren.
Hier ist ein Beispiel:
final class OriginExtractor implements AttributeExtractor {
@ Override
public HttpAttributes extract ( final HttpRequest request ) {
return HttpAttributes . of ( "origin" , request . getOrigin ());
}
}
Anschließend muss ein Logbuch erstellt werden, indem dieser Attributextraktor registriert wird:
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new OriginExtractor ())
. build ();
Dies führt dazu, dass Anforderungsprotokolle Folgendes enthalten:
"attributes":{"origin":"LOCAL"}
Weitere Beispiele für Fortgeschrittene finden Sie in den Klassen JwtFirstMatchingClaimExtractor
und JwtAllMatchingClaimsExtractor
. Ersteres extrahiert den ersten Anspruch, der mit einer Liste von Anspruchsnamen aus dem Anforderungs-JWT-Token übereinstimmt. Letzterer extrahiert alle Ansprüche, die einer Liste von Anspruchsnamen entsprechen, aus dem Anforderungs-JWT-Token.
Wenn Sie mehrere AttributeExtractor
s einbinden müssen, können Sie die Klasse CompositeAttributeExtractor
verwenden:
final List < AttributeExtractor > extractors = List . of (
extractor1 ,
extractor2 ,
extractor3
);
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new CompositeAttributeExtractor ( extractors ))
. build ();
Logbook funktioniert in mehreren verschiedenen Phasen:
Jede Phase wird durch eine oder mehrere Schnittstellen repräsentiert, die zur Anpassung verwendet werden können. Jede Phase hat eine sinnvolle Vorgabe.
Das Protokollieren von HTTP-Nachrichten und das Einbeziehen ihres Textkörpers ist eine ziemlich kostspielige Aufgabe, daher ist es sehr sinnvoll, die Protokollierung für bestimmte Anfragen zu deaktivieren. Ein häufiger Anwendungsfall wäre das Ignorieren von Health-Check- Anfragen eines Load Balancers oder jeglicher Anfragen an Verwaltungsendpunkte, die normalerweise von Entwicklern gestellt werden.
Das Definieren einer Bedingung ist so einfach wie das Schreiben eines speziellen Predicate
, das entscheidet, ob eine Anfrage (und die entsprechende Antwort) protokolliert werden soll oder nicht. Alternativ können Sie vordefinierte Prädikate verwenden und kombinieren:
Logbook logbook = Logbook . builder ()
. condition ( exclude (
requestTo ( "/health" ),
requestTo ( "/admin/**" ),
contentType ( "application/octet-stream" ),
header ( "X-Secret" , newHashSet ( "1" , "true" ):: contains )))
. build ();
Ausschlussmuster, z. B. /admin/**
, folgen grob dem Ant-Stil der Pfadmuster, ohne die Abfragezeichenfolge der URL zu berücksichtigen.
Das Ziel der Filterung besteht darin, die Protokollierung bestimmter sensibler Teile von HTTP-Anfragen und -Antworten zu verhindern. Dies umfasst normalerweise den Authorization- Header, kann aber auch für bestimmte Klartext-Abfrage- oder Formularparameter gelten – z. B. „ password“ .
Logbook unterstützt verschiedene Arten von Filtern:
Typ | Funktioniert weiter | Gilt für | Standard |
---|---|---|---|
QueryFilter | Abfragezeichenfolge | Anfrage | access_token |
PathFilter | Weg | Anfrage | n / A |
HeaderFilter | Header (einzelnes Schlüssel-Wert-Paar) | beide | Authorization |
BodyFilter | Inhaltstyp und Text | beide | json: access_token refresh_token Formular: client_secret , password und refresh_token |
RequestFilter | HttpRequest | Anfrage | Ersetzen Sie binäre, mehrteilige und Stream-Körper. |
ResponseFilter | HttpResponse | Antwort | Ersetzen Sie binäre, mehrteilige und Stream-Körper. |
QueryFilter
, PathFilter
, HeaderFilter
und BodyFilter
sind relativ hochrangig und sollten in etwa 90 % aller Fälle alle Anforderungen abdecken. Für kompliziertere Setups sollte man auf die Low-Level-Varianten, also RequestFilter
bzw. ResponseFilter
(in Verbindung mit ForwardingHttpRequest
/ ForwardingHttpResponse
), zurückgreifen.
Sie können Filter wie folgt konfigurieren:
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 ();
Sie können beliebig viele Filter konfigurieren – sie werden nacheinander ausgeführt.
Sie können die JSON-Pfadfilterung auf JSON-Körper anwenden. Hier einige Beispiele:
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 ();
Sehen Sie sich das folgende Beispiel vor und nach der Filterung an:
{
"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 verwendet eine Korrelations-ID, um Anfragen und Antworten zu korrelieren. Dies ermöglicht spielbezogene Anfragen und Antworten, die sich normalerweise an verschiedenen Stellen in der Protokolldatei befinden würden.
Wenn die Standardimplementierung der Korrelations-ID für Ihren Anwendungsfall nicht ausreicht, können Sie eine benutzerdefinierte Implementierung bereitstellen:
Logbook logbook = Logbook . builder ()
. correlationId ( new CustomCorrelationId ())
. build ();
Die Formatierung definiert grundsätzlich, wie Anfragen und Antworten in Zeichenfolgen umgewandelt werden. Formatierer geben nicht an, wo Anfragen und Antworten protokolliert werden – diese Aufgabe übernehmen Autoren.
Logbook wird mit zwei verschiedenen Standardformatierern geliefert: HTTP und JSON .
HTTP ist der Standardformatierungsstil, der von DefaultHttpLogFormatter
bereitgestellt wird. Es ist in erster Linie für die lokale Entwicklung und das Debuggen konzipiert, nicht für den Produktionseinsatz. Dies liegt daran, dass es nicht so leicht maschinenlesbar ist wie 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 ist ein alternativer Formatierungsstil, der vom JsonHttpLogFormatter
bereitgestellt wird. Im Gegensatz zu HTTP ist es in erster Linie für den Produktionsgebrauch konzipiert – Parser und Protokollkonsumenten können es problemlos nutzen.
Erfordert die folgende Abhängigkeit:
< 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! "
}
Hinweis: Körper vom Typ application/json
(und application/*+json
) werden in den resultierenden JSON-Baum eingebunden . Das heißt, ein JSON-Antworttext wird nicht maskiert und als Zeichenfolge dargestellt:
{
"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! "
}
}
Das Common Log Format (CLF) ist ein standardisiertes Textdateiformat, das von Webservern beim Generieren von Serverprotokolldateien verwendet wird. Das Format wird über CommonsLogFormatSink
unterstützt:
185.85.220.253 - - [02/Aug/2019:08:16:41 0000] "GET /search?q=zalando HTTP/1.1" 200 -
Das Extended Log Format (ELF) ist ein standardisiertes Textdateiformat wie das Common Log Format (CLF), das von Webservern beim Generieren von Protokolldateien verwendet wird. ELF-Dateien bieten jedoch mehr Informationen und Flexibilität. Das Format wird über ExtendedLogFormatSink
unterstützt. Siehe auch W3C-Dokument.
Standardfelder:
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)
Beispiel für die Standardprotokollausgabe:
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"
Benutzer können Standardfelder mit ihren benutzerdefinierten Feldern über den Konstruktor von ExtendedLogFormatSink
überschreiben:
new ExtendedLogFormatSink ( new DefaultHttpLogWriter (), "date time cs(Custom-Request-Header) sc(Custom-Response-Header)" )
Für HTTP-Headerfelder: cs(Any-Header)
und sc(Any-Header)
können Benutzer beliebige Header angeben, die sie aus der Anfrage extrahieren möchten.
Andere unterstützte Felder sind im Wert von ExtendedLogFormatSink.Field
aufgeführt, der in den benutzerdefinierten Feldausdruck eingefügt werden kann.
cURL ist ein alternativer Formatierungsstil, der vom CurlHttpLogFormatter
bereitgestellt wird und Anfragen als ausführbare cURL
-Befehle rendert. Im Gegensatz zu JSON ist es in erster Linie für Menschen konzipiert.
curl -v -X GET ' http://localhost/test ' -H ' Accept: application/json '
Siehe HTTP oder stellen Sie einen eigenen Fallback für Antworten bereit:
new CurlHttpLogFormatter ( new JsonHttpLogFormatter ());
Splunk ist ein alternativer Formatierungsstil, der vom SplunkHttpLogFormatter
bereitgestellt wird und Anfragen und Antworten als Schlüssel-Wert-Paare darstellt.
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!
Schreiben definiert, wohin formatierte Anfragen und Antworten geschrieben werden. Logbook verfügt über drei Implementierungen: Logger, Stream und Chunking.
Standardmäßig werden Anfragen und Antworten mit einem slf4j -Logger protokolliert, der die Kategorie org.zalando.logbook.Logbook
und die Protokollebene „ trace
verwendet. Dies kann individuell angepasst werden:
Logbook logbook = Logbook . builder ()
. sink ( new DefaultSink (
new DefaultHttpLogFormatter (),
new DefaultHttpLogWriter ()
))
. build ();
Eine alternative Implementierung besteht darin, Anfragen und Antworten an einen PrintStream
zu protokollieren, z. B. System.out
oder System.err
. Dies ist normalerweise eine schlechte Wahl für den Einsatz in der Produktion, kann aber manchmal für die kurzfristige lokale Entwicklung und/oder Untersuchung nützlich sein.
Logbook logbook = Logbook . builder ()
. sink ( new DefaultSink (
new DefaultHttpLogFormatter (),
new StreamHttpLogWriter ( System . err )
))
. build ();
Der ChunkingSink
teilt lange Nachrichten in kleinere Blöcke auf und schreibt sie einzeln, während er sie an eine andere Senke delegiert:
Logbook logbook = Logbook . builder ()
. sink ( new ChunkingSink ( sink , 1000 ))
. build ();
Die Kombination aus HttpLogFormatter
und HttpLogWriter
eignet sich gut für die meisten Anwendungsfälle, weist jedoch Einschränkungen auf. Die direkte Implementierung der Sink
Schnittstelle ermöglicht anspruchsvollere Anwendungsfälle, z. B. das Schreiben von Anfragen/Antworten in einen strukturierten persistenten Speicher wie eine Datenbank.
Mit der CompositeSink
können mehrere Spülen zu einer zusammengefasst werden.
Sie müssen den LogbookFilter
als Filter
in Ihrer Filterkette registrieren – entweder in Ihrer web.xml
-Datei (bitte beachten Sie, dass der XML-Ansatz alle Standardeinstellungen verwendet und nicht konfigurierbar ist):
< 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 >
oder programmgesteuert über den ServletContext
:
context . addFilter ( "LogbookFilter" , new LogbookFilter ( logbook ))
. addMappingForUrlPatterns ( EnumSet . of ( REQUEST , ASYNC ), true , "/*" );
Achtung : Der ERROR
Versand wird nicht unterstützt. Es wird dringend empfohlen, Fehlerantworten im REQUEST
oder ASNYC
Versand zu erstellen.
Der LogbookFilter
behandelt Anfragen standardmäßig mit einem application/x-www-form-urlencoded
Textkörper, der sich nicht von anderen Anfragen unterscheidet, d. h. Sie sehen den Anfragetext in den Protokollen. Der Nachteil dieses Ansatzes besteht darin, dass Sie keine der HttpServletRequest.getParameter*(..)
-Methoden verwenden können. Weitere Einzelheiten finden Sie in Ausgabe Nr. 94.
Ab Logbook 1.5.0 können Sie nun eine von drei Strategien angeben, die definieren, wie Logbook mit dieser Situation umgeht, indem Sie die Systemeigenschaft logbook.servlet.form-request
verwenden:
Wert | Vorteile | Nachteile |
---|---|---|
body (Standard) | Körper wird protokolliert | Downstream-Code kann getParameter*() nicht verwenden |
parameter | Der Körper wird protokolliert (aber er wird aus Parametern rekonstruiert) | Downstream-Code kann getInputStream() nicht verwenden |
off | Downstream-Code kann entscheiden, ob getInputStream() oder getParameter*() verwendet werden soll. | Der Körper wird nicht protokolliert |
Sichere Anwendungen erfordern normalerweise eine etwas andere Einrichtung. Generell sollten Sie es vermeiden, nicht autorisierte Anfragen zu protokollieren, insbesondere den Textkörper, da Angreifer dadurch schnell die Möglichkeit haben, Ihre Protokolldatei – und damit Ihren wertvollen Speicherplatz – zu überfluten. Vorausgesetzt, dass Ihre Anwendung die Autorisierung innerhalb eines anderen Filters verarbeitet, haben Sie zwei Möglichkeiten:
Sie können das vorherige Setup einfach erreichen, indem Sie den LogbookFilter
nach Ihrem Sicherheitsfilter platzieren. Letzteres ist etwas anspruchsvoller. Sie benötigen zwei LogbookFilter
Instanzen – eine vor Ihrem Sicherheitsfilter und eine danach:
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 , "/*" );
Der erste Logbuchfilter protokolliert nur nicht autorisierte Anfragen. Der zweite Filter protokolliert wie immer autorisierte Anfragen.
Das logbook-httpclient
-Modul enthält sowohl einen HttpRequestInterceptor
als auch einen HttpResponseInterceptor
zur Verwendung mit dem HttpClient
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. addInterceptorFirst ( new LogbookHttpResponseInterceptor ())
. build ();
Da der LogbookHttpResponseInterceptor
nicht mit dem HttpAsyncClient
kompatibel ist, gibt es eine andere Möglichkeit, Antworten zu protokollieren:
CloseableHttpAsyncClient client = HttpAsyncClientBuilder . create ()
. addInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. build ();
// and then wrap your response consumer
client . execute ( producer , new LogbookHttpAsyncResponseConsumer <>( consumer ), callback )
Das logbook-httpclient5
-Modul enthält einen ExecHandler
zur Verwendung mit dem HttpClient
:
CloseableHttpClient client = HttpClientBuilder . create ()
. addExecInterceptorFirst ( "Logbook" , new LogbookHttpExecHandler ( logbook ))
. build ();
Der Handler sollte zuerst hinzugefügt werden, sodass nach der Protokollierung eine Komprimierung und vor der Protokollierung eine Dekomprimierung durchgeführt wird.
Um eine bahnbrechende Änderung zu vermeiden, gibt es auch einen HttpRequestInterceptor
und einen HttpResponseInterceptor
die mit HttpClient
verwendet werden können, was einwandfrei funktioniert, solange keine Komprimierung (oder andere ExecHandler) verwendet wird:
CloseableHttpClient client = HttpClientBuilder . create ()
. addRequestInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. addResponseInterceptorFirst ( new LogbookHttpResponseInterceptor ())
. build ();
Da der LogbookHttpResponseInterceptor
nicht mit dem HttpAsyncClient
kompatibel ist, gibt es eine andere Möglichkeit, Antworten zu protokollieren:
CloseableHttpAsyncClient client = HttpAsyncClientBuilder . create ()
. addRequestInterceptorFirst ( new LogbookHttpRequestInterceptor ( logbook ))
. build ();
// and then wrap your response consumer
client . execute ( producer , new LogbookHttpAsyncResponseConsumer <>( consumer ), callback )
Notiz
Unterstützung für JAX-RS 2.x
Die Unterstützung für JAX-RS 2.x (Legacy) wurde in Logbook 3.0 bis 3.6 eingestellt.
Ab Logbook 3.7 ist die JAX-RS 2.x-Unterstützung wieder verfügbar.
Sie müssen jedoch den javax
Klassifizierer hinzufügen, um das richtige Logbook-Modul zu verwenden:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-jaxrs</ artifactId >
< version >${logbook.version}</ version >
< classifier >javax</ classifier >
</ dependency >
Sie sollten außerdem sicherstellen, dass sich die folgenden Abhängigkeiten in Ihrem Klassenpfad befinden. Standardmäßig importiert logbook-jaxrs
jersey-client 3.x
, was nicht mit JAX-RS 2.x kompatibel ist:
Das logbook-jaxrs
Modul enthält:
Ein LogbookClientFilter
, der für Anwendungen verwendet wird, die HTTP-Anfragen stellen
client . register ( new LogbookClientFilter ( logbook ));
Ein LogbookServerFilter
zur Verwendung mit HTTP-Servern
resourceConfig . register ( new LogbookServerFilter ( logbook ));
Das Modul logbook-jdkserver
bietet Unterstützung für den JDK-HTTP-Server und enthält:
Ein LogbookFilter
zur Verwendung mit dem integrierten Server
httpServer . createContext ( path , handler ). getFilters (). add ( new LogbookFilter ( logbook ))
Das logbook-netty
-Modul enthält:
Ein LogbookClientHandler
der mit einem HttpClient
verwendet werden soll:
HttpClient httpClient =
HttpClient . create ()
. doOnConnected (
( connection -> connection . addHandlerLast ( new LogbookClientHandler ( logbook )))
);
Ein LogbookServerHandler
zur Verwendung mit einem HttpServer
:
HttpServer httpServer =
HttpServer . create ()
. doOnConnection (
connection -> connection . addHandlerLast ( new LogbookServerHandler ( logbook ))
);
Benutzer von Spring WebFlux können eine der folgenden Optionen auswählen:
NettyWebServer
(übergeben Sie einen HttpServer
).NettyServerCustomizer
ReactorClientHttpConnector
(übergeben Sie einen HttpClient
).WebClientCustomizer
logbook-spring-webflux
Benutzer von Micronaut können den offiziellen Dokumenten zur Integration von Logbook in Micronaut folgen.
Das logbook-okhttp2
-Modul enthält einen Interceptor
zur Verwendung mit Version 2.x des OkHttpClient
:
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
Wenn Sie gzip-komprimierte Antworten erwarten, müssen Sie zusätzlich unseren GzipInterceptor
registrieren. Die in OkHttp integrierte transparente GZIP-Unterstützung wird nach jedem Netzwerk-Interceptor ausgeführt, der Logbook dazu zwingt, komprimierte Binärantworten zu protokollieren.
OkHttpClient client = new OkHttpClient ();
client . networkInterceptors (). add ( new LogbookInterceptor ( logbook ));
client . networkInterceptors (). add ( new GzipInterceptor ());
Das logbook-okhttp
-Modul enthält einen Interceptor
zur Verwendung mit Version 3.x des OkHttpClient
:
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. build ();
Wenn Sie gzip-komprimierte Antworten erwarten, müssen Sie zusätzlich unseren GzipInterceptor
registrieren. Die in OkHttp integrierte transparente GZIP-Unterstützung wird nach jedem Netzwerk-Interceptor ausgeführt, der Logbook dazu zwingt, komprimierte Binärantworten zu protokollieren.
OkHttpClient client = new OkHttpClient . Builder ()
. addNetworkInterceptor ( new LogbookInterceptor ( logbook ))
. addNetworkInterceptor ( new GzipInterceptor ())
. build ();
Das Modul logbook-ktor-client
enthält:
Ein LogbookClient
der mit einem HttpClient
verwendet werden soll:
private val client = HttpClient ( CIO ) {
install( LogbookClient ) {
logbook = logbook
}
}
Das Modul logbook-ktor-server
enthält:
Ein LogbookServer
zur Verwendung mit einer Application
:
private val server = embeddedServer( CIO ) {
install( LogbookServer ) {
logbook = logbook
}
}
Alternativ können Sie logbook-ktor
verwenden, das sowohl die Module logbook-ktor-client
als auch logbook-ktor-server
enthält.
Das logbook-spring
-Modul enthält einen ClientHttpRequestInterceptor
zur Verwendung mit RestTemplate
:
LogbookClientHttpRequestInterceptor interceptor = new LogbookClientHttpRequestInterceptor ( logbook );
RestTemplate restTemplate = new RestTemplate ();
restTemplate . getInterceptors (). add ( interceptor );
Logbook verfügt über eine praktische automatische Konfiguration für Spring Boot-Benutzer. Es richtet alle folgenden Teile automatisch mit sinnvollen Standardwerten ein:
Anstatt eine Abhängigkeit für logbook-core
zu deklarieren, deklarieren Sie eine für den Spring Boot Starter:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-spring-boot-starter</ artifactId >
< version >${logbook.version}</ version >
</ dependency >
Jede Bean kann bei Bedarf überschrieben und angepasst werden, z. B. so:
@ Bean
public BodyFilter bodyFilter () {
return merge (
defaultValue (),
replaceJsonStringProperty ( singleton ( "secret" ), "XXX" ));
}
Eine Liste möglicher Integrationspunkte finden Sie unter LogbookAutoConfiguration
oder in der folgenden Tabelle:
Typ | Name | Standard |
---|---|---|
FilterRegistrationBean | secureLogbookFilter | Basierend auf LogbookFilter |
FilterRegistrationBean | logbookFilter | Basierend auf LogbookFilter |
Logbook | Basierend auf Bedingung, Filtern, Formatierer und Writer | |
Predicate<HttpRequest> | requestCondition | Kein Filter; wird später mit logbook.exclude und logbook.exclude kombiniert |
HeaderFilter | Basierend auf logbook.obfuscate.headers | |
PathFilter | Basierend auf logbook.obfuscate.paths | |
QueryFilter | Basierend auf logbook.obfuscate.parameters | |
BodyFilter | BodyFilters.defaultValue() , siehe Filterung | |
RequestFilter | RequestFilters.defaultValue() , siehe Filterung | |
ResponseFilter | ResponseFilters.defaultValue() , siehe Filterung | |
Strategy | DefaultStrategy | |
AttributeExtractor | NoOpAttributeExtractor | |
Sink | DefaultSink | |
HttpLogFormatter | JsonHttpLogFormatter | |
HttpLogWriter | DefaultHttpLogWriter |
Mehrere Filter werden zu einem zusammengefasst.
logbook-spring
Einige Klassen von logbook-spring
sind in der automatischen Konfiguration enthalten.
Sie können LogbookClientHttpRequestInterceptor
mit Code wie dem folgenden automatisch verbinden:
private final RestTemplate restTemplate ;
MyClient ( RestTemplateBuilder builder , LogbookClientHttpRequestInterceptor interceptor ){
this . restTemplate = builder
. additionalInterceptors ( interceptor )
. build ();
}
Die folgenden Tabellen zeigen die verfügbare Konfiguration (alphabetisch sortiert):
Konfiguration | Beschreibung | Standard |
---|---|---|
logbook.attribute-extractors | Liste der AttributeExtractors, einschließlich Konfigurationen wie type (derzeit JwtFirstMatchingClaimExtractor oder JwtAllMatchingClaimsExtractor ), claim-names und claim-key . | [] |
logbook.filter.enabled | Aktivieren Sie den LogbookFilter | true |
logbook.filter.form-request-mode | Bestimmt, wie Formularanfragen behandelt werden | body |
logbook.filters.body.default-enabled | Aktiviert/deaktiviert Standardtextfilter, die von java.util.ServiceLoader erfasst werden | true |
logbook.format.style | Formatierungsstil ( http , json , curl oder splunk ) | json |
logbook.httpclient.decompress-response | Aktiviert/deaktiviert den zusätzlichen Dekomprimierungsprozess für HttpClient mit gzip-codiertem Textkörper (nur für Protokollierungszwecke). Dies bedeutet zusätzliche Dekomprimierung und mögliche Auswirkungen auf die Leistung. | false (deaktiviert) |
logbook.minimum-status | Mindeststatus zum Aktivieren der Protokollierung ( status-at-least und body-only-if-status-at-least ) | 400 |
logbook.obfuscate.headers | Liste der Header-Namen, die verschleiert werden müssen | [Authorization] |
logbook.obfuscate.json-body-fields | Liste der zu verschleiernden JSON-Body-Felder | [] |
logbook.obfuscate.parameters | Liste der Parameternamen, die verschleiert werden müssen | [access_token] |
logbook.obfuscate.paths | Liste der Pfade, die verschleiert werden müssen. Überprüfen Sie die Filterung auf Syntax. | [] |
logbook.obfuscate.replacement | Ein Wert, der anstelle eines verschleierten Werts verwendet werden soll | XXX |
logbook.predicate.include | Nur bestimmte Pfade und Methoden einbeziehen (sofern definiert) | [] |
logbook.predicate.exclude | Bestimmte Pfade und Methoden ausschließen (überschreibt logbook.predicate.include ) | [] |
logbook.secure-filter.enabled | Aktivieren Sie den SecureLogbookFilter | true |
logbook.strategy | Strategie ( default , status-at-least , body-only-if-status-at-least , without-body ) | default |
logbook.write.chunk-size | Teilt Protokollzeilen in kleinere Blöcke mit einer Größe von bis zu chunk-size auf. | 0 (deaktiviert) |
logbook.write.max-body-size | Schneidet den Text auf Zeichen mit max-body-size ab und hängt ... an.logbook.write.max-body-size -Wert | -1 (deaktiviert) |
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" ]
Für die grundlegende Logback-Konfiguration
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
Konfigurieren Sie Logbook mit einem LogstashLogbackSink
HttpLogFormatter formatter = new JsonHttpLogFormatter();
LogstashLogbackSink sink = new LogstashLogbackSink(formatter);
für Ausgaben wie
{
"@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
}
}
Sie haben die Flexibilität, die Standardprotokollierungsebene anzupassen, indem Sie LogstashLogbackSink
mit einer bestimmten Ebene initialisieren. Zum Beispiel:
LogstashLogbackSink sink = new LogstashLogbackSink(formatter, Level.INFO);
getWriter
und/oder getParameter*()
. Weitere Informationen finden Sie unter Servlet.ERROR
-Versand. Es wird dringend davon abgeraten, es zur Erstellung von Fehlerantworten zu verwenden. Wenn Sie Fragen, Bedenken, Fehlerberichte usw. haben, melden Sie bitte ein Problem im Issue Tracker dieses Repositorys.
Um einen Beitrag zu leisten, stellen Sie einfach eine Pull-Anfrage und fügen Sie eine kurze Beschreibung (1–2 Sätze) Ihrer Ergänzung oder Änderung hinzu. Weitere Einzelheiten finden Sie in den Beitragsrichtlinien.
Grand Turk, eine Nachbildung einer dreimastigen Fregatte sechster Klasse aus Nelsons Tagen – Logbuch und Karten von JoJan ist unter einer Creative Commons-Lizenz (Namensnennung – Weitergabe unter gleichen Bedingungen 3.0 nicht portiert) lizenziert.