Get a quote

السجلات المنظمة في Go: كيف تبني سياقاً يساعدك فعلاً في الإنتاج

السجلات التي تقول 'مستخدم غير موجود' دون سياق هي أغلى أنواع الضوضاء في الإنتاج. السجلات المنظمة مع نشر السياق عبر طلبات HTTP تحل هذه المشكلة، وحزمة slog في Go 1.21 تجعلها الخيار الافتراضي.

السجلات التي تقول "مستخدم غير موجود" أو "فشل الدفع" دون أي سياق محيط هي أغلى أنواع الضوضاء في بيئة الإنتاج. تجد السطر لكن لا تعرف السبب: أي مستخدم، أي مسار طلب، أي مستأجر في النظام، وما كانت حالة النظام قبل ثلاث خطوات. السجلات المنظمة مع نشر السياق بشكل منتظم تحل هذه المشكلة، وحزمة slog المدمجة في Go 1.21 تجعلها الخيار الطبيعي للخدمات الجديدة.

ما الذي يتعطل في غياب السجلات المنظمة؟

الأعراض تظهر أثناء الحوادث. المهندس المناوب يفتح CloudWatch أو Datadog، يبحث عن نص الخطأ، ويجد 400 سطر سجل بلا سياق. كل سطر يقول "فشل استعلام قاعدة البيانات" مع تتبع مكدس عام. بدون معرف الطلب أو معرف المستأجر أو اسم العملية، يستحيل تتبع حادثة واحدة من نقطة دخول HTTP إلى موقع الخطأ.

هذا ليس افتراضياً. كل خدمة Go تبدأ بـ fmt.Println أو log.Printf تنتهي بهذه الحالة خلال ستة أشهر من حركة مرور الإنتاج الحقيقية.

كيف يغير slog النموذج في Go 1.21

slog يُضمَّن مع Go 1.21 كمسجل هيكلي في المكتبة القياسية. يمكّنك من إرفاق أزواج المفاتيح والقيم بكل استدعاء تسجيل، وإنشاء Logger يحمل مجموعة ثابتة من الحقول عبر الاستدعاءات، مع معالجات قابلة للتوصيل تُخرج JSON أو نصاً.

النموذج الذي يعمل في خلفيات SaaS هو المسجل المرتبط بالطلب: مسجل واحد لكل طلب، يُنشأ عند نقطة دخول HTTP أو gRPC، مُحمَّل مسبقاً بالحقول التي تحدد الطلب، ثم يُمرَّر عبر السياق لكي تستطيع الدوال اللاحقة التسجيل إليه.

func NewRequestLogger(parent *slog.Logger, r *http.Request, tenantID, requestID string) *slog.Logger {
    return parent.With(
        slog.String("tenant_id", tenantID),
        slog.String("request_id", requestID),
        slog.String("method", r.Method),
        slog.String("path", r.URL.Path),
    )
}

كل استدعاء تسجيل بهذا المسجل يحمل الحقول الأربعة تلقائياً.

ما الحقول التي يجب أن تظهر في كل سطر سجل؟

في نشر SaaS بالمنطقة، مجموعة الحقول التي تسرع حل الحوادث بشكل متسق:

  • tenant_id: مطلوب في كل سطر في النظام متعدد المستأجرين
  • request_id: UUID يُولَّد عند نقطة الدخول
  • trace_id: إذا كنت تشغّل تتبعاً موزعاً مع OpenTelemetry
  • user_id: عندما يكون الطلب مصادقاً عليه
  • operation: اسم العملية المنجزة
  • duration_ms: لأي عملية قابلة للقياس

معالجة الأخطاء في السجلات المنظمة

أكثر الأنماط فائدة للسجلات هو إرفاق الخطأ كحقل هيكلي:

if err != nil {
    logger.Error("failed to charge payment",
        slog.String("payment_id", paymentID),
        slog.String("error", err.Error()),
    )
    return err
}

للأخطاء من أنظمة خارجية مثل Stripe أو AWS، ضمّن رمز الخطأ الخارجي. هذا الحقل هو الفرق بين "فشل الدفع" و"فشل الدفع: رمز Stripe insufficient_funds".

مستويات السجل في الإنتاج

معظم فرق SaaS تُعقّد إعداد مستويات السجل. النموذج الذي يعمل:

  • ERROR: شيء معطوب ويحتاج إنساناً للنظر فيه
  • WARN: شيء غير طبيعي لكن النظام تعافى
  • INFO: أحداث دورة الحياة المهمة فقط
  • DEBUG: معطل في الإنتاج

لا تسجّل على مستوى INFO لكل استعلام قاعدة بيانات أو ضربة ذاكرة تخزين مؤقت. استخدم المقاييس للأعداد والزمن واحتفظ بالسجلات للأحداث المتقطعة.

أخذ عينات من السجلات عالية الحجم

خدمة SaaS تخدم 500 طلب في الثانية مع تسجيل INFO الكامل تُنتج حجماً يكلف أموالاً حقيقية في CloudWatch. عيّنات بسيطة:

func ShouldSampleInfo() bool {
    return rand.Intn(100) < 10 // معدل عينة 10%
}

طبّق هذا على مستوى الوسيط لإتمامات الطلبات الروتينية. لا تأخذ عينات أبداً من الأخطاء أو التحذيرات. هذا النمط يقلل تكاليف CloudWatch بنسبة 60 إلى 80 بالمئة.

هل تبني خدمات Go في لبنان أو المنطقة؟

فريق Voxire يبني خلفيات Go SaaS ويصمم مكدسات المراقبة للشركات في لبنان والمنطقة.

https://voxire.com/get-a-quote/

العودة إلى المدونة
Chat on WhatsApp