سجلاتي كانت بلا فائدة عند الطوارئ: كيف أنقذني ‘التسجيل المنظم’ (Structured Logging) من جحيم التنقيح الأعمى؟

يا جماعة الخير، اسمحوا لي أن أروي لكم حكاية صارت معي قبل كم سنة، يوم كنت لا أزال أعتبر أن تسجيل الأخطاء (Logging) مجرد طباعة رسائل نصية في ملف. كنت قاعد بأمان الله في مكتبي، أحتسي فنجان القهوة وأخطط للميزة الجديدة في نظامنا. فجأة، بدأت الإشعارات تنهال على هاتفي كالمطر: “النظام لا يعمل!”، “لا أستطيع إتمام عملية الدفع!”، ورسالة من مديري مباشرة: “أبو عمر، شو القصة؟ الموقع واقع!”.

شعرت بالدم يغلي في عروقي. تركت كل شيء وركضت إلى شاشة السجلات (Logs). فتحت الملف، وإذا بي أرى سيلاً من الأسطر غير المفهومة… كارثة حقيقية. أسطر مثل:


INFO: User logged in.
ERROR: Failed to process request.
INFO: Data saved successfully.
ERROR: Something went wrong.

يا ويلي! أي مستخدم؟ أي طلب؟ ما هو الخطأ بالضبط؟ كانت السجلات مثل شخص يصرخ في وجهك بكلمات متقطعة بلا أي سياق. كنت كمن يبحث عن إبرة في كومة قش، أو كما نقول “بدور على جمل أسود بليلة ظلما”. قضيت ساعات طويلة في “تنقيح أعمى” (Blind Debugging)، أحاول تخمين المشكلة وتتبعها يدوياً. كانت تلك الليلة من أسوأ ليالي حياتي المهنية، والدرس الذي تعلمته فيها كان قاسياً لكنه ثمين: الطريقة التقليدية في تسجيل البيانات هي وصفة لكارثة محققة.

من رحم تلك المعاناة، وُلد اهتمامي بما يُعرف بـ “التسجيل المُنظّم” أو Structured Logging، وهو المفهوم الذي أنقذني -وأنقذ فريقي- من جحيم التنقيح العشوائي. دعوني أشرح لكم بالتفصيل.


ما هو التسجيل (Logging) أصلاً؟ ولماذا هو مهم؟

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

  • التنقيح (Debugging): عندما يحدث خطأ، تكون السجلات هي أول مكان نلجأ إليه لنفهم ماذا حدث، ولماذا.
  • المراقبة (Monitoring): تمكننا السجلات من مراقبة صحة النظام وأدائه في الوقت الفعلي.
  • التدقيق (Auditing): توفر السجلات سجلاً تاريخياً للإجراءات المهمة، مثل من قام بتعديل بيانات حساسة ومتى.

المشكلة الكبرى: التسجيل غير المنظم (Unstructured Logging)

المشكلة التي واجهتها في تلك الليلة المشؤومة كانت بسبب اعتمادنا على “التسجيل غير المنظم”. هذا هو الأسلوب الافتراضي الذي يبدأ به معظم المبرمجين.

ما هو التسجيل غير المنظم؟

هو ببساطة كتابة رسائل نصية حرة (Plain Text) إلى ملف أو إلى الشاشة. الهدف منها أن يقرأها الإنسان فقط.

مثلاً، في كود بايثون، قد يبدو الأمر هكذا:


import logging

logging.basicConfig(level=logging.INFO)

def process_payment(user_id, amount):
    logging.info(f"Starting payment process for user {user_id} with amount {amount}.")
    try:
        # ... منطق معالجة الدفع ...
        raise ValueError("Insufficient funds")
    except Exception as e:
        # هنا الكارثة!
        logging.error(f"Payment failed for user {user_id}. Reason: {e}")

process_payment("user-123", 100)

الناتج سيكون سطراً نصياً:


ERROR:root:Payment failed for user user-123. Reason: Insufficient funds

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

  • “أرني كل الأخطاء التي حدثت للمستخدم user-123 خلال الساعة الماضية.”
  • “كم عدد عمليات الدفع التي فشلت بسبب Insufficient funds؟”
  • “أعطني متوسط المبالغ التي تفشل عمليات دفعها.”

الإجابة صعبة جداً. ستحتاج إلى كتابة تعابير نمطية (Regular Expressions) معقدة لتحليل هذه النصوص، وهي عملية بطيئة، هشة، وغير عملية على نطاق واسع.

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

الحل المنقذ: التسجيل المنظم (Structured Logging)

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

ما هو التسجيل المنظم؟

هو ممارسة تسجيل الأحداث بصيغة بيانات ثابتة وقابلة للتحليل، وأشهرها صيغة JSON.

لنعد كتابة المثال السابق باستخدام التسجيل المنظم. بدلاً من دمج كل المعلومات في سلسلة نصية واحدة، نفصلها إلى حقول (key-value pairs).

الرسالة السابقة ستصبح هكذا بصيغة JSON:


{
  "timestamp": "2023-10-27T12:34:56Z",
  "level": "error",
  "message": "Payment failed",
  "context": {
    "user_id": "user-123",
    "payment_details": {
      "amount": 100,
      "currency": "USD"
    },
    "error": {
      "type": "ValueError",
      "reason": "Insufficient funds"
    },
    "service": "payment-service",
    "request_id": "a7b3c9-d8e4-f5a6-b7c8"
  }
}

هل ترى الفرق؟ الآن كل معلومة لها حقل خاص بها. أصبحت سجلاتنا قاعدة بيانات صغيرة قابلة للاستعلام. الآن يمكننا بسهولة الإجابة على أسئلتنا السابقة باستخدام استعلامات بسيطة في أي نظام تجميع سجلات حديث (مثل ELK Stack, Datadog, Grafana Loki):

  • level:error AND context.user_id:"user-123"
  • message:"Payment failed" AND context.error.reason:"Insufficient funds" | count()
  • level:error | avg(context.payment_details.amount)

لقد انتقلنا من التنقيب اليدوي المؤلم إلى التحليل الآلي الفوري. هذا هو جوهر القوة في التسجيل المنظم.

كيف تبدأ بالتسجيل المنظم؟ (خطوات عملية)

الخبر السار أنك لست بحاجة لإعادة اختراع العجلة. هناك مكتبات رائعة في كل لغات البرمجة تجعل هذا الأمر سهلاً جداً.

1. اختر المكتبة المناسبة

لا تستخدم print() أو console.log() مباشرة. استخدم مكتبة متخصصة:

  • Python: structlog هي الخيار الأفضل والأكثر احترافية.
  • JavaScript/Node.js: Pino (سريعة جداً) أو Winston (مرنة جداً).
  • Java: استخدم `SLF4J` مع `Logback` وإضافة `logstash-logback-encoder` لتوليد JSON.
  • Go: zerolog أو zap.

2. مثال عملي باستخدام `pino` في Node.js

لنرَ كيف يبدو الكود عملياً. هذا مثال بسيط باستخدام مكتبة `pino` في بيئة Node.js:


// 1. تثبيت المكتبة
// npm install pino

// 2. إعداد المسجّل (Logger)
const pino = require('pino');
const logger = pino({
    level: 'info',
    timestamp: pino.stdTimeFunctions.isoTime,
    formatters: {
        level: (label) => {
            return { level: label };
        },
    },
});

// دالة لمعالجة الطلب
function handleRequest(req) {
    // إضافة سياق للطلب (هذه خطوة سحرية!)
    // كل السجلات داخل هذا السياق ستحتوي على requestId و userId تلقائياً
    const childLogger = logger.child({ 
        requestId: req.id,
        userId: req.user.id 
    });

    childLogger.info({ path: req.path }, "Request received");

    try {
        if (req.body.amount > 1000) {
            throw new Error("Amount exceeds limit");
        }
        // ... منطق العمل ...
        childLogger.info({ orderId: 'xyz-123' }, "Order processed successfully");

    } catch (err) {
        // تسجيل الخطأ مع كل السياق الغني
        childLogger.error({ err: err, requestBody: req.body }, "Failed to process request");
    }
}

// محاكاة طلب وارد
handleRequest({
    id: 'abc-123-xyz-789',
    path: '/api/payment',
    user: { id: 'user-456' },
    body: { amount: 2000, currency: 'SAR' }
});

الناتج الذي سيُطبع سيكون عبارة عن أسطر JSON منظمة وجميلة:


{"level":"info","time":"...","requestId":"abc-123-xyz-789","userId":"user-456","path":"/api/payment","msg":"Request received"}
{"level":"error","time":"...","requestId":"abc-123-xyz-789","userId":"user-456","err":{...},"requestBody":{...},"msg":"Failed to process request"}

لاحظ كيف أن `requestId` و `userId` أُضيفا تلقائياً لكل رسائل السجل المتعلقة بهذا الطلب. هذا يسمى “السياق” (Context)، وهو يغير قواعد اللعبة في تتبع المشاكل عبر الأنظمة الموزعة (Microservices).

نصائح أبو عمر الذهبية للتسجيل المنظم

  • وحّد أسماء الحقول: اتفق مع فريقك على أسماء موحدة للحقول المهمة (مثلاً، استخدم userId دائماً، وليس user_id مرة و userID مرة أخرى).
  • السياق هو الملك: دائماً أضف معرّفاً فريداً لكل طلب (Correlation ID / Request ID) وتمريره عبر كل الخدمات. هذا يسمح لك بتتبع رحلة الطلب الواحد عبر النظام بأكمله.
  • لا تسجل معلومات حساسة: إياك ثم إياك أن تسجل كلمات مرور، أرقام بطاقات ائتمان، أو أي معلومات تعريف شخصية (PII) كنص واضح. استخدم تقنيات الإخفاء (Masking) إذا لزم الأمر.
  • استخدم مستويات التسجيل بحكمة:
    • DEBUG: للمعلومات التفصيلية التي تهم المطورين فقط أثناء التطوير.
    • INFO: للأحداث المهمة في سير العمل الطبيعي للتطبيق (تسجيل دخول، إتمام عملية).
    • WARN: لأمور غير متوقعة ولكنها لا توقف عمل النظام (مثل محاولة فاشلة ثم نجاحها).
    • ERROR: لأخطاء جدية تتطلب تدخلاً (فشل عملية، انقطاع اتصال).
  • السجلات بالإنجليزية: حتى لو كان تطبيقك موجهاً للجمهور العربي، اجعل السجلات باللغة الإنجليزية. هي اللغة المعتمدة في عالم البرمجة وتسهل التعاون مع مطورين من خلفيات مختلفة وتسهل البحث عن حلول عبر الإنترنت.

الخلاصة: استثمر في مستقبلك كمبرمج 💡

الانتقال من التسجيل العشوائي إلى التسجيل المنظم ليس مجرد تحسين تقني، بل هو استثمار في راحة بالك وفي وقتك وفي استقرار أنظمتك. في المرة القادمة التي تحدث فيها كارثة في بيئة الإنتاج (وهي ستحصل حتماً)، ستكون ممتناً جداً لأنك قضيت بضع ساعات إضافية في إعداد نظام تسجيلاتك بشكل صحيح.

الزبدة: لا تنتظر حتى تحترق أصابعك بنار التنقيح الأعمى كما حصل معي. ابدأ اليوم، اختر مكتبة مناسبة، وابدأ بتحويل سجلاتك من مجرد نصوص فوضوية إلى بيانات ذكية وقوية. صدقني، “أبو عمر” المستقبلي في فريقك سيدعو لك.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

ذكاء اصطناعي

بحثنا كان يعثر على الكلمات، لا على النوايا: كيف أنقذتنا قواعد بيانات المتجهات من جحيم البحث الدلالي الأعمى؟

أشارككم قصة من قلب المعركة البرمجية، يوم كان نظام البحث لدينا أصمًا وأعمى، لا يفهم سوى تطابق الكلمات. سنغوص في عالم قواعد بيانات المتجهات (Vector...

14 أبريل، 2026 قراءة المزيد
خوارزميات

قاعدة بياناتنا كانت تجيب على ‘هل هذا موجود؟’ ببطء قاتل: كيف أنقذنا ‘مرشح بلوم’ (Bloom Filter) من جحيم الاستعلامات المكلفة؟

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

14 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

موقعنا كان يستبعد الملايين بصمت: كيف أنقذتنا ‘معايير الوصولية’ (Accessibility) من جحيم التصميم الإقصائي؟

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

14 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

استعلاماتنا كانت تزحف: كيف أنقذتنا ‘فهارس قاعدة البيانات’ من جحيم فحص الجداول الكاملة؟

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

14 أبريل، 2026 قراءة المزيد
الشبكات والـ APIs

أنظمتنا كانت تسأل ‘هل هناك جديد؟’ كل ثانية: كيف أنقذتنا ‘الخطافات الشبكية’ (Webhooks) من جحيم الاستقصاء المستمر (Polling)؟

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

14 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

تطبيقنا كان رهينة منطقة جغرافية واحدة: كيف أنقذتنا استراتيجية ‘متعددة المناطق’ (Multi-Region) من جحيم الانقطاع الكامل؟

أشارككم قصة حقيقية عن يوم توقف فيه تطبيقنا بالكامل بسبب انقطاع في منطقة سحابية واحدة، وكيف كانت استراتيجية "متعددة المناطق" (Multi-Region) هي طوق النجاة. سأشرح...

14 أبريل، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

حسابي في GitHub كان مقبرة صامتة: كيف أنقذني ‘ملف التعريف المميّز’ من جحيم التجاهل؟

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

14 أبريل، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

عطل خدمة واحدة كاد ينسف النظام: كيف أنقذنا نمط “قاطع الدائرة” من جحيم الأعطال المتتالية؟

أشارككم قصة حقيقية من قلب المعركة التقنية، يوم كاد عطل في خدمة دفع صغيرة أن ينهار معه نظامنا بأكمله. سنغوص في تفاصيل نمط "قاطع الدائرة"...

14 أبريل، 2026 قراءة المزيد
البودكاست