السجلات على نطاق واسع: ELK وLoki

الاستعلام عن السجلات والتحقيق في الأحداث

18 دقيقة الدرس 8 من 28

الاستعلام عن السجلات والتحقيق في الأحداث

معرفة كيفية استيعاب السجلات وتخزينها هي الأساس. أما معرفة كيفية الاستعلام عنها تحت الضغط — خلال حادث نشط في الثالثة صباحًا، حين يسأل المسؤول عما جرى — فهي المهارة التشغيلية التي تفرق بين المهندسين الكبار وسواهم. هذا الدرس غوص عميق في البحث الفعّال عن السجلات: لغات الاستعلام، وسير عمل التصحيح، والعادات التي تنقلك من "ثمة خطأ ما" إلى "تم تحديد السبب الجذري" في دقائق لا ساعات.

لغتا الاستعلام: LogQL و KQL

يحدد اختيارك لمنصة التخزين لغة الاستعلام المستخدمة. تستخدم Loki لغة LogQL، فيما تستخدم Elasticsearch لغة KQL (لغة استعلام Kibana) أو بنية Lucene الكاملة. كلتاهما تتبعان نمطًا مفاهيميًا واحدًا: تضييق نطاق البحث أولًا بمرشحات التسميات/الفهرس، ثم تطبيق مرشحات المحتوى على المجموعة المضيّقة. ترتيب العمليات بالعكس — مسح كل المحتوى قبل التصفية بالتسميات — هو السبب الأكثر شيوعًا للاستعلامات البطيئة على نطاق الإنتاج.

LogQL (Grafana Loki)

يتكون استعلام LogQL من جزأين إلزاميين: محدد البث (مطابقات التسميات بين الأقواس المعقوصة) وخط أنابيب السجل (مراحل مفصولة بأنبوب). يُقيَّم محدد البث مقابل الفهرس — وهو عملية رخيصة. أما مراحل خط الأنابيب فتعمل على بيانات الأجزاء الخام — وهي مكلفة ويجب أن تكون ضيقة قدر الإمكان.

# ── LogQL: تضييق تدريجي لنطاق البحث ───────────────────────────────────────── # 1. محدد البث فقط — يعيد جميع الأسطر من payment-api في الإنتاج {app="payment-api", env="production"} # 2. إضافة مرشح محتوى — يمسح الأجزاء المطابقة فقط بحثًا عن الكلمة المفتاحية {app="payment-api", env="production"} |= "timeout" # 3. تحليل JSON ثم التصفية على حقل منظم — أسرع بكثير من التعبيرات النمطية {app="payment-api", env="production"} | json | level="error" | duration_ms > 2000 # 4. تجميع: الطلبات في الدقيقة مقسّمة حسب رمز HTTP (استعلام مقاييس) sum by (status_code) ( rate( {app="payment-api", env="production"} | json | __error__="" [1m] ) ) # 5. استخراج النمط من سجلات NGINX غير المنظمة {app="nginx", env="production"} | pattern `<client> - - [<_>] "<method> <path> <_>" <status> <bytes>` | status >= 500
ممارسة احترافية: في Loki، ضع التسمية الأكثر انتقائية أولًا في محدد البث. إذا كانت نطاقًا واحدًا فقط مناسبًا، اضبط namespace="checkout" قبل env="production". تُقيِّم Loki مطابقات التسميات من اليسار إلى اليمين؛ المطابقة الأولى تُقلّص أكبر عدد من الأجزاء وتجعل كل مرحلة لاحقة أسرع. تسمي فرق SRE في Google هذا النهج "دفع المرشحات نحو التخزين" وهو ينطبق على جميع محركات استعلام السجلات.

KQL (Kibana / Elasticsearch)

KQL هي لغة استعلام مبسطة مبنية فوق Lucene. تنسجم بديهيًا مع البحث على مستوى الحقل، والنطاقات، والمنطق البولياني. للمستخدمين المتقدمين، يُضيف بناء Lucene الكامل (يُفعَّل عبر مبدّل في Kibana Discover) المطابقة الضبابية وبحث القرب وأنماط حرف البدل. يُترجَم كلاهما داخليًا إلى استعلامات Elasticsearch DSL.

# ── أمثلة KQL / Lucene في Kibana Discover ──────────────────────────────────── # مطابقة كلمة مفتاحية في أي حقل timeout # مطابقة على حقل محدد (KQL) level: error AND service.name: payment-api # استعلام نطاق (KQL) — أخطاء آخر 15 دقيقة كانت بطيئة level: error AND http.response.status_code >= 500 AND http.response.time_ms > 2000 # حرف البدل — إيجاد رسائل خطأ متعلقة بقاعدة البيانات message: *database* AND level: error # مطابقة عبارة — يجب أن تظهر الكلمتان بهذا الترتيب تمامًا message: "connection refused" # التحقق من الوجود — سجلات تحتوي على معرف تتبع فقط (طلبات مرتبطة) trace.id: * # ── Elasticsearch Query DSL (للتنبيهات واستدعاءات API و Kibana Lens) ───────── # مكافئ: level=error AND response_time > 2s خلال آخر 5 دقائق POST /logs-production-*/_search { "query": { "bool": { "filter": [ { "term": { "level": "error" } }, { "range": { "http.response.time_ms": { "gte": 2000 } } }, { "range": { "@timestamp": { "gte": "now-5m" } } } ] } }, "sort": [{ "@timestamp": "desc" }], "size": 100 }
فكرة محورية: جمل filter في KQL (مقابل must في DSL) لا تؤثر على درجة الملاءمة وتُخزّنها Elasticsearch في ذاكرة التخزين المؤقت — مما يجعلها أسرع بكثير. فضّل استخدام filter في جميع استعلامات الحقول المنظمة. استخدم must (الذي يحسب الدرجة) فقط عند الحاجة إلى تصنيف ملاءمة النص الكامل، وهو أمر نادر في البحث التشغيلي عن السجلات.

سير عمل التصحيح المبني على السجلات

سير عمل التصحيح المنهجي ليس اختياريًا على النطاق الكبير — إنه ما يمنعك من التخبط عشوائيًا عبر السجلات بينما يتصاعد متوسط وقت الاسترداد. تتبع فرق SRE في كبرى الشركات نمطًا ثابتًا بصرف النظر عن منصة التسجيل:

Log-Driven Incident Investigation Workflow 1. Scope time window + service labels 2. Volume error rate & anomaly check 3. Filter keyword & field drill-down 4. Correlate trace_id / user_id across services 5. RCA root cause + blast radius Grafana / Kibana time picker rate() / count() over time chart LogQL / KQL field filters Tempo / Jaeger trace link timeline + postmortem Log-Driven Incident Investigation Scope → Volume → Filter → Correlate → RCA
سير عمل التحقيق المبني على السجلات في خمس خطوات تستخدمها فرق SRE على نطاق الإنتاج.
  1. تحديد النطاق: اضبط النافذة الزمنية على لحظة ما قبيل إطلاق التنبيه. ثبّت تسميات الخدمة ذات الصلة. النوافذ الزمنية الواسعة تضاعف تكلفة الاستعلام — ابدأ ضيقًا وتوسّع فقط عند الحاجة.
  2. قياس الحجم: شغّل استعلام معدل الأخطاء مجمّعًا عبر الزمن (مثل: rate({app="checkout"} | json | level="error" [1m])). حدد الدقيقة التي ارتفع فيها معدل الأخطاء بالضبط. هذا يمنعك من إضاعة الوقت في ضوضاء غير ذات صلة في نفس النافذة.
  3. التصفية: انغمس في دقيقة الارتفاع. طبّق مرشحات الكلمات المفتاحية والحقول لعزل فئة الخطأ. اطلع على عدد من سطور السجل الخام — رسالة الخطأ الفعلية أو تتبع المكدس أو المضيف المنبثق موجودة هناك في الغالب.
  4. الربط: خذ trace_id أو request_id أو user_id من أحد سطور السجل الفاشلة. استعلم عنه عبر جميع الخدمات. هذا يُعيد بناء مسار الطلب الكامل ويكشف الخدمة التي أدخلت الخلل فعلًا مقابل الخدمات الضحايا في المصب.
  5. تحديد السبب الجذري: أنشئ الجدول الزمني: متى ظهر أول سجل شاذ؟ ماذا تغيّر (نشر، دفع إعدادات، ارتفاع حركة المرور، انتهاء شهادة)؟ ما هو نطاق التأثير (كم عدد المستخدمين/الطلبات المتأثرة)؟ وثّق ذلك لتقرير ما بعد الحادث.

الربط بين الخدمات عبر معرّفات التتبع

أقوى قدرة في منظومة المراقبة الحديثة هي القفز من سطر سجل إلى التتبع الموزع الكامل والعودة. لا يعمل هذا إلا إذا كان كل سطر سجل من كل خدمة يحمل trace_id يطابق التتبع المسجّل في Tempo أو Jaeger. عمليًا يعني هذا أن إطار عمل تطبيقك (OpenTelemetry SDK، أو Spring Sleuth، إلخ) يجب أن يحقن سياق التتبع النشط في خريطة MDC/السياق الخاصة بالسجل، وأن يُصدر تنسيق السجل المنظم هذا السياق كحقل JSON من المستوى الأعلى.

في Grafana، يمكن تهيئة مصدر بيانات Loki بـحقل مشتق يحوّل كل قيمة trace_id في سطر السجل إلى رابط قابل للنقر يفتح Grafana Tempo عند ذلك التتبع بالضبط. هذا يلغي خطوة النسخ واللصق اليدوية، وهي الطريقة التي تحقق بها الفرق المتميزة متوسط وقت استرداد أقل من خمس دقائق في حالات الإخفاق الموزعة المعقدة.

# ── حقل مشتق في مصدر بيانات Loki (grafana.ini / provisioning) ─────────────── # يحوّل كل trace_id في سطر سجل إلى رابط Tempo قابل للنقر apiVersion: 1 datasources: - name: Loki type: loki url: http://loki:3100 jsonData: derivedFields: - name: TraceID matcherRegex: '"trace_id":"([a-f0-9]{32})"' url: '$${__value.raw}' datasourceUid: tempo # معرف مصدر بيانات Tempo — يُتيح القفز للتتبع # ── LogQL: إيجاد جميع السجلات المرتبطة بتتبع محدد عبر كل الخدمات ────────── {env="production"} | json | trace_id="4bf92f3577b34da6a3ce929d0e0e4736" # ── KQL المكافئ في Kibana ─────────────────────────────────────────────────── trace.id: "4bf92f3577b34da6a3ce929d0e0e4736"

التنبيه من استعلامات السجلات

استعلام السجل الذي تشغّله يدويًا خلال حادث ما هو نصف القيمة فقط. الاستعلام ذاته، عند تشغيله على جدول زمني، يصبح تنبيهًا استباقيًا يُعلمك قبل أن يرفع أي مستخدم تذكرة. يدعم كل من Grafana وKibana قواعد التنبيه المبنية على السجلات. في Grafana، يمكن لاستعلام LogQL للمقاييس أن يسند قاعدة تنبيه قياسية؛ وفي Kibana، تُقيّم قواعد التنبيه استعلامات KQL/ES|QL على جدول زمني قابل للتهيئة.

فخ إنتاجي: قواعد التنبيه المبنية على السجلات الفضفاضة جدًا (مثل التنبيه على أي سطر سجل خطأ واحد) تولّد إرهاقًا كارثيًا من التنبيهات. دائمًا عبّر عن التنبيه كـمعدل أو حد عدد عبر نافذة زمنية — على سبيل المثال، "أكثر من 50 خطأ في الدقيقة لمدة 3 دقائق متواصلة." استخدم for: 3m في قواعد تنبيه Grafana لاشتراط استمرار الحالة قبل الإطلاق. هذا الإعداد الواحد يُزيل الغالبية العظمى من التنبيهات الكاذبة.

الأنماط المضادة الشائعة في الاستعلام

هذه هي الأخطاء التي تجعل التحقيقات في الحوادث بطيئة وأنظمة التسجيل مكلفة:

  • الاستعلام بدون حد زمني. استعلامات "كل الوقت" في Loki تمسح كل جزء كتب على الإطلاق. دائمًا اضبط نطاقًا زمنيًا في Grafana أو مرّر start/end صراحةً لواجهة Loki HTTP.
  • استخدام التعبيرات النمطية حين يكفي مطابقة النص. |= "error" مسح بايت بسيط؛ |~ "err.*r" يستدعي محرك RE2. على غيغابايتات من السجلات يصل الفارق إلى 10-20 ضعفًا في وقت الاستعلام. استخدم التعبيرات النمطية فقط حين يستوجبها النمط حقًا.
  • تسميات عالية الكاردينالية في Loki. إضافة user_id أو request_id كتسميات بث Loki ينشئ ملايين البثوث ويُنهار أداء الاستعلام. ضع البيانات عالية الكاردينالية في جسم سطر السجل (تُحلَّل بـ| json) لا في مجموعة التسميات.
  • نسيان التحقق من أخطاء المحلل. حين يحلّل LogQL سطر سجل JSON مشوّهًا، يضبط التسمية __error__. تضمين | __error__="" في استعلامات المعدل يضمن أنك تحسب أحداثًا حقيقية لا إخفاقات تحليل مُتنكّرة كثغرات في بياناتك.
فكرة محورية: كل استعلام تكتبه خلال حادث يجب أن يجيب على سؤال محدد. قبل الضغط على Enter، اسأل نفسك: "ما الذي أتوقع رؤيته، وماذا سأفعل إن رأيت شيئًا مختلفًا؟" هذا الانضباط — صياغة فرضية قبل تشغيل الاستعلام — هو ما يُبقي وقت التحقيق محدودًا. استكشاف السجلات عشوائيًا دون فرضية يمكن أن يستهلك ساعات دون الوصول إلى إجابة.