يا جماعة الخير، اسمحوا لي أن أروي لكم حكاية صارت معي قبل كم سنة، يوم كنت لا أزال أعتبر أن تسجيل الأخطاء (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: لأخطاء جدية تتطلب تدخلاً (فشل عملية، انقطاع اتصال).
- السجلات بالإنجليزية: حتى لو كان تطبيقك موجهاً للجمهور العربي، اجعل السجلات باللغة الإنجليزية. هي اللغة المعتمدة في عالم البرمجة وتسهل التعاون مع مطورين من خلفيات مختلفة وتسهل البحث عن حلول عبر الإنترنت.
الخلاصة: استثمر في مستقبلك كمبرمج 💡
الانتقال من التسجيل العشوائي إلى التسجيل المنظم ليس مجرد تحسين تقني، بل هو استثمار في راحة بالك وفي وقتك وفي استقرار أنظمتك. في المرة القادمة التي تحدث فيها كارثة في بيئة الإنتاج (وهي ستحصل حتماً)، ستكون ممتناً جداً لأنك قضيت بضع ساعات إضافية في إعداد نظام تسجيلاتك بشكل صحيح.
الزبدة: لا تنتظر حتى تحترق أصابعك بنار التنقيح الأعمى كما حصل معي. ابدأ اليوم، اختر مكتبة مناسبة، وابدأ بتحويل سجلاتك من مجرد نصوص فوضوية إلى بيانات ذكية وقوية. صدقني، “أبو عمر” المستقبلي في فريقك سيدعو لك.