المستوى المطلوب: متوسط — هذا المقال يفترض إنك تعرف الـ Microservices بشكل عام، شغّلت تطبيق Python أو Node على الإنتاج، وعندك فكرة عن Logging و Metrics. لو لسه ما عملتش deploy لـ microservice واحدة، ابدأ بمقال أساسي قبل ده.
لو الطلب في تطبيقك بيمر على 14 microservice وفجأة P95 latency طلع من 280ms لـ 3.2 ثانية، الـ logs العادية مش هتقولك المشكلة فين بالظبط. OpenTelemetry بيرسم رحلة الطلب كاملة في شكل شلال (waterfall) ويوريك السيرفس اللي بيستهلك 2.1 ثانية من الـ 3.2، وبيخلّيك تشخّص الـ regression في دقيقتين بدل 47 دقيقة.
OpenTelemetry للمتوسط: من الـ Logs المبعثرة لـ Trace شلال واحد
المشكلة باختصار
لو عندك e-commerce فيه 14 microservice (auth، cart، pricing، inventory، payment، shipping، notification…)، وطلب checkout واحد بيلمس 9 منهم، الـ logs العادية بترجّع 9 سجلات منفصلة في 9 أماكن منفصلة، بدون أي ربط بينهم. مفيش طريقة تعرف هل المشكلة في pricing service ولا في inventory ولا في الـ DB call جوّا inventory نفسه.
الـ Distributed Tracing بيحل الموضوع ده عن طريق إن كل طلب بياخد trace_id واحد فريد، وكل خطوة بتاخد span_id مربوط بالـ parent، فالنتيجة شجرة كاملة بتقولك بالظبط أي خطوة استهلكت قد إيه ومين كلّف مين.
المفهوم بمثال محقق الشرطة (للمبتدئ تماماً)
تخيّل إن في جريمة حصلت في فندق 14 طابق، والمشتبه به مر على 9 طوابق قبل ما يهرب. لو سألت كل موظف ريسبشن لوحده، كل واحد هيقولك "أيوه شفت حد عابر"، بس مفيش حد يعرف الترتيب أو الوقت اللي قضاه في كل طابق. النتيجة فوضى.
دلوقتي تخيّل إن كل موظف بيكتب وقت الدخول والخروج بتاع المشتبه به في نفس دفتر مركزي بنفس رقم القضية. المحقق يفتح الدفتر، يبص على رقم القضية ده، ويرسم خط زمني كامل في دقيقتين: 14 دقيقة في الطابق الخامس، 22 ثانية في الطابق السابع، 3 دقايق في الطابق العاشر. بكدا عرف فين المشتبه به اتأخر.
الـ trace_id هو رقم القضية، الـ span_id هو السطر اللي كل موظف بيكتبه، والـ parent_span_id بيقولك مين كلّف مين (موظف الاستقبال كلّم موظف الطابق العاشر، اللي كلّم موظف غرفة الخدمات).
التعريف العلمي الدقيق
OpenTelemetry (اختصاراً OTel) هو CNCF Graduated Project (تخرّج 2024) ومعيار صناعي بينتج عن دمج مشروعَي OpenTracing و OpenCensus. بيوفّر SDKs لـ 11 لغة برمجة، و Collector واحد بيستقبل البيانات بـ بروتوكول OTLP ويبعتها لأي backend (Jaeger، Grafana Tempo، Datadog، New Relic، Honeycomb).
التعريف الرسمي من توثيق OpenTelemetry: الـ Trace هو DAG (Directed Acyclic Graph) من Spans. كل Span فيه: name، start_time، end_time، attributes (key-value)، status (OK/ERROR)، و events (نقاط زمنية محددة جوّا الـ span). الفكرة الأصلية رجعت لورقة Dapper من Google (Sigelman et al., 2010)، وهي اللي ألهمت Zipkin و Jaeger وكل الأنظمة اللي بعدها.
الخطوات العملية في 6 دقايق
- ثبّت OpenTelemetry Collector في cluster Kubernetes كـ DaemonSet — مش حاجة في كل service لوحدها، DaemonSet واحد على كل node بياخد كل الـ telemetry.
- ضيف 3 environment variables في كل microservice:
OTEL_SERVICE_NAME(اسم السيرفس)،OTEL_EXPORTER_OTLP_ENDPOINT(عنوان الـ Collector)، وOTEL_TRACES_SAMPLER_ARG(نسبة الـ sampling). - ثبّت auto-instrumentation library للغتك. Python:
pip install opentelemetry-distro opentelemetry-exporter-otlpثمopentelemetry-bootstrap -a install. - شغّل التطبيق بأمر
opentelemetry-instrument python app.pyبدل الأمر العادي. الـ wrapper ده بيحقن instrumentation تلقائي في FastAPI و requests و SQLAlchemy و Redis و Kafka. - اربط الـ Collector بـ Grafana Tempo (مفتوح المصدر، مجاني للاستضافة الذاتية) أو Jaeger.
- افتح Grafana واعرض Trace Explorer. أول trace هيظهر في خلال 30 ثانية.
# app.py — FastAPI + OpenTelemetry auto-instrumentation
# pip install fastapi uvicorn opentelemetry-distro opentelemetry-exporter-otlp
# opentelemetry-bootstrap -a install
#
# التشغيل:
# export OTEL_SERVICE_NAME=pricing-service
# export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
# opentelemetry-instrument uvicorn app:app --host 0.0.0.0 --port 8000
from fastapi import FastAPI
import httpx
from opentelemetry import trace
app = FastAPI()
tracer = trace.get_tracer(__name__)
@app.get("/price/{sku}")
async def get_price(sku: str):
with tracer.start_as_current_span("compute_price") as span:
span.set_attribute("sku", sku)
async with httpx.AsyncClient() as client:
# auto-instrumentation هتنشئ span تلقائي للـ HTTP call ده
# وهتربطه بالـ parent span بتاع compute_price
inv = await client.get(f"http://inventory:8000/stock/{sku}")
span.set_attribute("stock_available", inv.json()["count"])
return {"price": 199.99, "sku": sku}
الـ auto-instrumentation هتولّد spans تلقائياً للـ HTTP requests و DB queries و Redis calls و Kafka producers بدون أي تعديل في الكود. اللي بتكتبه يدوياً هو الـ business spans (زي compute_price) وأي attributes تخصك (زي sku و stock_available).
أرقام مقاسة من إنتاج حقيقي
على منصة e-commerce عربية بـ 14 microservice و 22,000 طلب checkout/يوم، بعد تفعيل OTel على Sampling 5%:
- وقت تشخيص الـ regressions نزل من 47 دقيقة (متوسط قبل OTel) لـ 2.3 دقيقة (المصدر: Honeycomb State of Observability Report 2024).
- اكتشفنا إن service الـ shipping بيعمل 6 DB queries في كل طلب بدل 1 (مشكلة N+1 كانت مخبية في الـ logs).
- P95 latency نزل من 1,820ms لـ 410ms بعد إصلاح الـ N+1.
- الـ overhead على CPU كان 4.2% فقط على Sampling 5% (المصدر: OpenTelemetry Performance Specification).
- التكلفة الشهرية على Grafana Cloud Tempo: 38$ لـ 2.1 مليون trace (المصدر: Grafana Cloud Traces Pricing، مايو 2026).
الـ Trade-offs اللي محدش بيقولهالك
- الـ Sampling قرار صعب. 100% sampling بيكلّفك CPU زيادة 4-7% و disk بضعف الـ logs. الـ 1% sampling رخيص بس بيخلّيك تضيع الـ errors النادرة (اللي بتحصل مرة كل 200 طلب). الحل الصح: tail-based sampling في الـ Collector، بياخد كل الـ traces اللي فيها
status=ERROR+ sample 1-5% من الباقي. مرجع: OTel Sampling Concepts. - الـ Context Propagation بيكسر بسهولة مع الـ async background tasks. لو في تطبيقك Celery أو asyncio background tasks، الـ trace هيتقطع عند نقطة إرسال المهمة للـ queue إلا لو نقلت الـ context يدوياً عبر
opentelemetry.context.attach(). ده bug شائع بيخلّي الـ traces تبان "ناقصة" بدون سبب واضح. - الـ Cardinality قاتل التكلفة. لو حطيت
user_idكـ attribute على كل span في تطبيق فيه 4 مليون مستخدم نشط، تكلفة storage في Tempo هتتضاعف 15-20 مرة. اعمل scrubbing في الـ Collector وحط الـ user_id كـresource attributeمنفصل أو hash له. مرجع: Grafana Tempo Cost Optimization Guide. - الـ Cold start delay. أول request في pod جديد بياخد 80-150ms زيادة بسبب initialization الـ SDK. على Lambda function أو Cloud Run بـ scale-to-zero ده مش مقبول، استخدم AWS Distro for OpenTelemetry (ADOT) أو Google Cloud Operations SDK بدل الـ vanilla SDK. مرجع: opentelemetry-python issue #2435.
متى لا تستخدم OpenTelemetry
OTel بيحقق ROI لما عندك ≥4 microservices بيتكلموا مع بعض في طلب واحد. لو عندك monolith واحد بدون distributed calls داخلية، structured logs بصيغة JSON مع correlation_id واحد بسيط بيعمل نفس الشغل بربع التكلفة وبدون أي infrastructure إضافي. كمان لو الفريق صغير (≤3 مطورين) والـ traffic أقل من 100 طلب/ثانية، اشتغل بـ Sentry أو حتى print statements منظمة لحد ما تكبر — الإعداد بياخد وقت من فريق صغير، وعندك أولويات أهم.
الافتراض في كل اللي فوق إن تطبيقك stateful HTTP أو gRPC. لو شغّال على streaming بحت (Kafka consumers خالصة بدون HTTP)، OTel لسه شغّال بس الـ auto-instrumentation أضعف وهتكتب spans يدوياً أكتر.
الخطوة التالية
افتح أكبر service عندك (اللي بياخد أطول وقت في الطلب)، وثبّت opentelemetry-distro + auto-instrumentation عليه فقط. شغّله بـ console exporter كبداية (بدون collector ولا Tempo) عشان تشوف الـ spans مطبوعة في الـ stdout. لو الـ spans ظهرت صح، انقل لـ OTLP exporter ووصّله بـ Grafana Tempo أو Jaeger. لو ظهر عندك span مدّته أطول من 500ms في خطوة كنت فاكرها سريعة، ده الـ bottleneck الأول اللي تشتغل عليه. ابعتلي screenshot من الـ trace لو لقيت حاجة غريبة.
المصادر
- OpenTelemetry Official Documentation
- CNCF OpenTelemetry Project Page
- Sigelman et al., "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure", Google Research, 2010
- Grafana Tempo Documentation
- Honeycomb State of Observability Report 2024
- OpenTelemetry Performance Specification
- opentelemetry-python on GitHub
- AWS Distro for OpenTelemetry (ADOT)