كيفية استخدام التسجيل في Python 3

مقدمة

يعتبر وحدة التسجيل (logging) جزءًا من مكتبة بيثون القياسية وتوفر تتبعًا للأحداث التي تحدث أثناء تشغيل البرمجيات. يمكنك إضافة استدعاءات لتسجيل الحدث إلى كودك للإشارة إلى الأحداث التي قد حدثت.

تتيح وحدة التسجيل (logging) كلاً من تسجيل العمليات التشخيصية التي تسجل الأحداث المتعلقة بعملية التطبيق، وكذلك تسجيل المراجعة التي تسجل أحداث معاملات المستخدم للتحليل. يُستخدم بشكل خاص لتسجيل الأحداث في ملف.

المتطلبات المسبقة

يجب أن يكون لديك بيثون 3 مثبتًا وبيئة برمجة مهيأة على جهاز الكمبيوتر الخاص بك أو الخادم. إذا لم يكن لديك بيئة برمجة مهيأة، يمكنك الرجوع إلى دليل التثبيت والإعداد لـ بيئة برمجة محلية أو لـ بيئة برمجة على خادمك مناسبة لنظام التشغيل الخاص بك (أوبونتو، سنتوس، ديبيان، إلخ.).

لماذا استخدام وحدة logging

تحتفظ وحدة logging بسجل للأحداث التي تحدث داخل البرنامج، مما يجعل من الممكن رؤية الإخراج المتعلق بأي من الأحداث التي تحدث طوال وقت تشغيل قطعة من البرمجيات.

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

  • يصبح من الصعب التمييز بين الإخراج لتصحيح الأخطاء والإخراج العادي للبرنامج لأن الاثنين مختلطين
  • عند استخدام بيانات print() المتناثرة في جميع أنحاء الكود، لا يوجد طريقة فعالة لتعطيل تلك التي توفر إخراجًا لتصحيح الأخطاء
  • يصبح من الصعب إزالة جميع بيانات print() عند الانتهاء من عملية تصحيح الأخطاء
  • لا يوجد سجل سجل يحتوي على معلومات تشخيصية متاحة بسهولة

من الفكرة الجيدة العادة باستخدام وحدة logging في كودك حيث أن هذا أكثر ملاءمة لتطبيقات تتجاوز النصوص البسيطة في Python ويوفر نهجًا مستدامًا لتصحيح الأخطاء.

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

طباعة رسائل التصحيح إلى وحدة التحكم

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

إذا كنت معتادًا على استخدام البيانات المطبوعة (print()) لرؤية ما يحدث في برنامج ما، قد تكون معتادًا على رؤية برنامج يعرّف فئةً وينشئ كائنات تُولّد شيئًا مثل هذا:

pizza.py
class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

الكود أعلاه يحتوي على طريقة __init__ لتحديد name و price لكائن من فئة Pizza. بعد ذلك يحتوي على طريقتين، الأولى تسمى make() لصنع البيتزا، والثانية تسمى eat() لأكل البيتزا. تأخذ هاتان الطريقتان معلمة quantity، التي تُبدأ بقيمة 1.

الآن دعونا نشغل البرنامج:

  1. python pizza.py

سنتلقى الناتج التالي:

Output
Pizza created: artichoke ($15) Made 1 artichoke pizza(s) Ate 1 pizza(s) Pizza created: margherita ($12) Made 2 margherita pizza(s) Ate 1 pizza(s)

بينما تسمح العبارة print() لنا برؤية أن الشفرة تعمل، يمكننا استخدام وحدة logging بدلاً من ذلك.

لنقم بإزالة أو تعليق العبارات print() في جميع أنحاء الشفرة، وإضافة import logging إلى أعلى الملف:

pizza.py
import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

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

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

هذا المستوى من logging.DEBUG يشير إلى قيمة صحيحة ثابتة نشير إليها في الشفرة أعلاه لتعيين عتبة. مستوى DEBUG هو 10.

الآن، سنقوم بتبديل جميع العبارات print() بعبارات logging.debug() بدلاً من ذلك. على عكس logging.DEBUG الذي هو ثابت، فإن logging.debug() هو أسلوب من وحدة logging. عند العمل بهذا الأسلوب، يمكننا الاستفادة من نفس النص المرسل إلى print()، كما هو موضح في ما يلي:

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

في هذه النقطة، عند تشغيل البرنامج بالأمر python pizza.py، سنتلقى هذا الإخراج:

Output
DEBUG:root:Pizza created: artichoke ($15) DEBUG:root:Made 1 artichoke pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: margherita ($12) DEBUG:root:Made 2 margherita pizza(s) DEBUG:root:Ate 1 pizza(s)

تحتوي رسائل السجل على مستوى الخطورة DEBUG بالإضافة إلى الكلمة root مضمنة فيها، والتي تشير إلى مستوى وحدة Python الخاصة بك. يمكن استخدام وحدة logging مع تسلسل هرمي من السجلات التي تحمل أسماءً مختلفة، بحيث يمكنك استخدام سجل مختلف لكل من وحداتك.

على سبيل المثال، يمكنك تعيين السجلات مساوية لسجلات مختلفة تحمل أسماء مختلفة ومخرجات مختلفة:

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
Output
DEBUG:module_1:Module 1 debugger DEBUG:module_2:Module 2 debugger

الآن بعد أن فهمنا كيفية استخدام وحدة logging لطباعة الرسائل إلى وحدة التحكم، دعونا ننتقل إلى استخدام وحدة logging لطباعة الرسائل إلى ملف.

تسجيل الرسائل إلى ملف

الغرض الرئيسي من وحدة logging هو تسجيل الرسائل إلى ملف بدلاً من وحدة التحكم. يوفر الاحتفاظ بملف الرسائل لديك بيانات مع الوقت يمكنك الرجوع إليها وقياسها بحيث يمكنك رؤية التغييرات التي يجب إجراؤها على كودك.

للبدء في تسجيل الرسائل إلى ملف، يمكننا تعديل طريقة logging.basicConfig() لتضمين معلمة filename. في هذه الحالة، دعونا نسمي اسم الملف test.log:

pizza.py
import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

الكود أعلاه هو نفسه كما كان في القسم السابق، باستثناء أننا أضفنا الآن اسم الملف للسجل ليتم طباعته. بمجرد تشغيل الكود بأمر python pizza.py، يجب أن يكون لدينا ملف جديد في دليلنا يسمى test.log.

لنفتح ملف test.log باستخدام nano (أو محرر النص الذي تختاره):

  1. nano test.log

عندما يتم فتح الملف، سنحصل على ما يلي:

test.log
DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

هذا مشابه لمخرجات الوحدة التحكم التي واجهناها في القسم السابق، ولكن الآن في ملف test.log.

لنغلق الملف بالضغط على CTRL + x وننتقل مرة أخرى إلى ملف pizza.py لنقوم بتعديل الكود.

سنحتفظ بالجزء الأكبر من الكود بنفسه، ولكن سنعدل المعلمات في حالتي البيتزا، pizza_01 و pizza_02:

pizza.py
import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# قم بتعديل معلمات الكائن pizza_01
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# قم بتعديل معلمات الكائن pizza_02
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

مع هذه التغييرات، لنقم بتشغيل البرنامج مرة أخرى باستخدام الأمر python pizza.py.

بمجرد تشغيل البرنامج، يمكننا فتح ملف test.log مرة أخرى باستخدام nano:

  1. nano test.log

عندما نراجع الملف، سنرى أن تمت إضافة عدة أسطر جديدة، وأن الأسطر السابقة من آخر مرة تم فيها تشغيل البرنامج تم الاحتفاظ بها:

test.log
DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

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

يمكننا إضافة ذلك السمة إلى معامل يسمى format، مشيرين إليه كما هو موضح في الجدول بالسلسلة %(asctime)s. بالإضافة إلى ذلك، للحفاظ على اسم مستوى التصحيح DEBUG، سنحتاج إلى تضمين السلسلة %(levelname)s وللحفاظ على الرسالة التي نطلب من المسجل طباعتها، سنضمن %(message)s. سيتم فصل كل من هذه السمات بواسطة colon، كما هو موضح في الكود المضاف:

pizza.py
import logging

logging.basicConfig(
    filename="test.log",
    level=logging.DEBUG,
    format="%(asctime)s:%(levelname)s:%(message)s"
    )


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

عند تشغيل الكود أعلاه مع السمات المضافة باستخدام الأمر python pizza.py، سنحصل على أسطر جديدة مُضافة إلى ملف test.log الخاص بنا تتضمن الطابع الزمني القابل للقراءة بالإضافة إلى اسم المستوى DEBUG والرسائل المرتبطة التي يتم تمريرها إلى المسجل كسلاسل.

test.log
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: Sicilian ($18)
2021-08-19 23:31:34,484:DEBUG:Made 5 Sicilian pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 4 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: quattro formaggi ($16)
2021-08-19 23:31:34,484:DEBUG:Made 2 quattro formaggi pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 2 pizza(s)

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

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

جدول المستويات الخاص بالتسجيل

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

مستويات تسجيل الأحداث هي عبارة عن أعداد صحيحة (ثوابت)، وهي كلها بزيادات قدرها 10، بدءًا من NOTSET الذي يبدأ السجل عند القيمة الرقمية 0.

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

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

Level Numeric Value Function Used to
CRITICAL 50 logging.critical() Show a serious error, the program may be unable to continue running
ERROR 40 logging.error() Show a more serious problem
WARNING 30 logging.warning() Indicate something unexpected happened, or could happen
INFO 20 logging.info() Confirm that things are working as expected
DEBUG 10 logging.debug() Diagnose problems, show detailed information

يقوم وحدة logging بتعيين المستوى الافتراضي عند WARNING، لذا فإن WARNING و ERROR و CRITICAL ستتم تسجيلها بشكل افتراضي. في المثال أعلاه، قمنا بتعديل التكوين لتضمين مستوى DEBUG باستخدام الكود التالي:

logging.basicConfig(level=logging.DEBUG)

يمكنك قراءة المزيد عن الأوامر والعمل مع مصحح الأخطاء من التوثيق الرسمي لـ logging.

الاستنتاج

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

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-logging-in-python-3