Kodax

Logging وتتبع الأخطاء — Python المتقدم

التسجيل (Logging) وتتبع الأخطاء

print مناسبة أثناء التعلم، لكنها لا تكفي في برنامج حقيقي: لا تعرف وقت الرسالة، ولا مستوى أهميتها، ولا يمكنك إيقاف رسائل التصحيح في الإنتاج دون حذفها يدوياً. وحدة logging تحل كل ذلك.

المستويات

| المستوى | الاستخدام |

|---|---|

| DEBUG | تفاصيل للتشخيص أثناء التطوير |

| INFO | أحداث عادية: بدأ الخادم، تمت معالجة طلب |

| WARNING | شيء غير متوقع لكن البرنامج مستمر (الافتراضي الأدنى المعروض) |

| ERROR | فشلت عملية |

| CRITICAL | البرنامج قد لا يستطيع الاستمرار |

تحدد في الإعداد المستوى الأدنى المعروض، فتظهر رسائل DEBUG أثناء التطوير وتختفي في الإنتاج دون تعديل الكود.

الإعداد الأساسي

import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logger = logging.getLogger(__name__)
logger.info("بدأ التشغيل")

- استدعِ basicConfig مرة واحدة في بداية البرنامج الرئيسي، لا في كل وحدة.

- في كل وحدة: logger = logging.getLogger(__name__)، فتعرف من اسم المسجّل أي وحدة كتبت الرسالة.

تسجيل الاستثناءات

import logging
logger = logging.getLogger(__name__)
order_id, order = 7, {"qty": -1}

def process(order):
    raise ValueError("كمية سالبة")

try:
    process(order)
except ValueError:
    logger.exception("فشلت معالجة الطلب %s", order_id)

logger.exception تسجل بمستوى ERROR مع Traceback كاملاً، وهو أهم ما تحتاجه لاحقاً لمعرفة سبب المشكلة.

%s بدل f-string في الرسائل

logger.debug("المستخدم %s", user) أفضل من f-string لأن النص لا يُبنى إذا كان المستوى معطلاً، وأدوات تجميع السجلات تستطيع تجميع الرسائل المتشابهة.

المعالجات (Handlers)

السجلات يمكن أن تذهب لأكثر من مكان: الشاشة، ملف، ملف يتجدد عند حجم معين (RotatingFileHandler)، أو خدمة خارجية. لكل معالج مستواه وتنسيقه.

لا تسجل أسراراً

لا تكتب كلمات المرور أو التوكنات أو أرقام البطاقات في السجلات أبداً؛ ملفات السجلات تُقرأ وتُنسخ أكثر مما تتوقع.

أدوات تتبع أخرى

- breakpoint(): يوقف البرنامج في هذا السطر ويفتح مصحح pdb لتفحص المتغيرات خطوة بخطوة.

- traceback.format_exc(): نص الخطأ الحالي.

- مصحح المحرر (VS Code): نقاط توقف بالضغط بجانب السطر، وهو الأسهل للمبتدئين.

تمرين

أضف logging إلى مشروع تحليل المبيعات: INFO عند بدء ونهاية التحليل، WARNING لكل صف معطوب مع رقمه، و ERROR مع Traceback إذا لم يوجد الملف.

import logging
from logging.handlers import RotatingFileHandler
import traceback

# ===== إعداد بسيط =====
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

logger.debug("هذه رسالة debug")
logger.info("معلومات عامة")
logger.warning("تحذير!")
logger.error("خطأ حدث")
logger.critical("خطأ حرج!")

# ===== Logger مخصص =====
def setup_logger(name, log_file):
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    
    # Handler للملف
    file_handler = RotatingFileHandler(
        log_file,
        maxBytes=5*1024*1024,  # 5MB
        backupCount=3
    )
    file_handler.setLevel(logging.DEBUG)
    
    # Handler للـ Console
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    
    # التنسيق
    formatter = logging.Formatter(
        '%(asctime)s | %(name)s | %(levelname)s | %(message)s'
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)
    
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger

# استخدام
app_logger = setup_logger('myapp', 'app.log')

def process_data(data):
    app_logger.info(f"بدء معالجة البيانات: {len(data)} عنصر")
    try:
        # المعالجة...
        result = [x * 2 for x in data]
        app_logger.info("تمت المعالجة بنجاح")
        return result
    except Exception as e:
        app_logger.error(f"خطأ في المعالجة: {e}")
        app_logger.debug(traceback.format_exc())
        raise

# ===== Contextual Logging =====
class RequestLogger:
    def __init__(self, request_id):
        self.request_id = request_id
        self.logger = logging.getLogger('request')
    
    def log(self, level, message):
        self.logger.log(level, f"[{self.request_id}] {message}")

def handle_request(request):
    req_logger = RequestLogger(request['id'])
    req_logger.log(logging.INFO, "بدأ الطلب")
    # ...
    req_logger.log(logging.INFO, "انتهى الطلب")

# ===== تسجيل الاستثناءات =====
def risky_operation():
    try:
        result = 10 / 0
    except ZeroDivisionError:
        logger.exception("خطأ في العملية الحسابية")
        # يسجل الـ traceback كاملاً

# ===== JSON Logging =====
import json

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_data = {
            'timestamp': self.formatTime(record),
            'level': record.levelname,
            'message': record.getMessage(),
            'module': record.module
        }
        return json.dumps(log_data, ensure_ascii=False)
اختبر فهمك