Distributed Tracing بـ OpenTelemetry: خريطة الطلب من أوله لآخره
لو عندك 4 أو 5 microservices، وطلب واحد من الـ frontend بياخد 2800ms بدل 200ms، ومفيش طريقة تعرف بالظبط أنهي service هو السبب — المقال ده هيوفّرلك أسبوع كامل من التخمين. Distributed Tracing بـ OpenTelemetry بيديك خريطة دقيقة للطلب من لحظة دخوله لحد رجوع الرد، ومعاه زمن كل خطوة.
المشكلة باختصار
في monolith، لو طلب بطيء، بتفتح APM واحد وبتشوف أنهي function بتستهلك الوقت. في microservices، الطلب الواحد بيعدّي على auth-service ثم orders-service ثم payment-service ثم notifications-service. logs كل service موجودة في مكان مختلف، ومفيش حاجة تربطهم. بتقعد ساعتين بتدوّر في kibana و request-id يدوي علشان تعرف إن البطء في استعلام واحد في payment-service بياخد 1600ms.
Distributed Tracing بيحل المشكلة دي جذريًا: كل طلب بياخد trace_id واحد بيتنقل مع الهيدر بين كل الـ services، وكل عملية جوّا service بتتسجل كـ span تحت نفس الـ trace. النتيجة: شجرة كاملة بتوريك مين استنى مين.
المفهوم بمثال بسيط قبل أي كود
تخيّل شركة توصيل طلبات. العميل طلب أوردر في 9:00 صباحًا. الأوردر عدّى على 4 موظفين: استقبال، محاسبة، مخزن، ثم سائق. العميل استلم الأوردر 9:45. أنت كمدير عايز تعرف مين فيهم أخد وقت. لو كل موظف كتب في كراسته وقت استلامه ووقت تسليمه، تقدر تطلع جدول زي ده:
- الاستقبال: 9:00 → 9:05 (5 دقائق)
- المحاسبة: 9:05 → 9:08 (3 دقائق)
- المخزن: 9:08 → 9:35 (27 دقيقة) ← هنا المشكلة
- السائق: 9:35 → 9:45 (10 دقائق)
دلوقني عرفت إن المخزن هو المشكلة، من غير ما تقف على كل موظف وتسأله. ده بالظبط اللي Distributed Tracing بيعمله: كل "موظف" هو service، كل "كراسة" هي span، ورقم الأوردر المشترك هو trace_id.
المصطلحات العلمية بدقة
بعد ما فهمت المثال، دي التعريفات الرسمية من وثائق OpenTelemetry:
- Trace: تمثيل لرحلة طلب واحد عبر النظام بالكامل. كل trace ليه
trace_idفريد بـ 128 bit. - Span: عملية واحدة لها اسم، وقت بداية، وقت نهاية، وattributes. الـ span ممكن يكون له parent span ومنها بنبني الشجرة.
- Context Propagation: آلية نقل الـ trace_id و span_id بين الـ services. المعيار الرسمي هو W3C Trace Context، وبيتبعث في هيدر اسمه
traceparent. - Instrumentation: الكود اللي بيولّد الـ spans. ممكن يكون auto-instrumentation (بيشتغل من غير ما تعدّل كودك) أو manual (بتكتب spans مخصوصة).
- Exporter: المكوّن اللي بيبعت الـ spans لمكان تخزين (Jaeger, Tempo, Datadog, Honeycomb...).
إعداد Node.js في 3 دقائق (auto-instrumentation)
أسرع طريقة تبدأ بيها هي الـ auto-instrumentation. في Node.js، الحزمة @opentelemetry/auto-instrumentations-node بتـ patch تلقائيًا مكتبات زي Express و http و pg و ioredis، وبتولّد spans من غير ما تعدّل سطر في الكود الأساسي.
npm install \
@opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/semantic-conventionsاعمل ملف tracing.js وحمّله قبل أي حاجة في التطبيق:
// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { resourceFromAttributes } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[SemanticResourceAttributes.SERVICE_NAME]: 'orders-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.2.0',
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production'
}),
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4318/v1/traces'
}),
instrumentations: [getNodeAutoInstrumentations()]
});
sdk.start();وفي الـ start script: node --require ./tracing.js server.js. خلاص. كل طلب HTTP، كل استعلام Postgres، كل call على Redis، كلهم هيتسجّلوا كـ spans تلقائيًا.
Python بنفس المنطق
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
OTEL_SERVICE_NAME=payments-service \
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 \
OTEL_TRACES_EXPORTER=otlp \
opentelemetry-instrument python app.pyالأمر opentelemetry-bootstrap بيكتشف المكتبات المثبّتة (Django, Flask, FastAPI, psycopg2...) ويثبّت الـ instrumentation المناسبة. بعدها opentelemetry-instrument بيغلّف تطبيقك ويبدأ يبعت spans.
الـ Collector: محطة وسطى مش رفاهية
ممكن الـ SDK في التطبيق يبعت مباشرة لـ Jaeger أو Tempo، لكن ده مش مُوصى به في production. الأفضل يكون فيه OpenTelemetry Collector في النص — gateway بتستقبل الـ spans، تعمل buffering و batching و sampling، وبعدين تبعت لمكان التخزين.
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1024
tail_sampling:
decision_wait: 10s
policies:
- name: errors-policy
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow-policy
type: latency
latency: { threshold_ms: 500 }
- name: probabilistic-policy
type: probabilistic
probabilistic: { sampling_percentage: 10 }
exporters:
otlp/tempo:
endpoint: tempo:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, tail_sampling]
exporters: [otlp/tempo]الـ tail sampling هنا مهم: بيحتفظ بـ 100% من الـ traces اللي فيها error أو latency أعلى من 500ms، و 10% عشوائي من الباقي. ده بيقلّل التكلفة بنسبة تقارب 85% مع الحفاظ على الـ traces المهمة.
الأرقام اللي هتهمك فعلًا
- Overhead على التطبيق: SDK الرسمي بيضيف 1-3% CPU و ~50MB RAM في متوسط حالات production. مقبول.
- حجم البيانات: trace متوسط في نظام فيه 8 services بياخد 3-8 KB. مع sampling 10%، تطبيق بـ 2000 req/s بينتج حوالي 48 GB/يوم من الـ traces.
- تكلفة Tempo على S3: بتتكلف في حدود 15-30 دولار/شهر لحجم 500GB بـ retention 14 يوم. أرخص بكتير من Datadog APM.
- زمن استكشاف مشكلة: قبل tracing، تشخيص latency spike بياخد من 30 دقيقة لساعتين من الـ log diving. بعد tracing، بتوصل للـ service المسؤول في أقل من 3 دقائق.
Trade-offs لازم تكون صريح معاها
بتكسب: رؤية كاملة للـ request flow، سرعة تشخيص عالية، ربط tightl بين traces و logs و metrics. بتخسر:
- تكلفة تخزين: الـ spans بتتكدّس بسرعة. بدون sampling ذكي، هتدفع أكثر من اللازم.
- تعقيد في الإعداد: Collector، backend، Grafana، كلهم مكوّنات جديدة محتاجة صيانة.
- Overhead على طلبات معينة: endpoints بتعمل آلاف الـ DB calls في الطلب الواحد ممكن تعاني. لازم تستبعدها أو تقلّل sampling عليها.
- خصوصية: attributes ممكن تحتوي على بيانات حسّاسة (emails, tokens). لازم تعدّل الـ Collector يعمل redaction.
متى لا تستخدم Distributed Tracing
لا تبدأ بـ tracing لو:
- عندك service واحد (monolith). APM تقليدي أبسط وأرخص.
- حجم الطلبات أقل من 50 req/minute. مش هتاخد قيمة توازي الإعداد.
- فريقك لسه مش عامل centralized logging. Logs أولًا، تراسنج ثانيًا.
- الـ services بتاعتك بتتكلم بـ protocols غريبة مش مدعومة (بعض الـ legacy RPC).
الافتراضات اللي بنيت عليها الأرقام
الأرقام في المقال مبنية على تطبيق Node.js/Python متوسط الحجم، يستقبل بين 500 و 5000 طلب/ثانية، ويشتغل على Kubernetes بنظام 3-8 microservices. لو عندك أكتر من 20K طلب/ثانية أو حجم بيانات أكبر، احتاجت ضبط أدق للـ sampling rates والـ collector replicas.
الخطوة التالية
افتح أكبر service عندك، ثبّت SDK أو auto-instrumentation بتاع اللغة بتاعتك، وابعت traces لـ Jaeger محلي بـ Docker لمدة يوم واحد. docker run -d -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest هيديك UI كامل على localhost:16686. بعد ما تشوف أول trace شجري، هتفهم لوحدك أنهي service هو الأبطأ. لو الـ SDK مش بيبعت spans، 90% من الوقت المشكلة في endpoint أو authentication على الـ Collector — ابعتلي رسالة الخطأ.
المصادر
- OpenTelemetry Docs — Observability Primer و Traces concepts (opentelemetry.io/docs/concepts/signals/traces/).
- W3C Trace Context Recommendation — المعيار الرسمي لـ
traceparentheader (w3.org/TR/trace-context/). - OpenTelemetry Python Auto-Instrumentation Example (opentelemetry.io/docs/zero-code/python/example/).
- OpenTelemetry Kubernetes Operator — Injecting Auto-instrumentation (opentelemetry.io/docs/platforms/kubernetes/operator/automatic/).
- Grafana Tempo Docs — high-scale distributed tracing backend.
- Uptrace — What is Distributed Tracing? How It Works with OpenTelemetry.
- Red Hat Developer (فبراير 2026) — How to use auto-instrumentation with OpenTelemetry.
- Dash0 Knowledge Base — How OpenTelemetry Distributed Tracing Works (with Examples).