شرح logging في بايثون: الأحداث والأخطاء | بعد الأساسيات 5

مكتبة logging في بايثون هي الطريقة القياسية لتسجيل أحداث البرنامج والأخطاء أثناء التشغيل بدل الاعتماد على print() للتشخيص. يمكنك تصنيف الرسائل إلى DEBUG وINFO وWARNING وERROR وCRITICAL، ثم عرضها في الطرفية أو حفظها داخل ملف .log مع الوقت واسم الجزء الذي أصدر الرسالة.

للسكربتات الصغيرة تبدأ غالبًا بـ logging.basicConfig()، وعندما يكبر المشروع تستخدم logging.getLogger(__name__) مع Handlers وFormatters لتنظيم وجهة السجلات وشكلها. وعند التقاط استثناء داخل except تساعدك logger.exception() على تسجيل الرسالة مع Traceback كامل للتشخيص.

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

شرح logging في بايثون لتسجيل أحداث البرنامج والأخطاء | بايثون بعد الأساسيات 5
{alertInfo} الفكرة ببساطة: استخدم print() عندما تريد عرض نتيجة عادية للمستخدم، واستخدم logging عندما تريد تسجيل ما يحدث داخل البرنامج لمتابعة الحالة أو تشخيص مشكلة أو حفظ سجل للأخطاء.

{getToc} $title={محتوى المقال}

ما هي مكتبة logging في بايثون؟

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

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

هذا مهم خصوصًا بعد أن بدأت في بناء مشاريع منظمة، مثل مشروع يتعامل مع ملفات CSV أو أداة سطر أوامر CLI. في هذه المرحلة لم يعد السؤال فقط: “هل يعمل البرنامج؟” بل: “ماذا حدث داخله عندما لم يعمل كما توقعت؟”

الفرق بين print وlogging

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

لا يعني هذا أنك يجب أن تحذف print() من كل مشروع. البرامج التي تتعامل مع المستخدم في الطرفية تحتاج رسائل واضحة مثل “تم الحفظ بنجاح”. أما logging فيعمل كدفتر ملاحظات تقني للمشروع والمطور.

مستويات الرسائل في logging

تستخدم logging مستويات مختلفة لتوضح مدى أهمية الحدث. الترتيب التالي يساعدك في اختيار المستوى المناسب:

المستوىمتى تستخدمه؟مثال
DEBUGتفاصيل دقيقة تساعدك أثناء التطوير والتشخيص.عدد الصفوف التي قرأها البرنامج من ملف CSV.
INFOحدث طبيعي مهم أثناء سير البرنامج.تم حفظ التقرير بنجاح.
WARNINGحدث غير مثالي، لكن البرنامج ما زال قادرًا على الاستمرار.الملف غير موجود، لذلك سيتم إنشاء ملف جديد.
ERRORفشل حدث محدد ويحتاج إلى مراجعة.تعذر قراءة البيانات بسبب تنسيق غير صالح.
CRITICALمشكلة كبيرة قد تمنع البرنامج من العمل بشكل أساسي.تعذر بدء التطبيق لأن ملف الإعدادات الرئيسي تالف.
{alertInfo} مهم: إذا لم تضبط مستوى التسجيل، فإن الـ root logger يستخدم WARNING افتراضيًا؛ لذلك قد لا ترى رسائل DEBUG وINFO. استخدم basicConfig(level=logging.INFO) أو logging.DEBUG عندما تحتاج مستوى أدنى.

مستويات logging في بايثون من DEBUG إلى CRITICAL

أول مثال: إعداد logging باستخدام basicConfig()

ابدأ باستيراد المكتبة، ثم استخدم basicConfig() لتحديد أقل مستوى تريد إظهاره. في المثال التالي سنعرض رسائل INFO وما فوقها:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(levelname)s - %(message)s"
)

logging.debug("هذه رسالة تفصيلية للتشخيص")
logging.info("تم تشغيل البرنامج بنجاح")
logging.warning("لم يتم العثور على صورة المستخدم")

لن تظهر رسالة DEBUG هنا، لأننا حددنا المستوى INFO. لكن رسالتي INFO وWARNING ستظهران.

INFO - تم تشغيل البرنامج بنجاح
WARNING - لم يتم العثور على صورة المستخدم

جرّب تغيير level=logging.INFO إلى level=logging.DEBUG ثم شغّل الكود مرة أخرى. ستظهر الرسالة التفصيلية أيضًا.

إضافة الوقت إلى السجل

عندما تتكرر عمليات البرنامج، تصبح معرفة وقت الحدث مهمة. يمكنك إضافة التاريخ والوقت إلى شكل السجل باستخدام %(asctime)s:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

logging.info("بدأت عملية إنشاء التقرير")
logging.info("انتهت عملية إنشاء التقرير")

قد يكون الناتج بهذا الشكل:

2026-07-02 20:15:03 | INFO | بدأت عملية إنشاء التقرير
2026-07-02 20:15:04 | INFO | انتهت عملية إنشاء التقرير

الوقت الظاهر يعتمد على وقت جهازك أو بيئة التشغيل التي يعمل عليها البرنامج.

كيف تحفظ السجلات داخل ملف؟

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

import logging

logging.basicConfig(
    filename="app.log",
    filemode="a",
    encoding="utf-8",
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s"
)

logging.info("بدأ البرنامج")
logging.warning("تم استخدام قيمة افتراضية")
logging.error("تعذر حفظ التقرير")

بعد تشغيل الملف، سيظهر بجانبه ملف جديد باسم app.log. استخدمنا filemode="a" لإضافة السجلات الجديدة إلى نهاية الملف؛ وهذا هو الوضع الافتراضي عند الكتابة إلى ملف عبر basicConfig(). وإذا استخدمت filemode="w" فسيبدأ الملف من جديد عند كل تشغيل.

تسجيل رسائل بايثون في الطرفية وملف log باستخدام logging
{alertWarning} ملاحظة مهمة: لا تضع basicConfig() في ملفات كثيرة داخل المشروع. الأفضل أن تضبط إعدادات logging مرة واحدة عند بداية تشغيل التطبيق، ثم تستخدم التسجيل في بقية الملفات.

استخدام logger باسم الملف

في المشروع الصغير يمكن استعمال logging.info() مباشرة. لكن عندما يصبح المشروع من عدة ملفات، خصوصًا بعد تنظيم ملفات مشروع بايثون، فمن الأفضل إنشاء logger باسم كل ملف. الطريقة الشائعة هي استخدام __name__:

import logging

logger = logging.getLogger(__name__)

logger.info("تمت قراءة ملف البيانات")
logger.warning("بعض الصفوف تحتوي على قيم ناقصة")

وعند ضبط التنسيق، يمكنك إظهار اسم logger أيضًا:

format="%(asctime)s | %(name)s | %(levelname)s | %(message)s"

هذا يصبح مفيدًا عندما تريد معرفة أي جزء من المشروع سجل الرسالة: ملف قراءة البيانات، أو ملف الحسابات، أو ملف واجهة سطر الأوامر.

ما الفرق بين Logger وHandler وFormatter؟

عندما تتجاوز الإعداد البسيط، يكفي أن تفهم ثلاث قطع رئيسية في نظام التسجيل:

المكوّنوظيفتهمثال
Loggerيستقبل رسائل التسجيل من الكود.logging.getLogger(__name__)
Handlerيحدد وجهة الرسائل، مثل الطرفية أو ملف.StreamHandler وFileHandler
Formatterيحدد شكل الرسالة وترتيب الوقت والمستوى والاسم.logging.Formatter(...)

عرض السجلات في الطرفية وحفظها في ملف معًا

إذا احتجت وجهتين في الوقت نفسه، يمكنك إضافة أكثر من Handler إلى logger واحد:

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

file_handler = logging.FileHandler(
    "app.log",
    encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)

formatter = logging.Formatter(
    "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
)

console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

logger.addHandler(console_handler)
logger.addHandler(file_handler)

logger.debug("تفاصيل تظهر في الملف فقط")
logger.info("رسالة تظهر في الطرفية والملف")

في هذا المثال، يسجل الملف رسائل DEBUG وما فوقها، بينما تعرض الطرفية رسائل INFO وما فوقها. في تطبيق حقيقي اضبط الـHandlers مرة واحدة عند بدء التطبيق حتى لا تضيفها مرتين وتكرر الرسائل.

تسجيل الأخطاء مع try و except

تعلمت سابقًا استخدام try وexcept للتعامل مع الأخطاء حتى لا يتوقف البرنامج فجأة. الآن يمكنك جعل التعامل مع الخطأ أكثر فائدة عبر تسجيله أيضًا.

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

import json
import logging
from pathlib import Path

logging.basicConfig(
    filename="app.log",
    encoding="utf-8",
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s"
)

logger = logging.getLogger(__name__)
file_path = Path("data.json")

try:
    content = file_path.read_text(encoding="utf-8")
    data = json.loads(content)
    logger.info("تمت قراءة البيانات بنجاح")
    print(data)

except FileNotFoundError:
    logger.warning("ملف data.json غير موجود")
    print("لم يتم العثور على ملف البيانات.")

except json.JSONDecodeError:
    logger.exception("محتوى ملف JSON غير صالح")
    print("محتوى ملف البيانات غير صحيح.")

الدالة logger.exception() تستخدم داخل except عندما تريد تسجيل رسالة الخطأ مع Traceback كامل. وإذا أردت فهم ما تعنيه أسطر الـTraceback نفسها، راجع شرح أخطاء بايثون وطريقة قراءة رسائل الخطأ. هذه التفاصيل مفيدة للتشخيص، لكنها ليست نصًا مناسبًا لعرضه كاملًا للمستخدم النهائي.

استخدام logger.exception مع try وexcept في بايثون لتسجيل Traceback

مشروع صغير: سجل عمليات برنامج مهام

لنطبق الفكرة في مثال قريب من المشاريع الواقعية. البرنامج التالي يضيف مهمة إلى ملف نصي، ثم يسجل نجاح العملية أو فشلها في ملف logs/app.log.

هيكل المشروع

task_manager/
│
├── main.py
├── tasks.txt
└── logs/
    └── app.log

ملف main.py

import logging
from pathlib import Path

BASE_DIR = Path(__file__).parent
LOGS_DIR = BASE_DIR / "logs"
TASKS_FILE = BASE_DIR / "tasks.txt"

LOGS_DIR.mkdir(exist_ok=True)

logging.basicConfig(
    filename=LOGS_DIR / "app.log",
    encoding="utf-8",
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s"
)

logger = logging.getLogger(__name__)

task = input("اكتب المهمة الجديدة: ").strip()

if not task:
    logger.warning("حاول المستخدم إضافة مهمة فارغة")
    print("لا يمكن إضافة مهمة فارغة.")
else:
    try:
        with TASKS_FILE.open("a", encoding="utf-8") as file:
            file.write(task + "\n")

        logger.info("تمت إضافة مهمة جديدة")
        print("تمت إضافة المهمة بنجاح.")

    except OSError:
        logger.exception("فشلت عملية حفظ المهمة")
        print("تعذر حفظ المهمة. حاول مرة أخرى.")

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

تنظيم ملفات logging داخل مشروع بايثون وحفظ السجلات في مجلد logs

أخطاء شائعة عند استخدام logging

1. توقع ظهور DEBUG أو INFO دون تغيير المستوى

المستوى الفعّال الافتراضي للـroot logger هو WARNING، لذلك لا تظهر رسائل DEBUG وINFO عادةً قبل ضبط المستوى. إذا كنت تحتاج التفاصيل أثناء التشخيص، استخدم مثلًا level=logging.DEBUG.

2. استدعاء basicConfig أكثر من مرة دون تنظيم

في أغلب المشاريع، اضبط الإعدادات عند بداية البرنامج فقط. ثم أنشئ loggers في الملفات الأخرى باستخدام getLogger(__name__).

3. توقع أن basicConfig سيعيد الإعداد في كل مرة

عادةً لا يعيد basicConfig() ضبط الـroot logger إذا كانت لديه Handlers مضافة مسبقًا. لهذا اجعل الإعداد الأساسي مرة واحدة عند بدء التطبيق. توجد خيارات متقدمة مثل force=True لإعادة التهيئة عند الحاجة، لكن لا تستخدمها بدل تنظيم الإعداد الصحيح.

4. استخدام logging بدل الاستثناءات

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

5. تسجيل بيانات حساسة

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

6. جعل السجل مليئًا برسائل غير مفيدة

لا تسجل كل سطر من البرنامج. سجل القرارات والعمليات المهمة: بداية مهمة، نجاح عملية، تحذير يستحق المراجعة، أو خطأ يحتاج تشخيصًا.

متى تستخدم logging في مشاريعك؟

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

ماذا بعد تعلم logging؟

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

الخلاصة

  • print() مناسب لعرض نتائج عادية للمستخدم، بينما logging مناسب لتسجيل أحداث البرنامج وتشخيص المشكلات.
  • استخدم مستويات الرسائل لتوضيح أهمية الحدث: DEBUG وINFO وWARNING وERROR وCRITICAL.
  • يمكنك حفظ السجلات داخل ملف .log ومراجعته عند حدوث مشكلة لاحقًا.
  • استخدم logger.exception() داخل except عندما تريد تسجيل تفاصيل الاستثناء.
  • اضبط إعدادات التسجيل مرة واحدة عند بداية المشروع، وتجنب كتابة بيانات حساسة في ملفات السجل.
{alertSuccess} خطوتك التالية: افتح مشروعًا صغيرًا كتبته سابقًا، وأضف سجلًا واحدًا لبدء البرنامج، وسجلًا لنجاح عملية أساسية، وسجلًا للأخطاء داخل except. بهذه الخطوة يتحول مشروعك من مثال تعليمي إلى برنامج أسهل في المتابعة والصيانة.

مصادر خارجية رسمية للتوسع

أسئلة شائعة

هل أحتاج إلى تثبيت logging باستخدام pip؟

لا. مكتبة logging جزء من مكتبة بايثون القياسية، لذلك يكفي أن تكتب import logging.

هل أستبدل print بالكامل بـ logging؟

لا. استخدم print() للرسائل والنتائج التي يجب أن يراها مستخدم البرنامج مباشرة. واستخدم logging لتسجيل أحداث التشغيل والتفاصيل التقنية والأخطاء.

أين أضع basicConfig في المشروع؟

ضعه عادةً في الملف الذي يبدأ تشغيل التطبيق، مثل main.py. لا تكرر الإعدادات في كل ملف، ثم استخدم getLogger(__name__) داخل الملفات الأخرى.

هل يمكن أن أحفظ السجلات في ملف وعرضها في الطرفية معًا؟

نعم. استخدم StreamHandler للطرفية وFileHandler للملف، ثم أضف الاثنين إلى logger نفسه. ويمكنك تحديد مستوى مختلف لكل Handler.

هل logger.exception مختلف عن logger.error؟

نعم. داخل كتلة except، تستخدم logger.exception() عندما تريد تسجيل الرسالة مع تفاصيل الاستثناء. أما logger.error() فيسجل رسالة خطأ فقط ما لم تمرر له معلومات إضافية.

إرسال تعليق

أحدث أقدم