10 نوفمبر 2025·5 دقيقة قراءة

تتبّع OpenTelemetry في Go لرصد شامل لواجهات API من البداية للنهاية

شرح تتبّع OpenTelemetry في Go مع خطوات عملية لربط التتبّعات والمقاييس والسجلات عبر طلبات HTTP، مهام الخلفية، واستدعاءات الأطراف الثالثة.

تتبّع OpenTelemetry في Go لرصد شامل لواجهات API من البداية للنهاية

ماذا يعني التتبّع من البداية إلى النهاية لواجهة Go API

التتبّع (trace) هو خط الزمن لطلب واحد أثناء انتقاله عبر نظامك. يبدأ عند وصول استدعاء API وينتهي عند إرسال الاستجابة.

ضمن التتبّع توجد سبانز (spans). السبَان هو خطوة مؤقتة واحدة، مثل “تحليل الطلب” أو “تشغيل استعلام SQL” أو “استدعاء مزود الدفع”. يمكن أن تحمل السبانات أيضًا تفاصيل مفيدة، مثل رمز حالة HTTP، معرف مستخدم مؤمَّن، أو عدد الصفوف التي أعادها الاستعلام.

«من البداية إلى النهاية» يعني أن التتبّع لا يتوقف عند المعالج الأول. يتابع الطلب عبر الأماكن التي تختبئ فيها المشاكل عادة: الوسائط (middleware)، استعلامات قاعدة البيانات، استدعاءات الكاش، مهام الخلفية، واجهات طرف ثالث (دفع، بريد إلكتروني، خرائط)، وخدمات داخلية أخرى.

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

أيضًا من الصعب ربط السجلات عبر الخدمات. قد يكون لديك سطر سجل في API وآخر في عامل، ولا شيء بينهما. مع التتبّع، تشترك تلك الأحداث في نفس معرف التتبّع (trace ID)، فيمكنك اتباع السلسلة دون تخمين.

التتبّعات والمقاييس والسجلات: كيف تكمل بعضها بعضًا

التتبّعات، والمقاييس، والسجلات تجيب عن أسئلة مختلفة.

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

المقاييس تُظهر الاتجاه. إنها أفضل أداة للتنبيهات لأنها مستقرة ورخيصة للتجميع: نسب الكمون، معدل الطلبات، معدل الأخطاء، عمق قائمة الانتظار، والاشغال.

السجلات تُعطيك «لماذا» بنص واضح: فشل تحقق، مدخلات غير متوقعة، حالات حافة، وقرارات اتخذها الكود.

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

نموذج ذهني بسيط

استخدم كل إشارة لما هي جيدة فيه:

  • المقاييس تخبرك أن هناك خطبًا ما.
  • التتبّعات تُظهر أين ذهبت المدة في طلب واحد.
  • السجلات تشرح ما قرره الكود ولماذا.

مثال: نقطة النهاية POST /checkout تبدأ بالتهنيج. تُظهر المقاييس ارتفاعًا في الكمون p95. يُظهر التتبّع أن معظم الوقت داخل استدعاء مزود الدفع. سطر سجل مرتبط داخل ذلك السبان يظهر إعادة محاولات بسبب 502، ما يشير إلى إعدادات backoff أو حادثة في الجهة العليا.

قبل إضافة الشيفرة: التسمية، العيّنة، وماذا تُسجل

قليل من التخطيط مقدمًا يجعل التتبّعات قابلة للبحث لاحقًا. بدون ذلك، ستجمع البيانات، لكن الأسئلة الأساسية تصبح صعبة: «هل هذا بيئة staging أم prod؟» «أي خدمة بدأت المشكلة؟»

ابدأ بهوية متسقة. اختر service.name واضحًا لكل API Go (مثلًا checkout-api) وحقل بيئة واحد مثل deployment.environment=dev|staging|prod. حافظ على ثباتها. إذا تغيرت الأسماء في منتصف الأسبوع، ستبدو المخططات والبحث وكأنها أنظمة مختلفة.

بعدها قرر سياسة العيّنة (sampling). تتبع كل طلب رائع في التطوير، لكنه غالبًا مكلف في الإنتاج. نهج شائع هو أخذ عيّنة صغيرة من الحركة العادية والاحتفاظ بالتتبّعات للأخطاء والطلبات البطيئة. إذا عرفت بالفعل أن نقاط نهاية معينة عالية الحجم (فحوصات الصحة، الاستطلاعات)، فقلّل تتبعها أو لا تتبعها.

أخيرًا، اتفق على ما ستضعه كوسوم على السبانات وما لن تجمعه أبدًا. احتفظ بقائمة قصيرة من السمات المسموح بها التي تساعدك على ربط الأحداث عبر الخدمات، وضع قواعد خصوصية بسيطة.

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

خطوة بخطوة: إضافة تتبّع OpenTelemetry إلى API HTTP في Go

ستنشئ مزود تتبّعات (tracer provider) مرة واحدة عند بدء التشغيل. هذا يحدد إلى أين تذهب السبانات وأي سمات الموارد تُرفق بكل سبان.

1) تهيئة OpenTelemetry

تأكد من ضبط service.name. بدونه، تختلط التتبّعات من خدمات مختلفة وتصير المخططات صعبة القراءة.

// main.go (startup)
exp, _ := stdouttrace.New(stdouttrace.WithPrettyPrint())

res, _ := resource.New(context.Background(),
	resource.WithAttributes(
		semconv.ServiceName("checkout-api"),
	),
)

tp := sdktrace.NewTracerProvider(
	sdktrace.WithBatcher(exp),
	sdktrace.WithResource(res),
)

otel.SetTracerProvider(tp)

هذا هو الأساس لتتبّع OpenTelemetry في Go. بعد ذلك، تحتاج إلى سبان لكل طلب وارد.

2) أضف وسيط HTTP وسجّل الحقول الأساسية

استخدم وسيط HTTP يبدأ سبانًا تلقائيًا ويسجّل رمز الحالة والمدة. عيّن اسم السبان باستخدام قالب المسار (مثل /users/:id) وليس URL الخام، وإلا سينتهي بك الأمر بآلاف المسارات الفريدة.

استهدف خطًا أساسياً نظيفًا: سبان خادم واحد لكل طلب، أسماء سبانات مبنية على المسار، تسجيل حالة HTTP، انعكاس فشل المعالج كخطأ في السبان، ومدة مرئية في عارض التتبّع.

3) اجعل الأخطاء واضحة

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

في المعالجات، يمكنك فعل:

span := trace.SpanFromContext(r.Context())
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())

4) تحقق من معرفات التتبّع محليًا

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

حمل السياق عبر قواعد البيانات والاستدعاءات الخارجية

النشر حيث تحتاج
انشر إلى السحابة التي تختارها أو AppMaster Cloud دون تغيير طريقة قياس الخدمات.
انشر الآن

تتوقف الرؤية من البداية إلى النهاية عندما تفقد context.Context. يجب أن يكون سياق الطلب الوارد هو الخيط الذي تمرره إلى كل استدعاء قاعدة بيانات، استدعاء HTTP، ومساعد. إذا استبدلته بـ context.Background() أو نسيت تمريره لأسفل، سيتحول تتبّعك إلى أعمال منفصلة غير مرتبطة.

لـ HTTP الصادر، استخدم نقلًا (transport) مؤتمتًا حتى يصبح كل Do(req) سبانًا فرعيًا تحت الطلب الحالي. قم بتمرير رؤوس W3C trace على الطلبات الصادرة حتى تتمكن الخدمات اللاحقة من إلحاق سباناتها بنفس التتبّع.

تحتاج استدعاءات قاعدة البيانات إلى نفس المعاملة. استخدم موصلًا مؤتمتًا أو لفّ الاستدعاءات بسبانات حول QueryContext و ExecContext. سجّل فقط التفاصيل الآمنة. تريد العثور على الاستعلامات البطيئة دون تسريب بيانات.

السمات المفيدة ومنخفضة المخاطر تشمل اسم العملية (مثلاً SELECT user_by_id)، اسم الجدول أو النموذج، عدد الصفوف (عدد فقط)، المدة، عدد المحاولات، ونوع الخطأ العام (مهلة، ملغى، قيد).

المهلات جزء من القصة، ليست مجرد فشل. اضبطها باستخدام context.WithTimeout لقاعدة البيانات والاستدعاءات الخارجية، ودع الإلغاءات تنتقل لأعلى. عندما يُلغى استدعاء، علّم السبان كخطأ وأضف سببًا قصيرًا مثل deadline_exceeded.

تتبّع مهام الخلفية والطوابير

توحيد أسماء الخدمات
ولّد خدمات Go فعلية واحتفظ باتساق أسماء الخدمات ووسوم البيئة عبر التطبيقات.
ابدأ الآن

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

الحل واضح: عندما تدرج مهمة في الطابور، احفظ سياق التتبّع الحالي واخزنه في بيانات المهمة (حمولة، رؤوس، أو سمات، بحسب الطابور). عندما يبدأ العامل، استخرج ذلك السياق وابدأ سبانًا جديدًا كفرعي للأصلي.

نشر السياق بأمان

انسخ سياق التتبّع فقط، لا بيانات المستخدم.

  • حقن معرفات التتبّع وعلامات العيّنة فقط (نمط W3C traceparent).
  • احتفظ به منفصلاً عن الحقول التجارية (مثلاً حقل مخصص "otel" أو "trace").
  • عامله كمدخل غير موثوق عند قراءته مرة أخرى (تحقق من الصيغة، وتعامل مع البيانات المفقودة).
  • تجنّب وضع رموز، بريد إلكتروني، أو محتوى الطلب في بيانات المهمة.

السبانات التي تضيفها (بدون إغراق التتبّعات بالضوضاء)

التتبّعات القابلة للقراءة عادةً تتضمن بضع سبانات ذات معنى، لا عشرات الصغيرة. أنشئ سبانات حول الحدود ونقاط الانتظار. بداية جيدة هي سبان enqueue في معالج الـ API وسبان job.run في العامل.

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

المهام المجدولة تحتاج أصلًا أيضًا. إذا لم يكن هناك طلب وارد، فأنشئ سبان جذر جديد لكل مرة ووسمه باسم الجدول.

ربط السجلات بالتتبّعات (واحفظ السجلات آمنة)

التتبّعات تُخبرك أين ذهب الوقت. السجلات تُخبرك ماذا حدث ولماذا. أبسط طريقة للربط هي إضافة trace_id و span_id إلى كل سطر سجل كحقول مهيكلة.

في Go، استخرج السبان النشط من context.Context وغنّي اللوجّر لديك مرة واحدة لكل طلب (أو مهمة). عندئذٍ كل سطر سجل يشير إلى تتبّع محدد.

span := trace.SpanFromContext(ctx)
sc := span.SpanContext()
logger := baseLogger.With(
  "trace_id", sc.TraceID().String(),
  "span_id",  sc.SpanID().String(),
)
logger.Info("charge_started", "order_id", orderID)

هذا يكفي للانتقال من سطر سجل إلى السبان الدقيق الذي كان يعمل عندما حدث. كما يجعل السياق المفقود واضحًا: سيكون trace_id فارغًا.

اجعل السجلات مفيدة دون تسريب بيانات شخصية

تعيش السجلات غالبًا لفترة أطول وتنتقل أبعد من التتبّعات، لذا كن أكثر صرامة. فضّل المعرفات الثابتة والنتائج: user_id، order_id، payment_provider، status، وerror_code. إذا اضطررت لتسجيل مدخلات المستخدم، فقم بإخفائها أولًا وقيّد الطول.

سهّل تجميع الأخطاء

استخدم أسماء أحداث وأنواع أخطاء متسقة حتى تستطيع عدّها والبحث عنها. إذا تغيرت الصياغة في كل مرة، سيبدو نفس المشكلة كقضايا متعددة.

أضف مقاييس تساعدك فعليًا في العثور على المشاكل

إنشاء أداة داخلية
ابنِ أدوات داخلية ولوحات إدارة بواجهات يمكنك مراقبتها منذ يومها الأول.
ابدأ الآن

المقاييس هي نظام الإنذار المبكر. في إعداد يستخدم التتبّع بالفعل، يجب أن تجيب المقاييس عن: كم تكرارها، ومدى سوءها، ومنذ متى.

ابدأ بمجموعة صغيرة تعمل مع أي API تقريبًا: عدد الطلبات، عدد الأخطاء (حسب فئة الحالة)، نسب الكمون (p50، p95، p99)، الطلبات الجارية، وكمون التبعيات لقاعدة البيانات والمكالمات الخارجية المهمة.

للحفاظ على توافق المقاييس مع التتبّع، استخدم نفس قوالب المسار والأسماء. إذا كانت سباناتك تستخدم /users/{id}، يجب أن تستخدم مقاييسك ذلك أيضًا. حين يظهر مخطط "p95 لـ /checkout ارتفع"، تستطيع الانتقال مباشرة إلى التتبّعات المصفاة لذلك المسار.

كن حذرًا مع الوسوم (السمات). وسم واحد سيئ يمكن أن يفجّر التكاليف ويجعل لوحات التحكم عديمة الفائدة. قالب المسار، الطريقة، فئة الحالة، واسم الخدمة عادة آمنة. معرفات المستخدم، البراهين البريدية، URLs كاملة، ورسائل الخطأ الخام عادة ليست كذلك.

أضف بعض المقاييس المخصّصة للأحداث الحرجة للعمل (مثلاً: بدء/إكمال عملية checkout، فشل الدفع حسب مجموعة رموز النتيجة، نجاح المهام الخلفية مقابل إعادة المحاولة). حافظ على المجموعة صغيرة وحذف ما لا تستخدمه.

تصدير التليمتري والتدريج الآمن

التصدير هو المكان الذي يصبح فيه OpenTelemetry فعلًا. يجب أن يرسل خدمتك السبانات، والمقاييس، والسجلات إلى مكان موثوق دون إبطاء الطلبات.

للتطوير المحلي، اجعلها بسيطة. مُصدر إلى الكونسول (أو OTLP إلى جامع محلي) يسمح برؤية التتبّعات بسرعة والتحقق من أسماء السبانات والسمات. في الإنتاج، فضّل OTLP إلى عامل أو OpenTelemetry Collector قريب من الخدمة. يمنحك نقطة واحدة للتعامل مع إعادة المحاولة، والتوجيه، والفلترة.

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

العيّنة تحافظ على التكاليف متوقعة. ابدأ بعينة رأسية (head-based sampling) صغيرة (مثلاً 1-10% من الطلبات)، ثم أضف قواعد بسيطة: دائماً خزّن الأخطاء، ودائماً خزّن الطلبات البطيئة فوق حد معيّن. إذا كان لديك مهام خلفية عالية الحجم، خفّض عيّنتها.

نشر بالتدريج: التطوير بعينات 100%، التجهيز (staging) بحركة واقعية وعيّنة أقل، ثم الإنتاج بعينات محافظة وتنبيهات عن فشل المصدّر.

أخطاء شائعة تفسد الرؤية من البداية إلى النهاية

ربط الواجهة الأمامية بواجهات متتبّعة
أصدر تطبيقات ويب ومحمول مع خدمات خلفية يمكنك تصحيحها باستخدام معرفات التتبّع في السجلات.
جرّب الآن

تفشل الرؤية عادة لأسباب بسيطة: البيانات موجودة، لكنها لا ترتبط.

القضايا التي تكسر التتبّع الموزع في Go عادة تكون:

  • إسقاط السياق بين الطبقات. المعالج ينشئ سبانًا، لكن استدعاء قاعدة بيانات، عميل HTTP، أو goroutine يستخدم context.Background() بدلاً من سياق الطلب.
  • إعادة الأخطاء دون تمييز السبانات. إن لم تسجّل الخطأ وتعيّن حالة السبان، ستبدو التتبّعات "خضراء" حتى لو شاهد المستخدمون 500s.
  • تتبّع كل شيء. إذا أصبح كل مساعد سبانًا، تتحول التتبّعات إلى ضوضاء وتكلف أكثر.
  • إضافة سمات عالية التميّز. عناوين URL كاملة مع معرفات، إيميلات، قيم SQL الخام، أجسام الطلب، أو رسائل الخطأ الخام يمكن أن تخلق ملايين قيم فريدة.
  • تقييم الأداء بحسب المتوسطات. الحوادث تظهر في النسب (p95/p99) ومعدل الأخطاء، ليس الكمون المتوسط.

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

قائمة تحقق عملية "مكتمل"

رسم المنطق بتدفقات مرئية
حوّل تدفقات الأعمال إلى خدمات يمكن تتبعها عبر المعالجات والمهام والخدمات التابعة.
ابنِ الآن

ستكون قريبًا عندما تستطيع الانتقال من تقرير مستخدم إلى الطلب الدقيق، ثم تتبعه عبر كل قفزة.

  • اختر سطر سجل API واحد وحدد التتبّع الدقيق بواسطة trace_id. أكد أن سجلات أعمق من نفس الطلب (قاعدة بيانات، عميل HTTP، عامل) تحمل نفس سياق التتبّع.
  • افتح التتبّع وتحقق من التداخل: سبان خادم HTTP في الأعلى، مع سبانات فرعية لاستدعاءات DB وواجهات الطرف الثالث. القائمة المسطحة غالبًا تعني فقدان السياق.
  • حفّز مهمة خلفية من طلب API (مثل إرسال إيصال بريد إلكتروني) وتأكد أن سبان العامل مرتبط بالطلب.
  • تحقق من المقاييس للأساسيات: عدد الطلبات، معدل الأخطاء، ونسب الكمون. أكد أنه يمكنك التصفية بحسب المسار أو العملية.
  • امسح السمات والسجلات للأمان: لا كلمات مرور، رموز، أرقام بطاقات ائتمان كاملة، أو بيانات شخصية خام.

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

إذا كنت تولّد خلفيات Go (على سبيل المثال باستخدام AppMaster)، فالمساعدة هي جعل هذه القائمة جزءًا من روتين الإصدار حتى تبقى النقاط النهائية والعاملين قابليْن للتتبّع مع نمو التطبيق. AppMaster (appmaster.io) يولّد خدمات Go حقيقية، لذا يمكنك توحيد إعداد OpenTelemetry واحد ونقله عبر الخدمات ومهام الخلفية.

مثال: تصحيح عملية checkout بطيئة عبر الخدمات

رسالة عميل تقول: "الدفع يتجمد أحيانًا." لا يمكنك إعادة إنتاجه عند الطلب، وهذا بالضبط حين يفيدك تتبّع OpenTelemetry في Go.

ابدأ بالمقاييس لفهم شكل المشكلة. انظر إلى معدل الطلبات، معدل الأخطاء، وp95 أو p99 لوقت التأخر لنقطة نهاية checkout. إذا حدث التباطؤ في دفعات قصيرة وفقط لشرائح من الطلبات، فعادةً ما يشير إلى تبعية، أو انتظار في طابور، أو سلوك إعادة المحاولة بدلاً من استخدام المعالج CPU.

بعدها افتح تتبّعًا بطيئًا من نفس نافذة الوقت. غالبًا يكفي تتبّع واحد. قد يكون checkout الصحي 300 إلى 600 ملّي ثانية كاملًا. السيء قد يكون 8 إلى 12 ثانية، ومعظم الوقت داخل سبان واحد.

نمط شائع يبدو هكذا: معالج الـ API سريع، عمل قاعدة البيانات مقبول، ثم سبان مزود الدفع يُظهر محاولات مع backoff، واستدعاء لاحق ينتظر خلف قفل أو طابور. قد تعود الاستجابة 200، لذا التنبيهات المبنية فقط على الأخطاء لا تُطلق.

تُخبرك السجلات المترابطة بعد ذلك بالمسار الدقيق نصيًا: "إعادة محاولة شحن Stripe: مهلة"، تليها "تم إلغاء معاملة DB: فشل في التسلسل"، تليها "إعادة محاولة تدفق الدفع". هذا إشارة واضحة أنك تتعامل مع بضعة مشاكل صغيرة تتجمع لتنتج تجربة مستخدم سيئة.

بمجرد أن تعثر على عنق الزجاجة، ما يحافظ على قابلية القراءة مع الوقت هو الاتساق. قم بتوحيد أسماء السبانات، والسمات (هاش معرف المستخدم الآمن، معرف الطلب، اسم التبعية)، وقواعد العيّنة عبر الخدمات حتى يقرأ الجميع التتبّعات بنفس الطريقة.

من السهل أن تبدأ
أنشئ شيئًا رائعًا

تجربة مع AppMaster مع خطة مجانية.
عندما تكون جاهزًا ، يمكنك اختيار الاشتراك المناسب.

البدء