التتبع الموزع وOpenTelemetry

التوصيل باستخدام OTel

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

التوصيل باستخدام OTel

يمنحك OpenTelemetry مسارَين مختلفَين لإضافة التتبع إلى خدمة ما: التوصيل التلقائي، الذي يعمل دون أي تغيير في الكود، والتوصيل اليدوي، حيث تكتب استدعاءات SDK صريحة لإنشاء الامتدادات وإرفاق السمات وتسجيل الأحداث. في أنظمة الإنتاج بمقياس Google، تستخدم دائمًا تقريبًا الأسلوبَين معًا — التلقائي للأجزاء الروتينية (HTTP وقاعدة البيانات وgRPC)، واليدوي للمنطق التجاري الحرج الذي لا يستطيع الإطار رؤيته. يعلّمك هذا الدرس متى تستخدم كل منهما بالضبط، وكيف تفعل ذلك بالشكل الصحيح.

التوصيل التلقائي: المراقبة بدون تعديل الكود

يعمل التوصيل التلقائي عن طريق تغليف المكتبات المعروفة عند وقت التحميل. يعترض وكيل OTel (JVM، وPython، وNode.js) أو خطافات SDK استدعاءات الأطر الشائعة — Django وFlask وExpress وSpring Boot وgRPC وpsycopg2 وredis-py — وينشئ امتدادات تلقائيًا بإعدادات افتراضية معقولة. تحصل على امتدادات لكل طلب HTTP وكل استعلام DB وكل استدعاء خارجي دون لمس كود التطبيق.

مثال Python — تفعيل التوصيل التلقائي بدون تغيير الكود:

# Install the OTel Python agent and Flask instrumentation pip install opentelemetry-distro opentelemetry-exporter-otlp opentelemetry-bootstrap -a install # auto-detects installed libs and installs their instrumentation # Run your Flask app under the agent opentelemetry-instrument \ --service_name order-service \ --exporter_otlp_endpoint http://otel-collector:4317 \ --exporter_otlp_protocol grpc \ python app.py

هذا الأمر الواحد يُغلّف العملية. كل مُعالج مسار Flask، وكل استعلام SQLAlchemy، وكل استدعاء Redis يُصدر الآن امتدادات — دون أي تغيير في المصدر. يحقن الوكيل ترويسة traceparent بمعيار W3C في جميع استدعاءات HTTP الصادرة تلقائيًا، مما يضمن انتشار التتبعات عبر حدود الخدمات.

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

التوصيل اليدوي: توصيف ما يهم

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

إليك مثالًا واقعيًا بلغة Python لدالة معالجة الدفع — النوع من الكود الذي يهم فيه فهم توزيع الكمون فعلًا في الإنتاج:

from opentelemetry import trace from opentelemetry.trace import SpanKind, StatusCode tracer = trace.get_tracer("payments.service", "1.4.2") def process_payment(order_id: str, amount_cents: int, payment_method: str) -> dict: with tracer.start_as_current_span( "payments.process", kind=SpanKind.INTERNAL, ) as span: # --- Attributes: structured facts about this unit of work --- span.set_attribute("order.id", order_id) span.set_attribute("payment.method", payment_method) span.set_attribute("payment.amount_cents", amount_cents) span.set_attribute("payment.currency", "USD") # --- Span Event: record a timestamped moment --- span.add_event("fraud_check.start") fraud_result = run_fraud_check(order_id, amount_cents) span.add_event("fraud_check.complete", { "fraud.score": fraud_result.score, "fraud.decision": fraud_result.decision, }) if fraud_result.decision == "block": span.set_status(StatusCode.ERROR, "Payment blocked by fraud check") span.set_attribute("payment.blocked", True) raise PaymentBlockedError(order_id) with tracer.start_as_current_span("gateway.charge") as gw_span: gw_span.set_attribute("gateway.provider", "stripe") response = stripe.charge(order_id, amount_cents) gw_span.set_attribute("gateway.charge_id", response.charge_id) span.set_attribute("payment.success", True) return {"charge_id": response.charge_id}

يمنحك هذا شلالًا من التتبع يُظهر: امتداد payments.process الكامل مع مدة فحص الاحتيال مرئية كفوارق أحداث، وامتداد الطفل المتداخل gateway.charge مع بيانات وصفية خاصة بـ Stripe، وكل سمة متاحة كمرشّح في واجهة Jaeger أو Tempo — إجابة فورية على: "أرِني كل التتبعات التي فيها payment.amount_cents > 100000 وfraud.decision == allow واستغرقت أكثر من ثانيتين."

السمات: التصميم من أجل القابلية للاستعلام

السمات هي جوهر التصحيح القائم على التتبع. يُعرّف OTel اتفاقيات دلالية — مفردات مشتركة تجعل الامتدادات من أي خدمة، بأي لغة، تبدو متماثلة في الخلفية. اتبعها بدقة.

OTel Span Anatomy — attributes, events, status, parent link payments.process Attributes: order.id = "ord-9821" payment.amount_cents = 45000 payment.method = "card" http.status_code = 200 Span Events (timestamped) t+0ms → fraud_check.start t+43ms → fraud_check.complete { score: 0.12 } t+48ms → gateway.charge.start t+310ms→ gateway.charge.complete { id: ch_xyz } gateway.charge (child) gateway.provider = "stripe" gateway.charge_id = "ch_xyz" Span Status UNSET (default — no error) ERROR (set explicitly on failure) Attributes persist for the span lifetime; events are timestamped snapshots mid-span.
تشريح امتداد OTel — السمات تُصنّف العمل، والأحداث تلتقط اللحظات داخله، والامتدادات الفرعية تُمثّل العمليات الفرعية.

الاتفاقيات الدلالية الأساسية التي يجب حفظها (من opentelemetry-semantic-conventions):

  • http.request.method، وhttp.response.status_code، وurl.path — امتدادات HTTP
  • db.system، وdb.name، وdb.operation.name، وdb.query.text — امتدادات قاعدة البيانات
  • messaging.system، وmessaging.destination.name — Kafka وSQS وRabbitMQ
  • rpc.system، وrpc.service، وrpc.method — امتدادات gRPC
  • service.name، وservice.version، وdeployment.environment — سمات الموارد (تُضبط مرة واحدة، وتُطبَّق على جميع الامتدادات من تلك العملية)
سمات الموارد مقابل سمات الامتدادات. سمات الموارد تصف العملية (اسم الخدمة والإصدار والمضيف وpod في Kubernetes). اضبطها مرة واحدة عند تهيئة SDK عبر OTEL_RESOURCE_ATTRIBUTES أو فئة SDK المسماة Resource. أما سمات الامتداد فتصف هذه العملية المحددة. لا تكرر أبدًا بيانات الموارد على كل امتداد — فهذا يُضخّم التخزين وهو زائد عن الحاجة لأن الخلفية تربطها تلقائيًا.

أحداث الامتداد مقابل السجلات: النموذج الذهني الصحيح

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

لا تخزّن بيانات حساسة في السمات أو الأحداث. تُصدَّر سمات الامتداد إلى خلفية التتبع — Jaeger أو Tempo أو خدمة SaaS لجهة خارجية — وتُحتفظ بها أيامًا أو أسابيع في الغالب. لا تضع أبدًا معلومات تعريف شخصية (عناوين البريد الإلكتروني، والأسماء الكاملة، وأرقام بطاقات الدفع، وأرقام الضمان الاجتماعي) في سمة امتداد. خزّن معرّفًا مبهمًا (user.id، أو order.id) وابحث عن المعلومات الشخصية بشكل منفصل في قاعدة بيانات التطبيق عند الحاجة. هذا متطلب من متطلبات اللائحة الأوروبية لحماية البيانات GDPR ومتطلب أمني في الوقت ذاته: خلفية التتبع عادةً لديها سطح تحكم وصول أوسع بكثير من قاعدة بيانات الإنتاج.

تهيئة SDK: موفّر التتبع

قبل أن يُصدَر أي امتداد، يجب أن تُهيّئ عمليتك TracerProvider مع مُصدِّر ومورد. افعل هذا مرة واحدة عند بدء التطبيق — في main() الخاص بك، أو نقطة دخول WSGI، أو إقلاع الإطار. في Python مع تصدير OTLP gRPC إلى Collector:

from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION resource = Resource.create({ SERVICE_NAME: "order-service", SERVICE_VERSION: "2.3.1", "deployment.environment": "production", "k8s.pod.name": os.environ.get("POD_NAME", "unknown"), }) exporter = OTLPSpanExporter( endpoint="http://otel-collector.observability.svc:4317", insecure=True, # TLS terminated at the Collector in-cluster ) provider = TracerProvider(resource=resource) provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__)

يُخزّن BatchSpanProcessor الامتدادات في الذاكرة ويُفرغها بشكل غير متزامن — الخيار الوحيد الآمن للإنتاج. أما SimpleSpanProcessor المتزامن فيُعيق الخيط الاستدعائي عند كل تصدير ويُستخدم فقط في الاختبارات أو أدوات سطر الأوامر.

تجميع كل شيء: ما تفعله فرق كبرى الشركات فعلًا

في الشركات التي تُشغّل مئات من الخدمات المصغّرة، يكون النمط دائمًا: التوصيل التلقائي يتعامل مع طبقة النقل، والتوصيل اليدوي يُعلّق على العمليات التجارية، ومكتبة داخلية مشتركة تُغلّف تمهيد SDK حتى تُهيَّأ كل خدمة باتساق. تُعرّف الفرق تصنيفًا للسمات على مستوى الشركة بأكملها (مفاتيح user.* وorder.* وpayment.* المعتمدة) يُطبَّق بقواعد lint على استدعاءات التتبع. المهندسون الجدد لا يكتبون استدعاءات OTel SDK الخام — بل يستخدمون الغلاف الداخلي الذي يتضمن بالفعل سمات الموارد الصحيحة، ونقطة نهاية المُصدِّر الصحيحة، وتكوين أخذ العينات الصحيح. هذا الاتساق هو ما يجعل التتبعات مفيدة على نطاق واسع: يمكنك كتابة استعلام Jaeger واحد عبر 300 خدمة والحصول على نتائج متسقة لأن كل امتداد يتبع نفس مخطط السمات.