صمّم سجلّاتك كأنها API

أسئلة العمل يجيب عنها CloudWatch Logs Insights: عقود سطور السجل، لماذا توسيع سطر موجود يتفوق على إضافة سطر جديد، مطبّات JSON escaping، وفخ مدة الاحتفاظ.

السؤال الذي غيّر طريقتي في كتابة سطور السجل لم يكن تقنيًا. كان سؤالًا من أحد أصحاب المصلحة: «كم عملية رفع فشلت أمس — وكم منها كان خطأ العملاء أنفسهم؟». لم تكن هناك لوحة تعرضه، ولا جدول يحتفظ به — فالسجلات المعالَجة تُحذف دوريًا حسب التصميم. الأثر الوحيد الذي يتذكّر بثبات كان ملفات السجل، وقدرة السجلات على الإجابة عن سؤالِ عملٍ تعتمد كليًا على قرارات اتخذها شخص ما قبل أشهر، بلا اكتراث، وهو يكتب سطر تسجيل.

هذه هي الأطروحة: سطر السجل الذي تستهلكه الاستعلامات هو API. له مستهلكون، وله مخطط (schema)، وعليه التزامات توافق — لكنه لا يُعامَل كذلك حتى يصل أول سؤال صعب.

ما الذي جعل الأسئلة قابلة للإجابة

كانت المنصة خطّ معالجة دفعات serverless (مراحل Lambda وSQS) تكتب خطواته JSON منظّمًا مع رموز رسائل ثابتة. السطور التي صارت مهمة لاحقًا امتلكت ثلاث خصائص تستحق النسخ:

  1. رمز ثابت قابل للـ grep لكل نوع حدث — الاستعلام filter message like /MSG_RESULT_SUMMARY/ ينجو من تغيّر الصياغة؛ بينما filter message like /finished processing/ يموت أول مرة «يحسّن» فيها أحدهم الجملة.
  2. الحمولة المهمة داخل السطر نفسه. سطر الملخص لكل دفعة معالَجة كان يحمل نتائج كل سجل — [{"id":519,"status":"REGISTER_ERROR"}, …] — فيصبح العدّ استعلامًا واحدًا، لا عملية ربط عبر أنظمة.
  3. التسجيل عند الحدود، قبل أن يتاح لأي شيء آخر أن يفشل. كان الملخص يُكتب لحظة استلام الرسالة، قبل أن يرفض التحقق أي عنصر. سطور السجل التي تنطلق داخل فروع شرطية ترث كل أخطاء تلك الفروع.

بهذا أجاب Logs Insights عن إجماليات النجاح/الفشل اليومية باستعلام واحد. ثم جاء سؤال المتابعة — أي الإخفاقات يخص أي جمهور — فكشف الحدود.

وسّع السطر الموجود، لا تضف سطرًا جديدًا

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

  • سطر جديد يعني أن كل استعلام قائم، وكل مستهلك مستقبلي، صار عليه مطابقة سطرين لكل سجل.
  • السطر الجديد قد ينطلق بمعدل مختلف عن القديم (إعادات محاولة، إخفاقات جزئية)، فتنحرف الأعداد عن بعضها بهدوء.
  • السطر القديم يبقى ناقصًا إلى الأبد.

الإصلاح الأفضل كان حقلًا واحدًا: دمغ "type":"user" أو "type":"admin" على كل عنصر في الحمولة التي كان سطر الملخص يسجلها أصلًا. كل الاستعلامات القائمة ظلت تعمل؛ والسؤال الجديد صار مجرد فلتر. توسيع عقد قائم يتفوق على إنشاء عقد موازٍ — القاعدة نفسها في تصميم الـ API، وللأسباب نفسها.

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

المطبّات التي كلّفت ساعات حقيقية

  • الـ escaping في المُسلسِل يكسر المطابقة الساذجة. كان المسجّل يكتب JSON، والحمولة مصفوفة مضمّنة داخل نص الرسالة، فتصل علامات الاقتباس في السطر الخام مسبوقة بشرطات مائلة عكسية. الاستعلام البديهي — filter message like /"status":"REGISTER_ERROR"/ — لم يطابق شيئًا. عدّ الرموز المجردة (وحساب التكرارات داخل السطر بطول السلاسل الفرعية) نجح؛ أما افتراض أن سطر السجل يشبه ما كتبتَه في الكود فلم ينجح. اختبر استعلاماتك دائمًا على سطور مخزنة حقيقية.
  • طوابع وقت السجلات بتوقيت UTC؛ وأصحاب المصلحة ليسوا كذلك. كل عدّ «يومي» يحتاج إلى تطبيق فرق التوقيت عمدًا، مرة واحدة، داخل الاستعلام — لا إلى إعادة اكتشافه مع كل حادثة.
  • مدة الاحتفاظ باب باتجاه واحد. لم تكن قاعدة البيانات بديلًا: الجداول التشغيلية تُحذف نهائيًا وفق جدول قصير، وسجلات الـ logs لها نافذتها أيضًا. كل ما لم يوضع في السطر لحظة كتابته يظل غير قابل للاسترداد عن كامل الفترة السابقة للإصلاح. استطعنا اشتقاق أحد الجمهورين نظيفًا من التاريخ (لأن نمط عناوينه مميز)، أما الآخر فلا — لا تناظرٌ دائم سببه حقل واحد غائب.

اجعلها أصلًا للفريق

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

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