مقدمة: لماذا تحتاج إلى تسجيل صحيح في C++؟
التسجيل (Logging) ليس مجرد تسجيل رسائل في ملف. في بيئة الإنتاج (production)، هو أداتك الرئيسية للمراقبة (monitoring)، التصحيح (debugging)، والتدقيق (audit). التسجيل الذي تم إعداده بشكل سيء يمكن أن يؤدي إلى فقدان بيانات حرجة، انخفاض في الأداء (I/O-bound)، أو على العكس، إلى "تلوث ضوضائي" للأقراص. في هذه المقالة، سنستعرض أفضل ممارسات التسجيل في C++ التي ستساعدك على تجنب الأخطاء النمطية وبناء نظام موثوق لجمع السجلات (logs).
1. اختيار المكتبة: spdlog vs glog vs std::clog
المكتبة القياسية لـ C++ تقدم فقط التدفقات الأساسية std::clog و std::cerr، وهي غير مناسبة للمشاريع الجادة. دعنا نستعرض ثلاث طرق شائعة:
1.1. spdlog — المعيار الحديث
spdlog هي مكتبة header-only تدعم التسجيل غير المتزامن (asynchronous logging)، تدوير الملفات (rotation)، التنسيق (formatting) (على غرار fmtlib)، والعديد من المستقبلات (sinks) (ملف، وحدة تحكم، syslog، Kafka).
المزايا الرئيسية:
- أداء عالٍ (بفضل التدفقات غير المتزامنة)
- إعداد مرن للمستويات والتنسيقات
- دعم التسجيل المنظم (structured logging) (JSON)
#include <spdlog/spdlog.h>#include <spdlog/sinks/rotating_file_sink.h>
int main() { // إنشاء مسجل ملفات دوار (5 ملفات كل منها 10 MB) auto logger = spdlog::rotating_logger_mt("file_logger", "logs/app.log", 1048576 * 10, 5); logger->set_level(spdlog::level::info); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v");
logger->info("تم تشغيل التطبيق"); logger->warn("مساحة الذاكرة الحرة منخفضة: {} MB", free_memory); logger->error("خطأ في الاتصال بقاعدة البيانات: {}", err.what()); return 0;}1.2. Google glog — حل مُجرّب
مكتبة glog من Google سهلة الاستخدام وتحتوي على دعم مدمج للتسجيل الشرطي (conditional logging) وتفريغات المكدس (stack dumps). العيب هو تنسيق أقل مرونة وعدم وجود دعم غير متزامن.
#include <glog/logging.h>
int main(int argc, char* argv[]) { google::InitGoogleLogging(argv[0]); FLAGS_log_dir = "./logs"; FLAGS_stderrthreshold = google::WARNING; // تكرار WARNING+ في stderr
LOG(INFO) << "تم تشغيل الخادم على المنفذ " << port; LOG_IF(WARNING, memory_usage > 0.9) << "الذاكرة على وشك النفاد"; LOG(FATAL) << "خطأ حرج، إنهاء البرنامج"; return 0;}1.3. مسجل مكتوب يدويًا باستخدام std::clog
استخدم std::clog فقط للتصحيح (debugging) أو للأدوات المساعدة الصغيرة جدًا. فهو لا يدعم التدوير (rotation)، المستويات (levels)، أو تعدد الخيوط (multithreading).
#define LOG(level, msg) std::clog << "[" << level << "] " << msg << std::endl;LOG("INFO", "بدء التشغيل");LOG("ERROR", "خطأ: " + std::to_string(errno));2. أفضل ممارسات الإعداد والهندسة المعمارية
حتى أفضل مكتبة لن تنقذ الموقف إذا كانت بنية التسجيل (logging architecture) مبنية بشكل خاطئ. التزم بالمبادئ التالية:
2.1. استخدم مستويات التسجيل (Logging Levels)
حدد 5-6 مستويات والتزم بها بدقة:
- TRACE — تصحيح تفصيلي (دخول/خروج الدوال)
- DEBUG — معلومات تصحيح للمطورين
- INFO — سير العمل الطبيعي للتطبيق (بدء التشغيل، الإنهاء)
- WARN — مشاكل محتملة (مساحة قرص منخفضة، تحميل عالٍ)
- ERROR — أخطاء لا توقف العمل (استثناءات)
- FATAL — خطأ حرج، وبعده يتم إنهاء التطبيق
// في بيئة الإنتاج (production)، نضبط المستوى على WARN لتجنب ملء السجلات برسائل INFOlogger->set_level(spdlog::level::warn);2.2. التسجيل المنظم (Structured Logging) (JSON)
لـ