معايير السجلات المنظَّمة
معايير السجلات المنظَّمة
قبل أن تتمكن من إرسال السجلات إلى Elasticsearch أو الاستعلام عنها في Loki أو التنبيه عليها في Grafana، عليك أن تقرر ما هو سطر السجل فعلاً. هذا القرار — الذي يُتخذ مرة واحدة بشكل غير متسق عبر خمسين خدمة — هو السبب في أن معظم خطوط أنابيب السجلات تتحول إلى مقابر من الضوضاء غير القابلة للاستعلام. يؤسس هذا الدرس المعايير الإنتاجية التي تستخدمها شركات مثل Stripe وCloudflare وGitHub لجعل سجلاتها مصدراً موثوقاً للحقيقة عند مئات الآلاف من الأحداث في الثانية.
لماذا تفشل السجلات غير المنظَّمة على نطاق واسع
سطر السجل التقليدي غير المنظَّم يبدو هكذا: 2024-03-12 14:23:01 ERROR checkout failed for user 42 after 3201ms. يستطيع الإنسان قراءته. أما الآلة فلا تستطيع تحليله بشكل موثوق. لاستخراج معرف المستخدم أو المدة أو نوع الخطأ من مليون سطر كهذا، يجب عليك كتابة regex هشة — وفي اللحظة التي يغير فيها أحد الفرق تنسيق سجلاته، تنكسر الـ regex ولوحة القيادة تعيد أصفاراً بصمت.
السجل المنظَّم يستبدل الرسائل النصية الحرة بمستندات مفتاح/قيمة قابلة للقراءة آلياً — JSON في معظم الأحيان. كل حقل هو سمة مسمّاة ومكتوبة. السجل أعلاه يتحول إلى كائن JSON حيث user_id وduration_ms وlevel حقول من الدرجة الأولى يمكنك فهرستها وتصفيتها وتجميعها دون أي منطق تحليل وقت الاستعلام. هذا ليس مجرد راحة للمطور — إنه المتطلب المعماري الأساسي لعمل التسجيل المركزي.
مخطط JSON القانوني للسجلات
لا يوجد معيار رسمي واحد، لكن الصناعة تقاربت على مجموعة صغيرة من الحقول الأساسية الإلزامية التي يجب أن تحملها كل سطر سجل، بصرف النظر عن أي خدمة أو لغة تُصدرها. هذه الحقول هي ما يسمح لمجمّع سجلاتك بدمج تدفقات من واجهة برمجية بـ Go وعامل Python وخدمة Node.js في مجموعة بيانات متماسكة قابلة للاستعلام المشترك.
لنستعرض أهم مجموعات الحقول والمنطق الكامن وراء كل منها.
اصطلاحات الحقول: لماذا كل مفتاح
الطابع الزمني. دائماً ISO 8601 بتوقيت UTC مع دقة المللي ثانية: 2024-03-12T14:23:01.847Z. لا تستخدم أعداد Unix epoch الصحيحة في سجلات التطبيق — فهي غير قابلة للقراءة دون محول، وكل واجهة سجلات ستُعيد تحليل السلسلة ISO على أي حال. اللاحقة Z إلزامية؛ السجلات التي لا تحملها تخلق غموضاً حين تمتد الخدمات عبر مناطق.
المستوى. استخدم بالضبط هذه الخمسة بأحرف صغيرة: debug، info، warn، error، fatal. لا تخترع critical أو severe أو INFORMATION. تكاثر الحالات المختلطة والمرادفات يكسر مرشحات مستوى السجل عبر المجمّع. يجب أن تُصدر خدمات الإنتاج info وما فوقه بشكل افتراضي؛ debug يُفعَّل ديناميكياً عبر علامة ميزة أو متغير بيئة، ولا يُترك دائماً.
الخدمة والإصدار والبيئة. هذه الثلاثة معاً تُحدد ما أصدر السجل. يجب أن يطابق service اسم خدمة Kubernetes بالضبط — هذا يسمح بالربط التلقائي بين المقاييس والسجلات دون جدول تعيين. يجب أن يكون version علامة semver الكاملة أو SHA الـ git، حتى تتمكن من ربط التدهور بنشر محدد. يجب أن يكون env أحد development أو staging أو production — هذا يمنع ضوضاء التدريج من تلويث لوحات الإنتاج.
مفاتيح الحقول ذات المساحات الاسمية. استخدم ترميز النقطة للتجميع حسب النطاق: http.method، http.status، db.query_ms، error.type، error.stack. هذا يعكس اصطلاحات الدلالات في OpenTelemetry، مما يعني أن سجلاتك ستتوافق تلقائياً مع التتبعات حين تضيف التتبع الموزع. المفاتيح المسطحة غير ذات المساحات الاسمية لكل شيء خطأ شائع يؤدي إلى تعارضات بين الفرق.
http.*، db.*، messaging.*، rpc.*، error.*.معرفات الارتباط ومعرفات الطلبات
الممارسة الأكثر فائدة في التسجيل المنظَّم هي نشر معرف فريد عبر كل سطر سجل ينتمي إلى نفس الطلب. بدون ذلك، حين ترى خطأً في الخدمة C، لا توجد طريقة للعثور على السجلات الأعلى مستوى في الخدمتين A وB التي سبقته. مع request_id، تكتب قيمة واحدة في واجهة استعلام السجلات وترى فوراً الرواية الكاملة للطلب عبر جميع الخدمات.
هناك نوعان مختلفان من المعرفات في أنظمة الإنتاج، والخلط بينهما يسبب ألماً تشغيلياً:
request_id— يُولَّد في بوابة API أو موازن الحمل لكل طلب HTTP وارد. نطاقه سلسلة استدعاء متزامنة واحدة. استخدمه لإعادة بناء ما حدث خلال معاملة HTTP واحدة. تنسيق جيد هو ULID (قابل للترتيب معجمياً، مسبوق بالوقت):req_01HXYZ9ABCDE.trace_id— معرف W3C TraceContext بـ 128 بت يمتد عبر الحدود اللامتزامنة وقفزات قوائم الانتظار والخدمات المتعددة. هذا هو معرف التتبع الموزع الخاص بك. يُولَّد مرة واحدة لكل عملية مرئية للمستخدم ويُنشر عبر ترويسات HTTP (traceparent) وبيانات تعريف قوائم انتظار الرسائل. استخدمه لربط السجلات بالتتبعات في Tempo أو Jaeger أو X-Ray.
يجب حقن كلا المعرفين في سياق التسجيل على مستوى الإطار حتى يحمل كل سطر سجل يُصدر خلال ذلك الطلب كليهما تلقائياً — دون مطالبة كل مطور بتذكر تمريرهما يدوياً. في Go يعني ذلك استخدام context.Context وwares وسيطة تستدعي log.With(ctx, "trace_id", traceID). في Python هو مرشح تسجيل؛ في Node.js هو AsyncLocalStorage. الآلية تختلف لكن المبدأ عالمي: المعرفات تنتقل مع سياق الطلب.
ما يجب تسجيله — وما يجب تجنبه
كل سطر سجل يُكلّف مالاً وزمن استجابة. على نطاق Google، حقل إضافي واحد مضاف لكل سطر سجل يمكن أن يُكلّف مئات الآلاف من الدولارات سنوياً في التخزين. الانضباط يعني معرفة ما يجب تضمينه.
سجّل عند كل حدود خدمة: الطلب الوارد (الطريقة والمسار وهوية المُستدعي)، والاستدعاء الصادر (الوجهة والطريقة وملخص المعاملات)، والنتيجة (الحالة والمدة والخطأ إن وُجد). هذا هو الحد الأدنى لإعادة بناء رحلة الطلب. لا تسجّل كل استدعاء دالة داخلي — ذلك يعود للتتبعات لا السجلات.
لا تسجّل الأسرار أو البيانات الشخصية أبداً — كلمات المرور والرموز وأرقام بطاقات الائتمان وأرقام الهوية وعناوين البريد الإلكتروني الكاملة. استخدم إخفاء الحقول على مستوى مكتبة التسجيل أو خطوة معالجة مسبقة في الشاحن. في كود التطبيق: "card_number": "[REDACTED]". أطر الامتثال العديدة (PCI-DSS وGDPR وHIPAA) تجعل هذا متطلباً صارماً.
انضباط مستوى الخطورة في الإنتاج
في الواقع، معظم الفرق تعاني من مشكلتين: سجلات info بالغة الإسهاب تُغرق الإشارات الحقيقية، وسجلات error مثقلة جداً لدرجة أنها تفقد معناها. وضع عقد واضح لكل مستوى — مُطبَّق في مراجعة الكود — أمر ضروري.
- debug — الحالة الداخلية المفيدة فقط حين تصحح نشطاً. معطّل في الإنتاج بشكل افتراضي. يُفعَّل لكل خدمة عبر علامة ديناميكية.
- info — الأحداث التجارية الطبيعية الذات المعنى: طلب مُستلم، مهمة بدأت، دفع مُصرَّح. واحد لكل عملية من المستوى الأعلى. لا تُصدر
infoأبداً في حلقة ضيقة. - warn — الشذوذات القابلة للتعافي التي تحتاج مراقبة لكن لا تستدعي إجراءً فورياً: نجح إعادة المحاولة بعد فشل، استُخدمت واجهة برمجية مهجورة، قيمة إعداد مفقودة مع تطبيق افتراضي.
- error — فشلت عملية وتعذّر على النظام التعافي بمفرده. قد يحتاج إنسان للتدخل. كل سجل
errorيجب أن يتضمنerror.typeوerror.stack. - fatal — لا يمكن للعملية الاستمرار. يخرج التطبيق فوراً بعد إصدار هذا السطر. استخدمه بتحفظ شديد؛ هو ليس بديلاً لـ
error.
الاختبار التشغيلي: إذا استيقظ أحدهم الساعة 3 صباحاً بسبب تنبيه error، فكل سجل error يجب أن يصف شيئاً يستحق الاستيقاظ من أجله. إن لم يكن كذلك، خفّضه إلى warn. إرهاق التنبيهات يبدأ بمستوى خطورة سجل مصنَّف بشكل خاطئ.
debug في الإنتاج أوقفت بنية تسجيلهم الكاملة لمدة 20 دقيقة قبل أن يلتقطها تنبيه حجم السجل.