
هناك الكثير من الأسئلة حول عمل الخدمات في مراحل التطوير والاختبار والدعم ، وكلها للوهلة الأولى على عكس:
"ماذا حدث؟" "هل كان هناك طلب؟" ،
"ما هو تنسيق التاريخ؟" ،
"لماذا لا تستجيب الخدمة؟" الخ.
سيتمكن السجل الذي تم تجميعه بشكل صحيح من الإجابة بالتفصيل على هذه الأسئلة والعديد من الأسئلة الأخرى بشكل مستقل تمامًا دون مشاركة المطورين. سعيًا لتحقيق هذا الهدف المغري ، ولدت مكتبة تسجيل Eclair ، المصممة للدخول في حوار مع جميع المشاركين في العملية دون سحب الكثير من البطانيات.
حول بطانية وميزات الحل - أدناه.
ما هي مشكلة التسجيل
إذا لم تكن مهتمًا جدًا بفهم المبنى ، يمكنك المتابعة فورًا إلى وصف حلنا .
- سجل التطبيق هو غيابه.
في معظم الأحيان ، هو الوحيد الذي يمكنه إثبات نجاح التطبيق. لا توجد حالة في الخدمات المصغرة ؛ الأنظمة المجاورة متحركة ومتحركة. "كرر" ، "أعيد إنشاء" ، "فحص مزدوج" - كل هذا صعب و / أو مستحيل. يجب أن يكون السجل بالمعلومات الكافية للإجابة على السؤال: "ماذا حدث؟" في أي وقت . . يجب أن يكون السجل واضحًا للجميع: المطور ، والاختبار ، وأحيانًا المحلل ، وأحيانًا المسؤول ، وأحيانًا السطر الأول من الدعم - يحدث أي شيء. - الخدمات الدقيقة تدور حول تعدد الخيوط.
غالبًا ما تتم معالجة الطلبات الواردة إلى الخدمة (أو البيانات التي تطلبها الخدمة) من خلال عدة سلاسل رسائل. عادة ما يتم خلط سجل كافة مؤشرات الترابط. هل تريد التمييز بين الخيوط المتوازية والتمييز بين الخيوط "المتسلسلة"؟ يتم إعادة استخدام الدفق نفسه للمعالجة التسلسلية للطلبات ، مرارًا وتكرارًا تنفيذ منطقه الخاص لمجموعات البيانات المختلفة. هذه التدفقات "المتسلسلة" تتدفق من مستوى آخر ، لكن حدودها يجب أن تكون واضحة للقارئ. - يجب أن يحفظ السجل تنسيق البيانات الأصلي.
إذا تم تبادل الخدمات في الواقع بواسطة XML ، فيجب على السجل المقابل تخزين XML. إنه ليس مضغوطًا دائمًا وليس جميلًا دائمًا (ولكنه مريح). من السهل أن ترى النجاح ، أسهل في تحليل الفشل. في بعض الحالات ، يمكن استخدام السجل لتشغيل الطلب أو إعادة معالجته يدويًا. - يتطلب جزء من البيانات في السجل علاقة خاصة.
غالبًا ما يلزم تخزين البيانات الواردة (الطلبات) والبيانات الصادرة (الإجابات) والطلبات المقدمة إلى أنظمة الجهات الخارجية والاستجابات منها بشكل منفصل. تخضع لمتطلبات خاصة: من خلال مدة الصلاحية أو الموثوقية. بالإضافة إلى ذلك ، يمكن أن تحتوي هذه البيانات على مبلغ مثير للإعجاب مقارنة بخط سجل نموذجي. - جزء من البيانات ليس للسجل.
يجب استبعاد ما يلي عادة من السجل العادي: البيانات الثنائية (صفائف البايت ، base64 ، ..) ، البيانات الشخصية للعملاء / الشركاء / الأفراد الآخرين والكيانات القانونية. إنها دائمًا قصة فردية ، ولكنها منهجية ولا تخضع للتحكم اليدوي.
لماذا لا اليدين
خذ
org.slf4j.Logger
(قم
org.slf4j.Logger
إليه مع مقدمي الطلبات من أي مجموعة) واكتب كل ما هو مطلوب إلى السجل. مداخل الطرق الرئيسية ، مخارج ، إذا لزم الأمر ، تعكس أخطاء اشتعلت ، بعض البيانات. هل هذا ضروري؟ نعم ولكن:
- كمية التعليمات البرمجية تنمو بشكل غير معقول (بشكل غير عادي). في البداية ، هذا ليس مدهشًا جدًا ، إذا قمت بتسجيل الدخول الأساسي فقط (الدعم الناجح ، بالمناسبة ، مع هذا النهج).
- استدعاء المسجل بيديك يصبح كسولًا بسرعة. الإعلان عن حقل
static
باستخدام جهاز تسجيل كسول للغاية (حسنًا ، يمكن أن يفعل لومبوك هذا لنا). نحن المطورين كسالى. ونحن نستمع إلى كسلنا ، هذا كسل نبيل: إنه يغير العالم باستمرار للأفضل. - الخدمات الدقيقة ليست جيدة من جميع الجهات. نعم ، إنها صغيرة وجميلة ، ولكن هناك جانب آخر: هناك الكثير! غالبًا ما يكتب تطبيق واحد من البداية إلى النهاية بواسطة مطور واحد. الإرث لا يلوح أمام عينيه. سعيد ، غير مثقل بالقواعد المفروضة ، يعتبر المطور أنه من واجب اختراع تنسيق السجل الخاص به ، ومبدأه وقواعده الخاصة. ثم ينفذ الاختراع ببراعة. كل فئة مختلفة. هل هذه مشكلة؟ ضخم.
- سيعيد تحطيم السجل الخاص بك. حتى الفكرة القادرة لن تنقذه. تحديث السجل مستحيل مثل تحديث Javadoc. في نفس الوقت ، على الأقل يقرأ Javadoc فقط من قبل المطورين (لا ، لا يقرأ أحد) ، لكن جمهور السجلات أوسع بكثير وفريق التطوير غير محدود.
- يعد MDC (سياق التشخيص المعين) جزءًا لا يتجزأ من تطبيق متعدد الخيوط. يتطلب الملء اليدوي لـ MDC التنظيف في الوقت المناسب في نهاية العمل في التيار. وإلا ، فإنك تخاطر بربط
ThreadLocal
غير ذات صلة. اليدين والعيون للسيطرة على هذا ، أجرؤ على القول ، أمر مستحيل.
وبهذه الطريقة نحل هذه المشاكل في تطبيقاتنا.
ما هو Eclair وماذا يمكنه أن يفعل
Eclair هي أداة تبسط كتابة التعليمات البرمجية المسجلة. يساعد على جمع المعلومات الوصفية اللازمة حول شفرة المصدر ، وربطها بالبيانات التي تحلق في التطبيق في وقت التشغيل وإرسالها إلى مستودع السجل المعتاد ، مع توليد الحد الأدنى من التعليمات البرمجية.
الهدف الرئيسي هو جعل السجل واضحًا لجميع المشاركين في عملية التطوير. لذلك ، راحة كتابة التعليمات البرمجية ، لا تنتهي فوائد Eclair ، ولكنها تبدأ فقط.
تسجل Eclair الطرق والمعلمات المشروحة:
- يسجل إدخال / خروج الأسلوب من الأسلوب / الاستثناءات / الوسيطات / القيم التي أرجعتها الطريقة
- تصفية الاستثناءات لتسجيلها على وجه التحديد إلى الأنواع: فقط عند الضرورة
- يغير "تفاصيل" السجل ، استنادًا إلى إعدادات التطبيق للموقع الحالي: على سبيل المثال ، في الحالة الأكثر تفصيلاً ، يطبع قيم الوسيطات (كلها أو بعضها) ، في أقصر نسخة - فقط حقيقة إدخال الطريقة
- يطبع البيانات بتنسيق JSON / XML / بأي تنسيق آخر (جاهز للعمل مع جاكسون ، JAXB خارج الصندوق): يفهم التنسيق الأكثر ملاءمة لمعلمة معينة
- يفهم SpEL (لغة تعبير الربيع) للتثبيت التعريفي والتنظيف التلقائي MDC
- يكتب إلى N loggers ، "المسجل" في فهم Eclair هو حبة فول في السياق الذي يطبق واجهة
EclairLogger
: يمكنك تحديد المسجل الذي يجب أن يعالج التعليق التوضيحي بالاسم أو بالاسم المستعار أو بشكل افتراضي - يخبر المبرمج عن بعض الأخطاء في استخدام التعليقات التوضيحية: على سبيل المثال ، يعرف Eclair أنه يعمل على الوكلاء الديناميكيين (مع جميع الميزات التالية) ، لذلك ، يمكن أن يشير إلى أن التعليق التوضيحي على الطريقة
private
لن يعمل أبدًا - يقبل التعليقات التوضيحية الوصفية (كما يطلق عليها الربيع): يمكنك تحديد التعليقات التوضيحية الخاصة بك لتسجيل الدخول باستخدام بعض التعليقات التوضيحية الأساسية - لتقليل الشفرة
- قادرة على إخفاء البيانات "الحساسة" عند الطباعة: من خارج منطقة الجزاء - تدريع XMLath
- يكتب سجلاً في الوضع "اليدوي" ، ويعرف المستثمر و "يوسع" الحجج التي تنفذ
Supplier
: إعطاء الفرصة لتهيئة الحجج "كسول"
كيفية توصيل Eclair
يتم نشر شفرة المصدر على GitHub بموجب ترخيص Apache 2.0.
للاتصال ، تحتاج إلى Java 8 و Maven و Spring Boot 1.5+. قطعة أثرية استضافها مستودع Maven المركزي:
<dependency> <groupId>ru.tinkoff</groupId> <artifactId>eclair-spring-boot-starter</artifactId> <version>0.8.3</version> </dependency>
يحتوي المبتدئ على تنفيذ قياسي لـ
EclairLogger
، والذي يستخدم نظام تسجيل تمت تهيئته بواسطة Spring Boot مع مجموعة من الإعدادات التي تم التحقق منها.
أمثلة
فيما يلي بعض الأمثلة على الاستخدام النموذجي للمكتبة. أولاً ، يتم إعطاء جزء من الكود ، ثم السجل المقابل ، اعتمادًا على توفر مستوى معين من التسجيل. يمكن العثور على مجموعة أكثر اكتمالاً من الأمثلة في ويكي المشروع في قسم
الأمثلة .
أبسط مثال
المستوى الافتراضي هو DEBUG.
@Log void simple() { }
إذا كان المستوى متاحًا | ... ثم سيكون السجل هكذا |
---|
TRACE DEBUG | DEBUG [] rteeExample.simple > DEBUG [] rteeExample.simple < |
INFO WARN ERROR | - |
تعتمد تفاصيل السجل على مستوى التسجيل المتاح.
يؤثر مستوى التسجيل المتاح في الموقع الحالي على تفاصيل السجل. كلما انخفض المستوى المتاح (أي أقرب إلى TRACE) ، كلما كان السجل أكثر تفصيلاً.
@Log(INFO) boolean verbose(String s, Integer i, Double d) { return false; }
المستوى | سجل |
---|
TRACE DEBUG | INFO [] rteeExample.verbose > s="s", i=4, d=5.6 INFO [] rteeExample.verbose < false |
INFO | INFO [] rteeExample.verbose > INFO [] rteeExample.verbose < |
WARN ERROR | - |
صقل تسجيل الاستثناءات
يمكن تصفية أنواع الاستثناءات المسجلة. سيتم التعهد باستثناءات مختارة وذريتهم. في هذا المثال ، سيتم تسجيل
NullPointerException
على مستوى WARN ،
Exception
على مستوى الخطأ (افتراضيًا) ، ولن يتم تسجيل
Error
على الإطلاق (لأنه لم
Error
تضمين
Error
في فلتر التعليق التوضيحي الأول
@Log.error
ويتم استبعاده بشكل صريح من فلتر التعليق التوضيحي الثاني).
@Log.error(level = WARN, ofType = {NullPointerException.class, IndexOutOfBoundsException.class}) @Log.error(exclude = Error.class) void filterErrors(Throwable throwable) throws Throwable { throw throwable; }
المستوى | سجل |
---|
TRACE DEBUG INFO WARN | WARN [] rteeExample.filterErrors ! java.lang.NullPointerException java.lang.NullPointerException: null at rteeExampleTest.filterErrors(ExampleTest.java:0) .. ERROR [] rteeExample.filterErrors ! java.lang.Exception java.lang.Exception: null at rteeExampleTest.filterErrors(ExampleTest.java:0) ..
|
ERROR | ERROR [] rteeExample.filterErrors ! java.lang.Exception java.lang.Exception: null at rteeExampleTest.filterErrors(ExampleTest.java:0) .. |
قم بتعيين كل معلمة على حدة
@Log.in(INFO) void parameterLevels(@Log(INFO) Double d, @Log(DEBUG) String s, @Log(TRACE) Integer i) { }
المستوى | سجل |
---|
TRACE | INFO [] rteeExample.parameterLevels > d=9.4, s="v", i=7 |
DEBUG | INFO [] rteeExample.parameterLevels > d=9.4, s="v" |
INFO | INFO [] rteeExample.parameterLevels > 9.4 |
WARN ERROR | - |
حدد وتخصيص تنسيق الطباعة
يمكن تكوين "الطابعات" المسؤولة عن تنسيق الطباعة بواسطة معالجات ما قبل وما بعد. في المثال أعلاه ،
maskJaxb2Printer
تكوين
maskJaxb2Printer
بحيث
maskJaxb2Printer
العناصر التي تطابق تعبير XPath
"//s"
باستخدام
"********"
. في نفس الوقت ، تطبع
jacksonPrinter
"كما هي".
@Log.out(printer = "maskJaxb2Printer") Dto printers(@Log(printer = "maskJaxb2Printer") Dto xml, @Log(printer = "jacksonPrinter") Dto json, Integer i) { return xml; }
المستوى | سجل |
---|
TRACE DEBUG | DEBUG [] rteeExample.printers > xml=<dto><i>5</i><s>********</s></dto>, json={"i":5,"s":"password"} DEBUG [] rteeExample.printers < <dto><i>5</i><s>********</s></dto> |
INFO WARN ERROR | - |
مسجلات متعددة في السياق
يتم تسجيل الطريقة باستخدام عدة برامج تسجيل في نفس الوقت: عن طريق المسجل الافتراضي (المشروح باستخدام
@Primary
)
auditLogger
AuditLogger. يمكنك تحديد عدة برامج تسجيل إذا كنت تريد فصل الأحداث المسجلة ليس فقط حسب المستوى (TRACE - ERROR) ، ولكن أيضًا إرسالها إلى مخازن مختلفة. على سبيل المثال ، يمكن للمسجل الرئيسي كتابة سجل إلى ملف على القرص باستخدام slf4j ، ويمكن لـ
auditLogger
كتابة شريحة بيانات خاصة إلى تخزين ممتاز (على سبيل المثال ، في Kafka) بتنسيقه الخاص.
@Log @Log(logger = "auditLogger") void twoLoggers() { }
إدارة MDC
يتم حذف MDCs التي تم تعيينها باستخدام التعليق التوضيحي تلقائيًا بعد الخروج من الطريقة المُعلَّقة. يمكن حساب قيمة سجل MDC ديناميكيًا باستخدام SpEL. فيما يلي أمثلة: سلسلة ثابتة تدرك بواسطة ثابت ، تقييم التعبير
1 + 1
، استدعاء
jacksonPrinter
، استدعاء الأسلوب
static
randomUUID
.
لا يتم حذف MDCs مع السمة
global = true
بعد الخروج من الطريقة: كما ترى ، فإن السجل الوحيد المتبقي في MDC حتى نهاية السجل هو
sum
.
@Log void outer() { self.mdc(); } @Mdc(key = "static", value = "string") @Mdc(key = "sum", value = "1 + 1", global = true) @Mdc(key = "beanReference", value = "@jacksonPrinter.print(new ru.tinkoff.eclair.example.Dto())") @Mdc(key = "staticMethod", value = "T(java.util.UUID).randomUUID()") @Log void mdc() { self.inner(); } @Log.in void inner() { }
سجل عند تنفيذ الرمز أعلاه:
DEBUG [] rteeExample.outer >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.mdc >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.inner >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.mdc <
DEBUG [sum=2] rteeExample.outer <
تثبيت MDC على أساس المعلمات
إذا قمت بتحديد MDC باستخدام التعليق التوضيحي على المعلمة ، فإن المعلمة التي تم التعليق عليها متاحة ككائن الجذر لسياق التقييم. هنا
"s"
هو حقل من الفئة
Dto
بنوع
String
.
@Log.in void mdcByArgument(@Mdc(key = "dto", value = "#this") @Mdc(key = "length", value = "s.length()") Dto dto) { }
سجل عند تنفيذ الرمز أعلاه:
DEBUG [length=8, dto=Dto{i=12, s='password'}] rteeExample.mdcByArgument > dto=Dto{i=12, s='password'}
تسجيل يدوي
بالنسبة للتسجيل "اليدوي" ، يكفي تنفيذ تطبيق
ManualLogger
. الحجج التي تم تمريرها والتي تنفذ
Supplier
الواجهة سيتم "توسيعها" فقط إذا لزم الأمر.
@Autowired private ManualLogger logger; @Log void manual() { logger.info("Eager logging: {}", Math.PI); logger.debug("Lazy logging: {}", (Supplier) () -> Math.PI); }
المستوى | سجل |
---|
TRACE DEBUG | DEBUG [] rteeExample.manual > INFO [] rteeExample.manual - Eager logging: 3.141592653589793 DEBUG [] rteeExample.manual - Lazy logging: 3.141592653589793 DEBUG [] rteeExample.manual < |
INFO | INFO [] rteeExample.manual - Eager logging: 3.141592653589793 |
WARN ERROR | - |
ماذا لا تفعل ايكلير
لا تعرف Eclair المكان الذي ستخزن فيه سجلاتك ، إلى متى وإلى التفاصيل. لا تعرف Eclair كيف تخطط لاستخدام السجل الخاص بك. يستخرج Eclair بعناية من تطبيقك جميع المعلومات التي تحتاجها ويعيد توجيهها إلى التخزين الذي قمت بتكوينه.
مثال على تهيئة
EclairLogger
لتوجيه سجل إلى مسجل Logback مع ملحق معين:
@Bean public EclairLogger eclairLogger() { LoggerFacadeFactory factory = loggerName -> { ch.qos.logback.classic.LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); ch.qos.logback.classic.Logger logger = context.getLogger(loggerName);
هذا الحل ليس للجميع.
قبل البدء في استخدام Eclair كأداة رئيسية للتسجيل ، يجب أن تتعرف على عدد من ميزات هذا الحل. ترجع هذه "الميزات" إلى حقيقة أن Eclair يعتمد على آلية الوكيل القياسية لـ Spring.
- إن سرعة تنفيذ الكود الملفوف في البروكسي التالي غير مهمة ، لكنها ستنخفض. بالنسبة لنا ، نادرا ما تكون هذه الخسائر كبيرة. إذا ظهر السؤال المتعلق بتقليل المهلة الزمنية ، فهناك العديد من إجراءات التحسين الفعالة. يمكن اعتبار رفض سجل إعلامي مناسب كأحد الإجراءات ، ولكن ليس في المقام الأول.
- StackTrace "منتفخ" أكثر قليلاً. إذا لم تكن معتادًا على المكدس الطويل من وكلاء الربيع ، فقد يكون هذا مصدر إزعاج لك. لسبب واضح بنفس القدر ، سيكون تصحيح الطبقات بالوكالة أمرًا صعبًا.
-
لا يمكن ربط كل فئة وكل طريقة: لا يمكن إجراء بروكسي للطرق
private
، ستحتاج إلى تسجيل الدخول في سلسلة الطرق في حبة واحدة ، ولا يمكنك استخدام وكيل أي شيء غير حبة ، وما إلى ذلك.
في النهاية
من الواضح تمامًا أنه يجب استخدام هذه الأداة ، مثل أي أداة أخرى ، للاستفادة منها. وهذه المادة تضيء بشكل سطحي فقط الجانب الذي قررنا فيه التحرك بحثًا عن الحل المثالي.
النقد والأفكار والتلميحات والروابط - أرحب بحرارة بأي من مشاركتك في حياة المشروع! سأكون سعيدًا إذا وجدت Eclair مفيدًا لمشاريعك.