اسم السجل ، /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 >
تشترك الوحدات/العناصر الإضافية في Logbook دائمًا في نفس رقم الإصدار.
وبدلاً من ذلك، يمكنك استيراد قائمة المواد الخاصة بنا...
< dependencyManagement >
< dependencies >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-bom</ artifactId >
< version >${logbook.version}</ version >
< type >pom</ type >
< scope >import</ scope >
</ dependency >
</ dependencies >
</ dependencyManagement >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-core</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-httpclient</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-jaxrs</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-json</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-netty</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-okhttp</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-okhttp2</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-servlet</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-spring-boot-starter</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor-common</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor-client</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor-server</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-ktor</ artifactId >
</ dependency >
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-logstash</ artifactId >
</ dependency >
يجب تكوين مسجل السجل لتتبع المستوى من أجل تسجيل الطلبات والاستجابات. مع Spring Boot 2 (باستخدام Logback) يمكن تحقيق ذلك عن طريق إضافة السطر التالي إلى application.properties
logging.level.org.zalando.logbook: TRACE
تتطلب جميع عمليات التكامل مثيل Logbook
الذي يحتوي على جميع التكوينات ويربط جميع الأجزاء الضرورية معًا. يمكنك إما إنشاء واحدة باستخدام جميع الإعدادات الافتراضية:
Logbook logbook = Logbook . create ();
أو قم بإنشاء نسخة مخصصة باستخدام LogbookBuilder
:
Logbook logbook = Logbook . builder ()
. condition ( new CustomCondition ())
. queryFilter ( new CustomQueryFilter ())
. pathFilter ( new CustomPathFilter ())
. headerFilter ( new CustomHeaderFilter ())
. bodyFilter ( new CustomBodyFilter ())
. requestFilter ( new CustomRequestFilter ())
. responseFilter ( new CustomResponseFilter ())
. sink ( new DefaultSink (
new CustomHttpLogFormatter (),
new CustomHttpLogWriter ()
))
. build ();
يستخدم سجل السجل استراتيجية صارمة للغاية حول كيفية القيام بتسجيل الطلب/الاستجابة:
يمكن تخفيف بعض هذه القيود باستخدام تطبيقات HttpLogWriter
المخصصة، لكنها لم تكن مثالية على الإطلاق.
بدءًا من الإصدار 2.0، يأتي Logbook الآن مزودًا بنمط إستراتيجية في جوهره. تأكد من قراءة وثائق واجهة Strategy
لفهم الآثار المترتبة.
يأتي السجل مع بعض الاستراتيجيات المضمنة:
BodyOnlyIfStatusAtLeastStrategy
StatusAtLeastStrategy
WithoutBodyStrategy
بدءًا من الإصدار 3.4.0، تم تجهيز Logbook بميزة تسمى Attribute Extractor . السمات هي في الأساس قائمة بأزواج المفاتيح/القيم التي يمكن استخلاصها من الطلب و/أو الاستجابة وتسجيلها معهم. نشأت الفكرة من الإصدار 381، حيث تم طلب ميزة لاستخراج مطالبة الموضوع من رموز JWT المميزة في رأس التفويض.
تحتوي واجهة AttributeExtractor
على طريقتين extract
: إحداهما يمكنها استخراج السمات من الطلب فقط، والأخرى تحتوي على كل من الطلب والاستجابة عند الاستفادة منها. يقوم كلاهما بإرجاع مثيل لفئة HttpAttributes
، والتي هي في الأساس Map<String, Object>
. لاحظ أنه نظرًا لأن قيم الخريطة من النوع Object
، فيجب أن يكون لها طريقة toString()
مناسبة حتى تظهر في السجلات بطريقة ذات معنى. وبدلاً من ذلك، يمكن لتنسيقات السجل حل هذه المشكلة من خلال تطبيق منطق التسلسل الخاص بهم. على سبيل المثال، يستخدم منسق السجل المدمج JsonHttpLogFormatter
ObjectMapper
لإجراء تسلسل للقيم.
هنا مثال:
final class OriginExtractor implements AttributeExtractor {
@ Override
public HttpAttributes extract ( final HttpRequest request ) {
return HttpAttributes . of ( "origin" , request . getOrigin ());
}
}
يجب بعد ذلك إنشاء السجل عن طريق تسجيل مستخرج السمات هذا:
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new OriginExtractor ())
. build ();
سيؤدي هذا إلى أن تتضمن سجلات الطلبات شيئًا مثل:
"attributes":{"origin":"LOCAL"}
للحصول على المزيد من الأمثلة المتقدمة، انظر إلى فئتي JwtFirstMatchingClaimExtractor
و JwtAllMatchingClaimsExtractor
. يقوم الأول باستخراج المطالبة الأولى التي تطابق قائمة أسماء المطالبات من رمز طلب JWT المميز. يقوم الأخير باستخراج جميع المطالبات المطابقة لقائمة أسماء المطالبات من رمز طلب JWT المميز.
إذا كنت بحاجة إلى دمج عدة AttributeExtractor
، فيمكنك استخدام فئة CompositeAttributeExtractor
:
final List < AttributeExtractor > extractors = List . of (
extractor1 ,
extractor2 ,
extractor3
);
final Logbook logbook = Logbook . builder ()
. attributeExtractor ( new CompositeAttributeExtractor ( extractors ))
. build ();
يعمل السجل في عدة مراحل مختلفة:
يتم تمثيل كل مرحلة بواجهة واحدة أو أكثر يمكن استخدامها للتخصيص. كل مرحلة لها افتراضي معقول.
يعد تسجيل رسائل HTTP وتضمين نصوصها مهمة مكلفة إلى حد ما، لذا فمن المنطقي جدًا تعطيل التسجيل لطلبات معينة. تتمثل إحدى حالات الاستخدام الشائعة في تجاهل طلبات التحقق من الصحة من موازن التحميل، أو أي طلب إلى نقاط نهاية الإدارة التي يصدرها المطورون عادةً.
يعد تعريف الشرط أمرًا سهلاً مثل كتابة Predicate
خاص يقرر ما إذا كان يجب تسجيل الطلب (والاستجابة المقابلة له) أم لا. وبدلاً من ذلك، يمكنك استخدام ودمج المسندات المحددة مسبقًا:
Logbook logbook = Logbook . builder ()
. condition ( exclude (
requestTo ( "/health" ),
requestTo ( "/admin/**" ),
contentType ( "application/octet-stream" ),
header ( "X-Secret" , newHashSet ( "1" , "true" ):: contains )))
. build ();
أنماط الاستبعاد، على سبيل المثال /admin/**
، تتبع بشكل فضفاض نمط Ant لأنماط المسار دون أخذ سلسلة الاستعلام الخاصة بعنوان URL في الاعتبار.
الهدف من التصفية هو منع تسجيل أجزاء حساسة معينة من طلبات واستجابات HTTP. يتضمن هذا عادةً رأس التفويض ، ولكن يمكن أن ينطبق أيضًا على استعلام نص عادي معين أو معلمات النموذج - على سبيل المثال كلمة المرور .
يدعم Logbook أنواعًا مختلفة من المرشحات:
يكتب | يعمل على | ينطبق على | تقصير |
---|---|---|---|
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 ();
يحدد التنسيق كيفية تحويل الطلبات والاستجابات إلى سلاسل بشكل أساسي. لا يحدد المنسقون مكان تسجيل الطلبات والاستجابات، بل يقوم الكتّاب بهذا العمل.
يأتي Logbook مزودًا بمنسقين افتراضيين مختلفين: HTTP و JSON .
HTTP هو نمط التنسيق الافتراضي الذي يوفره DefaultHttpLogFormatter
. تم تصميمه بشكل أساسي لاستخدامه في التطوير المحلي وتصحيح الأخطاء، وليس لاستخدامه في الإنتاج. هذا لأنه ليس من السهل قراءته آليًا مثل JSON.
Incoming Request: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b
GET http://example.org/test HTTP/1.1
Accept: application/json
Host: localhost
Content-Type: text/plain
Hello world!
Outgoing Response: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b
Duration: 25 ms
HTTP/1.1 200
Content-Type: application/json
{ "value" : " Hello world! " }
JSON هو نمط تنسيق بديل، يقدمه JsonHttpLogFormatter
. على عكس HTTP، فهو مصمم بشكل أساسي للاستخدام الإنتاجي - يمكن للموزعين ومستهلكي السجلات استهلاكه بسهولة.
يتطلب التبعية التالية:
< dependency >
< groupId >org.zalando</ groupId >
< artifactId >logbook-json</ artifactId >
</ dependency >
{
"origin" : " remote " ,
"type" : " request " ,
"correlation" : " 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b " ,
"protocol" : " HTTP/1.1 " ,
"sender" : " 127.0.0.1 " ,
"method" : " GET " ,
"uri" : " http://example.org/test " ,
"host" : " example.org " ,
"path" : " /test " ,
"scheme" : " http " ,
"port" : null ,
"headers" : {
"Accept" : [ " application/json " ],
"Content-Type" : [ " text/plain " ]
},
"body" : " Hello world! "
}
{
"origin" : " local " ,
"type" : " response " ,
"correlation" : " 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b " ,
"duration" : 25 ,
"protocol" : " HTTP/1.1 " ,
"status" : 200 ,
"headers" : {
"Content-Type" : [ " text/plain " ]
},
"body" : " Hello world! "
}
ملاحظة: سيتم تضمين النصوص من النوع application/json
(و application/*+json
) في شجرة JSON الناتجة. أي لن يتم الهروب من نص استجابة JSON وتمثيله كسلسلة:
{
"origin" : " local " ,
"type" : " response " ,
"correlation" : " 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b " ,
"duration" : 25 ,
"protocol" : " HTTP/1.1 " ,
"status" : 200 ,
"headers" : {
"Content-Type" : [ " application/json " ]
},
"body" : {
"greeting" : " Hello, world! "
}
}
تنسيق السجل العام (CLF) هو تنسيق ملف نصي قياسي تستخدمه خوادم الويب عند إنشاء ملفات سجل الخادم. يتم دعم التنسيق عبر 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 للمزيد من التفاصيل.
اعتبارًا من الإصدار 1.5.0 من Logbook، يمكنك الآن تحديد إحدى الاستراتيجيات الثلاث التي تحدد كيفية تعامل 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 (القديم) في Logbook 3.0 إلى 3.6.
اعتبارًا من الإصدار 3.7 من Logbook، عاد دعم JAX-RS 2.x.
ومع ذلك، تحتاج إلى إضافة مصنف javax
لاستخدام وحدة Logbook المناسبة:
< 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
الدعم لخادم JDK HTTP وتحتوي على:
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
لاستخدامه مع الإصدار 2.x من OkHttpClient
:
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
لاستخدامه مع الإصدار 3.x من OkHttpClient
:
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 | قائمة AttributeExtractors، بما في ذلك التكوينات مثل type (حاليًا JwtFirstMatchingClaimExtractor أو JwtAllMatchingClaimsExtractor ) claim-names claim-key . | [] |
logbook.filter.enabled | تمكين عامل LogbookFilter | true |
logbook.filter.form-request-mode | يحدد كيفية التعامل مع طلبات النموذج | body |
logbook.filters.body.default-enabled | تمكين/تعطيل عوامل تصفية النص الافتراضية التي تم جمعها بواسطة java.util.ServiceLoader | true |
logbook.format.style | نمط التنسيق ( http أو json أو curl أو splunk ) | json |
logbook.httpclient.decompress-response | تمكين/تعطيل عملية إلغاء الضغط الإضافية لـ 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*()
. راجع Servlet لمزيد من التفاصيل.ERROR
. ننصحك بشدة بعدم استخدامه لإنتاج استجابات للأخطاء. إذا كانت لديك أسئلة، أو استفسارات، أو تقارير أخطاء، وما إلى ذلك، فيرجى تقديم مشكلة في أداة تعقب المشكلات الخاصة بهذا المستودع.
للمساهمة، ما عليك سوى تقديم طلب سحب وإضافة وصف مختصر (1-2 جملة) لإضافتك أو تغييرك. لمزيد من التفاصيل، راجع إرشادات المساهمة.
Grand Turk، نسخة طبق الأصل من فرقاطة من الدرجة السادسة ذات ثلاثة سارية من أيام نيلسون - سجل ورسوم بيانية من تصميم JoJan، مرخصة بموجب المشاع الإبداعي (Attribution-Share Alike 3.0 Unported).