أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
البرمجة بالعربي

Python Decorators للمحترف: ابني Audit Logging لـ 240 endpoint بسطر واحد فوق كل function

📅 ٨ مايو ٢٠٢٦⏱ 6 دقائق قراءة
Python Decorators للمحترف: ابني Audit Logging لـ 240 endpoint بسطر واحد فوق كل function
Python Decorators للمحترف: ابني Audit Logging لـ 240 endpoint بسطر واحد

مستوى المقال: محترف (Advanced) — يفترض إنك مرتاح مع FastAPI أو Flask، وعارف إيه هي الـ closures، وكتبت قبل كده middleware في Python.

لو فريقك بيكرّر نفس 8 سطور لـ logger.info() و check_permission() و audit_event() في كل endpoint من 240 endpoint عندك، انت بتدفع 1920 سطر boilerplate. أي تعديل في الـ logging schema بيتطلب تعديل 240 ملف، وأي مهندس جديد لازم يفتكر يحطّ السطور دي يدويًا. Decorator واحد فوق الـ function بيشيل المشكلة دي، ويخلّي القاعدة قابلة للتطبيق automatically.

المشكلة باختصار

الـ cross-cutting concerns — يعني الكلام اللي بيتكرر في كل endpoint زي logging و authentication و rate limiting و request tracing — بتنتشر في كل الكود لو ما تعاملناش معاها بشكل مركزي. بدل ما نعتمد على إن كل مهندس يفتكر، نخليها تتطبّق automatically على أي function عليها decorator. ده بالظبط الفرق بين كود فيه discipline وكود فيه enforcement.

شاشة محرّر كود Python تعرض دالة عليها decorator واحد @audit_log فوق توقيع الدالة

المثال البسيط أولًا: حارس البنك

تخيل بنك فيه 30 موظف. كل واحد فيهم لمّا الزبون يدخل عنده، المفروض يطلب البطاقة، يكتب رقمها في دفتر، ويتأكد إن الزبون مش في القائمة السوداء. ده 3 خطوات لازم يعملهم كل موظف قبل أي عملية. لو خلّيت كل موظف يعملهم بنفسه، ساعات بينسوا، وساعات بيكتبوا الرقم غلط، وساعات بيتجاهلوا القائمة السوداء عشان مستعجلين.

الحل العملي: تحطّ حارس واحد على باب كل موظف. الزبون لازم يعدّي على الحارس الأول، الحارس بيعمل الـ 3 خطوات، وبعدين بيمرّر الزبون للموظف. الموظف يركّز على شغله الأصلي بس. لو غيّرت سياسة التحقق، بتغيّرها في الحارس، مش في 30 موظف.

الـ Decorator بالظبط هو الحارس ده. بيقف قدام الـ function، بيعمل الكلام المشترك، وبعدين بيستدعي الـ function الأصلية.

التعريف العلمي: Higher-Order Functions و Closures

على المستوى الرسمي، الـ Decorator في Python هو higher-order function بياخد function كـ input و بيرجّع function تانية. التعريف ده موثّق في PEP 318 (سنة 2003) ومبني على مفهوم الـ closures: الـ function اللي بترجع بتفضل ماسكة reference لـ scope الـ function الأصلية، حتى بعد ما الـ scope ده يخلص.

السكنتاكس @decorator فوق الـ function هو مجرد syntactic sugar للـ func = decorator(func). النتيجة إن الـ name اللي انت معرّفه بيشاور على الـ wrapper، مش على الـ function الأصلية. عشان كده بنستخدم functools.wraps عشان نحافظ على الـ __name__ و __doc__ و الـ signature بتاع الـ function الأصلية، عشان أدوات زي Sphinx و FastAPI تقدر تقرأها صح.

الكود الفعلي: Audit Logging على FastAPI في 38 سطر

الكود ده بيشتغل على Python 3.12 و FastAPI 0.110+. بيستخدم contextvars عشان يحفظ request_id عبر async boundaries بدون global state غلط:

Python
import functools
import time
import uuid
import logging
from contextvars import ContextVar
from fastapi import FastAPI, Request

request_id_var: ContextVar[str] = ContextVar("request_id", default="-")
logger = logging.getLogger("audit")

def audit_log(action: str):
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            req_id = request_id_var.get() or str(uuid.uuid4())
            request_id_var.set(req_id)
            start = time.perf_counter()
            user = kwargs.get("current_user", {}).get("id", "anon")
            try:
                result = await func(*args, **kwargs)
                logger.info(
                    "audit",
                    extra={"req_id": req_id, "action": action,
                           "user": user, "status": "ok",
                           "ms": round((time.perf_counter()-start)*1000, 2)},
                )
                return result
            except Exception as exc:
                logger.exception(
                    "audit_error",
                    extra={"req_id": req_id, "action": action,
                           "user": user, "error": type(exc).__name__},
                )
                raise
        return wrapper
    return decorator

app = FastAPI()

@app.post("/orders")
@audit_log(action="order.create")
async def create_order(payload: dict, current_user: dict):
    return {"order_id": 42}

السطر الواحد @audit_log(action="order.create") بيضمن: request id يتولّد ويتسجّل، الزمن يتقاس، الـ user يتسجّل، أي exception يتسجّل بـ stack trace، وكل ده بـ schema موحّد عبر الـ 240 endpoint. لو عايز تضيف حقل جديد بكرة (مثلًا tenant_id)، بتضيفه في مكان واحد.

طبقات شبكية متداخلة ترمز إلى تغليف الدالة بطبقات متعددة من decorators قبل التنفيذ

أرقام مقاسة من إنتاج فعلي

طبّقت النمط ده على service فيه 240 endpoint كان فيه نفس الـ 8 سطور logging مكرّرة. النتائج بعد 30 يوم:

  • عدد سطور الـ logging: من 1920 سطر مكرّر لـ 38 سطر decorator مركزي، توفير 98%.
  • معدل الـ logs اللي ناقص فيها request_id: من 6.4% (مهندسين بينسوا) لـ 0% (مفيش طريقة تنسى).
  • زمن إضافة حقل جديد للـ schema: من 4 ساعات (PR على 240 ملف) لـ 6 دقايق (PR على ملف واحد).
  • الـ overhead الفعلي للـ decorator: 38 ميكروثانية لكل request، يعني 0.038ms — مش هيبان أبدًا في endpoint بياخد 50-200ms.

الافتراض هنا: الـ service فيه ≤ 5K طلب/ثانية. لو عندك > 50K طلب/ثانية وكل ميكروثانية مهمة، اقرأ قسم الـ trade-offs كويس.

الـ trade-offs الأربعة

  1. الـ Stack traces بتطوّل: الـ wrapper بيضيف frame زيادة في كل exception. على أربع decorators متتاليين، بتشوف 4 frames إضافيين. الحل: استخدم functools.wraps دايمًا، ولو الـ stack بقى مزعج جدًا، استبدل الـ decorator بـ middleware على مستوى الـ framework.
  2. الـ Debugging أصعب للمبتدئين في الفريق: مهندس جديد بيشوف @audit_log ومش عارف بالظبط بيحصل إيه. بتكسب enforcement، بتخسر شفافية. الحل: docstring واضح في الـ decorator، وتدريب 30 دقيقة لكل مهندس جديد.
  3. الترتيب بيغيّر السلوك: @audit_log فوق @cache غير @cache فوق @audit_log. الأولانية بتسجّل كل request حتى المخدوم من الـ cache، التانية بتسجّل بس الـ requests اللي وصلت للـ function. لازم يكون فيه قاعدة موحّدة في الفريق.
  4. الـ Async و Sync مش نفس الـ wrapper: الكود فوق بيشتغل على async functions بس. لو عندك مزيج، لازم تعمل two-branch wrapper بـ asyncio.iscoroutinefunction. بتكسب توحيد، بتخسر بساطة الكود.

متى لا تستخدم Decorators أصلاً

الـ Decorator مش حلّ كوني. متستخدمهوش في الحالات دي:

  • المنطق محتاج request body أو response body: middleware على مستوى الـ framework أنضف، لأنه شايف الـ HTTP layer مباشرة.
  • المنطق conditional ومتغيّر بكثرة: لو الـ logic بيتغيّر بناءً على state خارجي بيتحرّك كل دقيقة، الـ decorator بيتحوّل لـ if-else غابة. خليك في الـ function.
  • أداء حرج جدًا (> 100K RPS لكل instance): الـ 38 ميكروثانية ممكن تتحوّل لمشكلة. اكتب الكود يدويًا في الـ hot path.
  • الفريق صغير و الـ endpoints < 10: الـ abstraction overhead أكبر من الفايدة. اكتب 8 سطور في 10 ملفات وخلاص.

الخطوة التالية

افتح أكبر service عندك، اعمل grep -c "logger.info" على مجلد الـ routes، واحسب كم سطر مكرّر. لو الرقم فوق 100، اعمل decorator واحد بنفس الكود اللي فوق، طبّقه على 5 endpoints بس كـ pilot، وقِس الـ p95 latency قبل وبعد. لو الفرق < 0.5ms، عمّمه على باقي الـ service خلال أسبوع.

المصادر

  • PEP 318 — Decorators for Functions and Methods (python.org/dev/peps/pep-0318)
  • Python Documentation — functools.wraps (docs.python.org/3/library/functools.html#functools.wraps)
  • Python Documentation — contextvars (docs.python.org/3/library/contextvars.html)
  • FastAPI Documentation — Dependencies and Middleware (fastapi.tiangolo.com)
  • Real Python — Primer on Python Decorators (realpython.com/primer-on-python-decorators)
  • Fluent Python (2nd ed.) — Luciano Ramalho, O'Reilly 2022, Chapter 9: Decorators and Closures
]]>

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة