حين لا مفرّ من بناء logger خاصّ بك، ما الحدّ الأدنى الذي تحتاجه فعلاً؟ - متطلّبات عمليّة وفحوص اختبار التكامل

· · Windows Development, Logging, Integration Testing, Test Design, Reliability

إن أمكنك استخدام logging framework جاهز، فذلك عادةً هو الخيار الأكثر أماناً. ومع ذلك، توجد حالات تدفعك فيها قيود التطبيق أو متطلّبات التشغيل إلى بناء logger مخصّص. الجزء الصعب هو تحديد ما يكفي للإصدار الأوّل دون الانتهاء بشيء إمّا أضعف من أن يُوثق به أو أثقل من أن يُصان.

يضيّق هذا المقال النطاق ليقتصر على سجلّات التشخيص الخاصّة بالتطبيق. لا يحاول حلّ سجلّات التدقيق، أو distributed tracing، أو مسارات المقاييس، أو التجميع السحابيّ دفعةً واحدة. الهدف هو حدّ أدنى عمليّ مفيد أثناء التحقيق في الحوادث، إضافةً إلى قائمة قصيرة من اختبارات التكامل تجعل التنفيذ جديراً بالثقة.

الخلاصة أوّلاً

في الإصدار الأوّل، الأساسيّات عادةً هي التالية.

  • استخدم UTF-8 JSON Lines
  • حافظ على سجلّ واحد لكلّ سطر
  • اشترط الحقول timestamp وlevel وcategory وmessage وfields وsessionId وprocessId
  • اجعل الافتراض عمليّة واحدة، ملفّ واحد
  • استخدم الكتابة المتزامنة عند الحجم المنخفض، أو single writer + bounded queue عند الحجم الأعلى
  • نفّذ flush المتزامن لـError / Critical ولأحداث بدء/نهاية الجلسة
  • أضف الدوران والاحتفاظ منذ الإصدار الأوّل
  • إن أخفقَ موقع السجلّ الأساسيّ، فلا تكتب بصمت في مكان آخر دون إظهار هذا الإخفاق صراحة

ذلك يكفي للبقاء عمليّاً مع تجنّب أكثر مشاكل الموثوقيّة شيوعاً.

ضيّق النطاق أوّلاً

تتعقّد loggers المخصّصة عندما يُطلب منها حلّ مشاكل كثيرة في وقت واحد. إن مزجتَ سجلّات التشخيص، وسجلّات التدقيق، وعدّادات الأداء، وdistributed traces، وتحليلات سلوك المستخدم في تصميم واحد، فإنّ قائمة المتطلّبات تنفجر فوراً.

الهدف هنا أصغر بكثير: السجلّات المستخدمة للتحقيق في إخفاقات التطبيق. بعبارة أخرى، تريد إعادة بناء متى حدث الأمر، في أيّ جزء من التطبيق، وبأيّ سياق محيط. حالما يُحصر النطاق في هذه المهمّة، تصبح قرارات التصميم أيسر بكثير.

الحدّ الأدنى من المتطلّبات

1. استخدم UTF-8 JSON Lines

بدء الأمور بإلصاق نصّ عاديّ سهل، لكنّه يصبح محرجاً للمعالجة لاحقاً. التنسيق الثنائيّ المخصّص الثقيل يعاني من المشكلة المعاكسة: من الأصعب فحصه أثناء التشغيل.

UTF-8 JSON Lines نقطة وسطى جيّدة. كلّ سطر سجلّ واحد، ممّا يُبقي الملفّ قابلاً للقراءة من قبل البشر وسهل التحليل من النصوص البرمجيّة والأدوات. إن تعطّلت عمليّة الكتابة، فعادةً ما يمكنك حصر التلف في سطر واحد بدلاً من فقد بنية الملفّ بأكمله.

2. ثبّت الحقول الإلزاميّة مبكّراً

الحدّ الأدنى العمليّ هو الحقول السبعة التالية.

  • timestamp
  • level
  • category
  • message
  • fields
  • sessionId
  • processId

إن احتفظتَ فقط بسلسلة message، فستندم حالما تنمو متطلّبات البحث أو الربط. وإن أضفتَ حقولاً كثيرة منذ البداية، فإنّ كلّ موقع استدعاء سيصبح أثقل ممّا يلزم. هذه المجموعة الثابتة الأصغر نقطة بداية معقولة.

3. اجعل الافتراض عمليّة واحدة، ملفّ واحد

السماح لعمليّات متعدّدة بالإلحاق بالملفّ نفسه يُولّد أوضاع إخفاق أكثر ممّا يبدو. التأمين، والكتابات الجزئيّة، وتوقيت الدوران، وسلوك التوقّف غير المتوقّع، تصبح جميعها أصعب.

الافتراض الأكثر أماناً هو عمليّة واحدة، ملفّ واحد. إن احتجتَ لاحقاً إلى سجلّات مجمّعة، فقم بالتجميع صراحةً عبر عمليّة منفصلة أو مجمّع تالي.

4. اختر استراتيجيّة الكتابة بحسب الحجم

عند الحجم المنخفض، تكون الكتابة المتزامنة غالباً الخيار الأفضل لأنّ السلوك بسيط ويسهل تعليله. التسجيل غير المتزامن المبكّر يميل إلى خلق غموض حول الإيقاف، وتوقيت flush، وفقد السجلّات النهائيّة.

إن أصبح حجم السجلّ مرتفعاً بما يكفي ليكون I/O المتزامن عنق زجاجة، فانتقل إلى single writer + bounded queue. قرار التصميم الرئيسيّ هو ما يحدث عندما تمتلئ القائمة. حدّد تلك السياسة صراحةً بدلاً من تركها للصدفة.

5. حدّد قواعد flush

عادةً ما يستحقّ flushلـError وCritical وأحداث بدء/نهاية الجلسة بصورة متزامنة لأنّها الأهمّ أثناء مراجعة الحوادث. معاملة كلّ رسالة Info بالطريقة نفسها كثيراً ما يُضرّ بالأداء دون أن يضيف قيمة تشغيليّة كبيرة.

6. أضف الدوران والاحتفاظ منذ الإصدار الأوّل

كثيراً ما يُؤجَّل الدوران، لكنّه يتحوّل سريعاً إلى مشكلة تشغيليّة إن فعلتَ ذلك. قد تتفاوت السياسة الدقيقة، لكن ينبغي للنظام على الأقلّ أن يتفادى النموّ غير المحدود وأن يُحدّد عدد الملفّات المحتفظ بها.

7. لا تُخفق بصمت بالانتقال إلى موقع غامض

إن لم يكن دليل السجلّ المقصود متاحاً، فإنّ تحويل الكتابات بصمت إلى مكان آخر يجعل الاستجابة للحوادث أسوأ عادةً. يتوقّع المشغّل وجود السجلّات في مكان واحد ويفقد وقتاً عند غيابها.

إن أخفق التسجيل، فأظهر هذا الإخفاق بوضوح من خلال التطبيق، أو الخطأ القياسيّ، أو سجلّ الأحداث، أو آليّة صريحة أخرى. أسوأ نتيجة هي سجلّات انتقلت إلى مكان لا يعرف أحدٌ أن يفحصه.

شكل معقول للإصدار الأوّل

غالباً ما يكون الإصدار الأوّل العمليّ ليس أكثر من هذا.

  • UTF-8 JSON Lines
  • عمليّة واحدة، ملفّ واحد
  • اسم ملفّ موجّه بالجلسة
  • دوران قائم على الحجم أو على بدء التشغيل
  • حدّ للاحتفاظ
  • flush متزامن لـError / Critical
  • واجهة برمجيّة تقبل fields مهيكلة

أيّ شيء أبعد من ذلك يجب عادةً أن ينتظر حتى يجعل ألمٌ تشغيليٌّ حقيقيّ المتطلّبَ التالي بديهيّاً.

الأخطاء الشائعة

هذه أخطاء تستحقّ التجنّب.

  • وضع كلّ السياق في سلسلة message واحدة
  • مشاركة ملفّ واحد بين عدّة عمليّات
  • جعل كلّ شيء غير متزامن دون سياسة flush واضحة
  • تأجيل الدوران والاحتفاظ
  • الانتقال بصمت إلى مجلّد آخر عند إخفاق الكتابات
  • إضافة الإرسال عبر الشبكة أو التخزين في قاعدة بيانات محليّة في الإصدار الأوّل

كلّ هذه قد تبدو ملائمة في البداية وتصبح مكلفة أثناء استكشاف الأعطال.

فكّر في اختبارات التكامل بملفّات وخيوط وعمليّات حقيقيّة

من الصعب الوثوق بـlogger إن كان مغطّى بـunit tests فقط. التحقّق من تنسيق السلاسل أو تسلسل JSON وحده لا يخبرك كثيراً عن المخاطر التشغيليّة الحقيقيّة، التي تعيش عادةً في I/O الملفّات، والتزامن، والدوران، والإيقاف، وإخفاقات الأذونات.

لذلك ينبغي لاختبارات التكامل أن تستخدم ملفّات حقيقيّة وخيوطاً حقيقيّة، وعمليّات حقيقيّة عند الحاجة. الهدف هو تجنّب logger يعمل في المسار السعيد لكنّه لا يمكن الوثوق به في ظروف الإخفاق.

اختبارات تكامل تستحقّ الاحتفاظ بها

سلامة الكتابة المنفردة

  • كلّ سطر هو بالضبط سجلّ JSON واحد
  • يمكن إعادة قراءة الملفّ بترميز UTF-8
  • يحتوي كلّ سجلّ على الحقول الإلزاميّة
  • لا تكسر الأسطر الجديدة المضمَّنة حدود السجلّات

التزامن داخل عمليّة واحدة

  • الكتابات المتزامنة من عدّة خيوط لا تُفسد السجلّات
  • أعداد السجلّات تطابق التوقّعات
  • عند استخدام قائمة، يطابق سلوك الترتيب أو الإسقاط السياسةَ الموثَّقة

سلوك flush والإيقاف

  • تظهر سجلّات Error / Critical فوراً
  • يستنزف الإيقاف الطبيعيّ القائمة
  • تنجو السجلّات المهمّة لنهاية الجلسة في مسارات الإيقاف القريبة من الاستثناء

الدوران والاحتفاظ

  • يحدث الدوران عند بلوغ العتبة
  • تُحذَف الملفّات القديمة وفق قاعدة الاحتفاظ
  • السجلّات المكتوبة حول حدود الدوران تبقى أسطر JSON صحيحة

حالات الإخفاق

  • السلوك عندما لا يوجد الدليل الهدف
  • السلوك عندما تكون صلاحيّة الكتابة مفقودة
  • السلوك عند حدوث خطأ يشبه امتلاء القرص
  • السلوك عند فيضان الـbounded queue

العمليّات المتعدّدة

إن قالت المواصفة عمليّة واحدة، ملفّ واحد، فيمكنك اختبار أنّ عمليّة أخرى لا تنضمّ إلى الملفّ نفسه. إن استخدمتَ عمليّة تجميع بدلاً من ذلك، فاختبر إخفاقات التسليم على ذلك المسار أيضاً.

ستّة اختبارات تستحقّ الشحن أوّلاً

إن حاولتَ أتمتة كلّ سيناريو في التمرير الأوّل، فقد تصبح المجموعة أثقل من logger نفسه. مجموعة بداية براغماتيّة هي الاختبارات الستّة التالية.

  1. كتابة عاديّة من خيط واحد
  2. كتابة متزامنة من عدّة خيوط
  3. flush متزامن لـError / Critical
  4. الدوران والاحتفاظ
  5. إخفاق صريح عند عدم توفّر المسار الهدف
  6. استنزاف وflush نهائيّ عند الإيقاف الطبيعيّ

تلك المجموعة الصغيرة وحدها تنقل logger مسافة طويلة بعيداً عن «إنّه يطبع سلاسل» إلى شيء يُعتمد عليه تشغيليّاً.

الخلاصة

الهدف الأوّل لـlogger مخصّص ليس غنى الميزات. إنّه الثقة أثناء التحقيق في الإخفاقات. ويعني ذلك عادةً تثبيت التنسيق على UTF-8 JSON Lines، وحصر الحقول الإلزاميّة، وجعل عمليّة واحدة، ملفّ واحد افتراضاً، واتّخاذ قرارات flush والدوران والاحتفاظ وسلوك الإخفاق مبكّراً.

ثمّ تحقّق من تلك القرارات باختبارات تكامل تستخدم ملفّات وخيوطاً وعمليّات حقيقيّة. إن أرسيتَ التصميم الأدنى ومجموعة الاختبارات الدنيا أوّلاً، فسيصبح من الأسهل بكثير تنمية logger لاحقاً دون أن يتحوّل إلى عبء صيانة.

مقالات ذات صلة

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

أين يتصل هذا الموضوع

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

العودة إلى المدونة