بناء نظام Logging منظم في تطبيقات Python

بناء نظام Logging منظم في تطبيقات Python

تساعد السجلات المطور على فهم ما حدث عندما لا يكون الخطأ أمامه مباشرة. لكن طباعة النصوص عشوائياً تجعل البحث صعباً، وقد تكشف بيانات لا ينبغي تسجيلها. في هذا المقال نستخدم وحدة logging المدمجة في Python، ونضيف مستويات ورسائل تتضمن سياقاً مفيداً. سنفرق بين debug وinfo وwarning وerror، ونكتب السجل إلى الطرفية وملف، ثم نوضح كيف نترك كلمات المرور والرموز السرية خارج أي رسالة.

بناء نظام Logging منظم في تطبيقات Python

الفكرة الأساسية

تتكون منظومة logging من logger وhandler وformatter. يحدد logger مستوى الرسالة، ويحدد handler وجهتها، بينما يحدد formatter شكلها. يمكن لكل وحدة استخدام logger باسمها بدلاً من إنشاء إعداد مستقل يكرر نفسه. في البيئة المحلية قد تحتاج إلى debug، أما الإنتاج فيحتاج إلى مستوى مناسب وسياسة تدوير للملفات. السجل الجيد يذكر العملية والمعرف والنتيجة، لكنه لا ينسخ جسم الطلب كله من دون حاجة.

مثال برمجي عملي

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
logger = logging.getLogger("orders")

def create_order(order_id, total):
    logger.info("creating order_id=%s total=%s", order_id, total)
    if total <= 0:
        logger.warning("rejected order_id=%s reason=non_positive_total", order_id)
        return False
    logger.info("order_created order_id=%s", order_id)
    return True

create_order("A-17", 120)

شرح المثال

يستخدم basicConfig إعداداً بسيطاً مناسباً للمثال، ثم ينشئ logger باسم الوحدة. تمرير القيم كمعاملات منفصلة بدلاً من دمجها في f-string يسمح للمكتبة بتأجيل تنسيق الرسالة عندما لا تحتاجها. يوضح المثال الفرق بين معلومة عادية وتحذير يدل على رفض العملية. في تطبيق متعدد الوحدات، يمكن ضبط handlers من ملف إعداد مركزي، مع ترك كل وحدة مسؤولة عن الرسائل التي تعرفها.

ضع الإعداد في بداية التطبيق، ثم أضف سجلات عند بداية العملية ونهايتها وعند الفشل. شغل الكود بمستويات مختلفة، وراجع ما يظهر. بعد ذلك أنشئ FileHandler مع تدوير للملف أو أرسل السجل إلى نظام مركزي. اختبر أن الاستثناء يملك stack trace عند الحاجة، وأن الرسائل لا تحتوي على رمز دخول أو بيانات شخصية غير لازمة.

طريقة التفكير قبل التنفيذ

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

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

اختبار الفكرة في حالات مختلفة

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

  • تحقق من البيانات قبل تمريرها إلى الجزء الذي يعالجها.
  • استخدم رسائل خطأ تشرح المشكلة من دون كشف أسرار النظام.
  • اختبر المسار الناجح والمسارات التي تعيد نتيجة فارغة.
  • سجّل القرار الذي اتخذته عندما توجد أكثر من طريقة للحل.

ملاحظات على جودة المشروع

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

متى يحتاج الحل إلى تطوير؟

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

أخطاء ينبغي تجنبها

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

خطوة تالية مناسبة

أضف request_id إلى الرسائل حتى تتبع طلباً واحداً عبر أكثر من وحدة. جرّب تنسيق JSON عندما يقرأ النظام السجلات آلياً، وضع سياسة لحجب الحقول الحساسة قبل الإرسال.

السجلات المفيدة لا تعني سجلات أكثر

السجل الجيد يجيب عن سؤال: ماذا حدث ومتى ولماذا؟ استخدم مستوى مناسباً مثل INFO للأحداث الطبيعية وWARNING للحالات التي تحتاج متابعة وERROR للفشل. أضف معرف الطلب أو المهمة حتى تجمع الأسطر المرتبطة، لكن لا تضع كلمات المرور أو الرموز السرية أو بيانات شخصية كاملة داخل السجل. اجعل الرسائل قابلة للبحث، ووجهها إلى ملف أو خدمة مركزية في بيئة الإنتاج. راقب حجم السجلات، وحدد مدة الاحتفاظ بها، وجرّب البحث عن حادثة افتراضية قبل أن تعتمد التصميم. السجل ليس بديلاً عن القياسات، لكنه يعطي قصة مفيدة عند التحقيق.

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

خطة تطبيق عملية

بعد فهم موضوع «بناء نظام Logging منظم في تطبيقات Python»، لا تنقل المثال إلى مشروع كبير دفعة واحدة. أنشئ مجلداً صغيراً، وثبت نسخة الأدوات التي ستستخدمها، ثم اكتب حالة نجاح واحدة يمكن تشغيلها من البداية إلى النهاية. احتفظ بالمدخل الذي استخدمته والنتيجة التي توقعتها، لأن ذلك يمنحك نقطة مقارنة عندما تغير الكود. إذا كان الموضوع يعتمد على خدمة أو قاعدة بيانات، جهز بيانات تجريبية لا تحمل معلومات حقيقية، واكتب طريقة تنظيفها بعد انتهاء الاختبار.

انتقل بعد ذلك إلى الحالات التي تسبب الالتباس. ماذا يحدث عندما تكون القيمة فارغة؟ كيف يتصرف البرنامج عند وصول نوع غير متوقع؟ هل تعود رسالة مفيدة إذا توقفت الخدمة الخارجية أو لم يجد التطبيق السجل المطلوب؟ اكتب إجابة لكل سؤال في اختبار أو ملاحظة قصيرة. لا تحاول معالجة كل احتمال في سطر واحد؛ فصل المسارات يجعل التصحيح أسهل ويمنع إخفاء المشكلة خلف استثناء عام.

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

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

الخلاصة

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

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