مكتبة logging في بايثون هي الطريقة القياسية لتسجيل أحداث البرنامج والأخطاء أثناء التشغيل بدل الاعتماد على print() للتشخيص. يمكنك تصنيف الرسائل إلى DEBUG وINFO وWARNING وERROR وCRITICAL، ثم عرضها في الطرفية أو حفظها داخل ملف .log مع الوقت واسم الجزء الذي أصدر الرسالة.
للسكربتات الصغيرة تبدأ غالبًا بـ logging.basicConfig()، وعندما يكبر المشروع تستخدم logging.getLogger(__name__) مع Handlers وFormatters لتنظيم وجهة السجلات وشكلها. وعند التقاط استثناء داخل except تساعدك logger.exception() على تسجيل الرسالة مع Traceback كامل للتشخيص.
هذا هو الدرس 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 باستخدام 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" فسيبدأ الملف من جديد عند كل تشغيل.
{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 نفسها، راجع شرح أخطاء بايثون وطريقة قراءة رسائل الخطأ. هذه التفاصيل مفيدة للتشخيص، لكنها ليست نصًا مناسبًا لعرضه كاملًا للمستخدم النهائي.
مشروع صغير: سجل عمليات برنامج مهام
لنطبق الفكرة في مثال قريب من المشاريع الواقعية. البرنامج التالي يضيف مهمة إلى ملف نصي، ثم يسجل نجاح العملية أو فشلها في ملف 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
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() فيسجل رسالة خطأ فقط ما لم تمرر له معلومات إضافية.



