لو عندك 12 خدمة شغّالة في الإنتاج، وكل ما طلب يفشل بتقعد ساعتين تقفز بين 5 dashboards علشان توصل لمين بالظبط اتسبّب في الخطأ، إنت محتاج تحوّل الـ logs المتفرّقة لـ trace واحد بيمشي عبر كل الخدمات. OpenTelemetry بيعمل ده في 30 دقيقة، وبيخلّيك تنقل بين Datadog و Jaeger و Grafana Tempo بدون ما تعدّل سطر واحد من كود التطبيق.
OpenTelemetry: المعيار اللي بيوحّد Traces و Metrics و Logs في SDK واحد
المشكلة باختصار
تخيّل dashboards عندك كده: AWS CloudWatch لـ logs، Prometheus لـ metrics، Jaeger لـ traces. كل خدمة جديدة بتيجي مع SDK مختلف، وكل tool بياخد بيانات بصيغة مختلفة. لمّا الفاتورة الشهرية لـ Datadog تتجاوز 4,800 دولار وتقرّر تنقل لحل أرخص، تكتشف إن نص الـ instrumentation الـ vendor-specific لازم تعيد كتابته من الأول.
الـ vendor lock-in هنا مش بس فلوس. هو لحظة بطء في كل قرار تقني، وفي وقت الأزمات بيخلّيك مكبّل.
مثال بسيط للمبتدئ: زي طلب الديليفري
تخيّل إنك طلبت عشاء من تطبيق ديليفري. الطلب بيمر بـ 5 محطّات: التطبيق الموبايل ← خدمة الدفع ← بنك المعاملات ← المطعم ← السائق. لو وصلك العشاء بارد، إنت عايز تعرف فين بالظبط حصل التأخير. هل البنك أخد 8 ثواني يأكّد الدفع؟ ولا المطعم استلم الطلب متأخر؟ ولا السائق لف 20 دقيقة في الزحمة؟
بدون نظام يربط الخطوات الخمس ببعض، إنت بتفتح 5 سجلات منفصلة وتحاول تطابق الأوقات يدوياً. مع نظام Distributed Tracing، كل طلب بياخد رقم تعريفي فريد، وكل محطة بتسجّل وقت دخول وخروج الطلب بنفس الرقم. تفتح dashboard واحد، وتشوف خط زمني كامل بيقولك بالظبط الخدمة اللي ضيّعت 3.2 ثانية.
التعريف العلمي الدقيق
OpenTelemetry (اختصارها OTel) هي مواصفة CNCF graduated من 2024 بتوحّد ثلاث أنواع بيانات تحت SDK واحد: traces, metrics, logs. المواصفة مبنية على ورقة Dapper من Google 2010 اللي عرّفت أساسيات الـ tracing الموزّع، وعلى W3C Trace Context spec اللي بيحدّد ازاي الـ trace_id بينتقل بين الخدمات في HTTP headers تحت اسم traceparent.
الـ SDK بيكتب البيانات بصيغة OTLP (OpenTelemetry Protocol)، ويبعتها لـ Collector عبر gRPC أو HTTP. الـ Collector بيعمل buffering و sampling ثم يوزّع البيانات على أي backend: Datadog، Jaeger، Honeycomb، Grafana Tempo، Splunk، Elastic. لمّا تقرّر تنقل من Datadog لـ Tempo، بتغيّر سطر واحد في إعداد الـ Collector فقط، وكود التطبيق ميتلمسش.
الحل في 60 سطر: Instrumentation كامل لخدمة Node.js
- ركّب SDK والـ instrumentation packages.
- ابدأ بـ auto-instrumentation للمكتبات الشائعة (Express، Fastify، PostgreSQL، Redis، AWS SDK).
- أضف custom spans للـ business logic المهم اللي مش مغطى auto.
- شغّل OTel Collector محلياً ووجّه البيانات لـ backend واحد على الأقل.
npm install @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-httpوفي ملف tracing.js:
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'orders-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.4.2',
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false },
})],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());سطر واحد في الـ Dockerfile قبل تشغيل التطبيق:
node --require ./tracing.js server.jsكده، أي طلب HTTP، أي query على PostgreSQL، أي استدعاء Redis، بيتسجّل تلقائياً مع timing دقيق وكل metadata الطلب. والأهم، الـ trace_id بيتحط في HTTP headers لكل استدعاء بين الخدمات، فالـ trace بيمتد عبر النظام كله.
أرقام حقيقية من Production
على cluster فيه 12 microservice و 4,200 طلب/ثانية، بعد 3 أسابيع من تشغيل OpenTelemetry بـ sampling rate 10%:
- زمن تشخيص الـ incidents: من 38 دقيقة (P50) لـ 4 دقايق.
- عدد الـ silent failures المكتشفة: 17 خطأ كان مخفي في timeouts غير مسجّلة.
- Overhead على CPU: 1.8% فقط (مقاس بـ pprof على Go services و clinic.js على Node).
- تكلفة الفاتورة الشهرية بعد الانتقال من Datadog لـ Grafana Tempo self-hosted على نفس الـ cluster: من 4,820 دولار لـ 340 دولار.
Trade-offs لازم تعرفها قبل ما تبدأ
الكسب: vendor independence حقيقي. المكتبات شغّالة في 11 لغة (Java, Python, Go, Node, .NET, Rust, Ruby, PHP, Swift, Erlang, JavaScript browser)، والـ Collector بيوصل لـ 80+ backend مختلف.
الخسارة:
- Auto-instrumentation بياخد 3-7% من الـ throughput لو شغّلتها بدون sampling. لازم تضبط
OTEL_TRACES_SAMPLER=parentbased_traceidratioبنسبة 0.1 على الأقل في الإنتاج. - الـ Collector نفسه بقى single point of failure. لازم تشغّله بـ 2 instances على الأقل بـ load balancer قدّامهم، أو تستخدم Sidecar pattern.
- الـ Storage تكلفته بتزيد بسرعة. trace فيه 80 span ممكن يوصل 18KB. على 100 مليون trace شهرياً، إنت بتاكل 1.8TB قبل الـ compression.
- الـ Semantic Conventions لسه بتتغيّر. توقّع breaking changes في الـ instrumentation libraries حتى تستقر في v2 (المتوقع نهاية 2026).
متى لا تستخدم OpenTelemetry
لو عندك خدمة واحدة monolith بدون أي microservices، الـ overhead مش مبرّر. structured logging بـ JSON عادي مع جدول pgaudit في PostgreSQL كافي تماماً.
لو فريقك أقل من 4 مطوّرين، الوقت اللي هتقضيه في إعداد الـ Collector والـ backend والـ retention policies أكبر بكتير من اللي هتكسبه. ابدأ بـ Sentry أو Honeycomb free tier؛ هيوفّروا 80% من القيمة بـ 5% من الجهد.
لو محتاج حماية بيانات صارمة (HIPAA، PCI-DSS، GDPR على بيانات حساسة)، الـ traces بتحمل في metadata بيانات الطلب كاملة. لازم تضبط span attribute filtering في الـ Collector قبل ما تبعت لأي SaaS، وده بيضيف تعقيد ومخاطر تسريب لو حد نسي rule.
الخطوة التالية
افتح أكتر خدمة عندها timeouts متكرّرة، حط فيها ملف tracing.js في 5 دقايق، وشغّل OTel Collector محلياً بـ docker-compose مع Jaeger UI كـ backend مؤقت. لو لقيت trace فيه أكتر من 6 spans على عملية إنت كنت فاكرها سطرين كود، يبقى رحلة الـ refactor بتبدأ من هنا. ابعت لي الـ trace الأكبر اللي لقيته، وهنفكّكه سوا.