---
title: دليل تعليمي OpenTelemetry Collector + Node.js
description: جهز تطبيق Node.js باستخدام OpenTelemetry، وشغل الـ Collector و Jaeger v2 في Docker، وشوف الـ distributed traces. كل النسخ متثبتة ومتحقق منها، 2026.
url: https://nerdleveltech.com/ar/opentelemetry-collector-nodejs-tracing-tutorial
language: ar
date: '2026-06-13'
category: cloud-devops
tags:
  - opentelemetry
  - opentelemetry collector
  - node.js
  - distributed tracing
  - jaeger
  - observability
  - otlp
  - devops
markdown_url: https://nerdleveltech.com/ar/opentelemetry-collector-nodejs-tracing-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/opentelemetry-collector-nodejs-tracing-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# دليل تعليمي OpenTelemetry Collector + Node.js

## ملخص

ستقوم بتهيئة تطبيق Node.js + Express باستخدام OpenTelemetry، وإرسال التتبعات (traces) عبر OTLP إلى **OpenTelemetry Collector**، ثم توزيعها على **Jaeger v2**، وقراءة التتبعات الموزعة الناتجة في واجهة مستخدم Jaeger. تعمل المجموعة بالكامل محليًا باستخدام أمر واحد: `Docker compose up`. تم تثبيت والتحقق من كل إصدارات الحزم وعلامات الصور (image tags) أدناه في 2026-06-13، وتم تنفيذ كود التهيئة للتأكد من الـ spans التي ينتجها. الميزانية الزمنية حوالي 30 دقيقة.

## ما ستتعلمه

- كيفية إرسال تتبعات Node.js OpenTelemetry إلى الـ Collector ثم إلى Jaeger، ولماذا يتوسط الـ Collector بينهما
- كيفية التهيئة التلقائية (auto-instrument) لتطبيق Express بحيث تظهر الـ spans الخاصة بـ HTTP والمسارات (routes) بدون كتابة كود داخل كل معالج (handler)
- كيفية كتابة إعدادات Collector جاهزة للإنتاج باستخدام مستقبل OTLP، ومعالجات `memory_limiter` + `batch`، ومصدرات `otlp` + `debug`
- كيفية تشغيل Jaeger v2 في Docker وعرض التتبعات الموزعة على `http://localhost:16686`
- كيفية إضافة span مخصص وسمات موارد (resource attributes) باستخدام SDK 2.x الحالي (`resourceFromAttributes`)
- كيفية تصحيح الخطأ الكلاسيكي "التتبعات الخاصة بي لا تظهر أبدًا في Jaeger"

## كيف تترابط الأجزاء معًا

إجابة مباشرة أولاً: يقوم تطبيقك بتصدير الـ spans عبر **OTLP** (بروتوكول OpenTelemetry السلكي) إلى Collector؛ يقوم الـ Collector بتجميعها وإرسالها إلى Jaeger v2، الذي يقوم بتخزينها وعرضها. الـ Collector اختياري في مرحلة التجربة البسيطة ولكنه قياسي في بيئة الإنتاج لأنه يفصل تطبيقك عن الواجهة الخلفية (backend) — يمكنك إضافة أخذ العينات (sampling)، والتجميع (batching)، وإعادة المحاولة، ووجهات إضافية دون إعادة نشر التطبيق.

إليك تدفق البيانات الذي ستنشئه:

```mermaid
flowchart LR
  A["Node.js app<br/>(OTLP/HTTP :4318)"] -->|OTLP| C["OTel Collector<br/>memory_limiter, batch"]
  C -->|OTLP gRPC :4317| J["Jaeger v2<br/>storage + query"]
  C -->|debug exporter| L["Collector logs<br/>(stdout)"]
  J --> U["Jaeger UI<br/>:16686"]
```

ينتقل OTLP عبر وسيلتي نقل: gRPC على المنفذ **4317** و HTTP على المنفذ **4318**.[^otlp] يستخدم التطبيق في هذا الدليل OTLP/HTTP للوصول إلى الـ Collector؛ ويستخدم الـ Collector بروتوكول OTLP/gRPC للوصول إلى Jaeger. كلاهما من الدرجة الأولى — الاختيار يتعلق بالتبعيات وجدران الحماية، وليس بالصحة التقنية.

لماذا نهتم بوجود Collector أصلاً بينما يمكن لـ Jaeger v2 استقبال OTLP مباشرة؟ لأن الـ Collector هو المكان الذي تعيش فيه الاهتمامات التشغيلية. تشغيل واحد كـ **وكيل (agent)** بجانب كل تطبيق (أو كـ **بوابة (gateway)** مشتركة لمجموعة من التطبيقات) يعني أنه يمكنك تغيير معدلات أخذ العينات، أو إضافة واجهة خلفية ثانية، أو تنظيف السمات الحساسة، أو استيعاب انقطاع الواجهة الخلفية مع إعادة المحاولة والانتظار في طوابير — كل ذلك دون إعادة نشر تطبيق واحد. يظل التطبيق بسيطًا: يرسل OTLP وينسى. هذا الفصل هو السبب الكامل لوجود الـ Collector، وهو السبب في أن إعدادات الإنتاج تضعه في المسار حتى لو كان البروتوكول يسمح لك بتخطيه.[^collector-config]

## المتطلبات الأساسية

- **Node.js 24.16.0** (خط LTS النشط الحالي) أو أحدث. علامة المحمل `node --import` التي يعتمد عليها هذا الدليل مستقرة منذ Node 20.6.[^node]
- **Docker** مع إضافة Compose (أمر `Docker compose`، وليس `Docker-compose` القديم).
- طرفية (terminal) ومتصفح. لا يفترض وجود معرفة مسبقة بـ Jaeger أو الـ Collector.

يتم تثبيت كل شيء آخر أدناه. الإصدارات المثبتة المستخدمة في الدليل:

| المكون                                      | الإصدار | المصدر                                 |
| ------------------------------------------- | ------- | -------------------------------------- |
| `@opentelemetry/sdk-node`                   | 0.219.0 | npm `latest`[^otel-js]                 |
| `@opentelemetry/auto-instrumentations-node` | 0.77.0  | npm `latest`[^otel-js]                 |
| `@opentelemetry/exporter-trace-otlp-proto`  | 0.219.0 | npm `latest`[^otel-js]                 |
| `@opentelemetry/resources`                  | 2.8.0   | npm `latest`[^otel-js]                 |
| `@opentelemetry/semantic-conventions`       | 1.41.1  | npm `latest`[^otel-js]                 |
| `@opentelemetry/API`                        | 1.9.1   | npm `latest`[^otel-js]                 |
| `express`                                   | 5.2.1   | npm `latest`                           |
| OTel Collector (contrib)                    | 0.153.0 | collector-releases[^collector-release] |
| Jaeger                                      | 2.19.0  | jaegertracing.io/download[^jaeger-v2]  |

هناك ملحوظة تستحق المعرفة مسبقًا: تشحن OpenTelemetry JS حزمها **المستقرة** على خطوط الإصدار 1.x/2.x (مثل `API` 1.9.1، و `resources` 2.8.0) وحزمها التي لا تزال **تجريبية** على خط 0.x مشترك (`sdk-node` 0.219.0، والمصدرات، والتهيئة التلقائية).[^otel-js] عدم وجود إصدار مثل "sdk-node 2.x" ليس خطأ — 0.219.0 هو الإصدار التجريبي الحالي، وهذا أمر متوقع.

## الخطوة 1 — إنشاء هيكل المشروع وتثبيت OpenTelemetry

أنشئ دليلًا للمشروع وقم بتثبيت SDK، وحزمة التهيئة التلقائية، ومصدر OTLP، و Express:

```bash
mkdir orders-API && cd orders-API
npm init -y
npm pkg set type=module
npm install \
  @opentelemetry/API@1.9.1 \
  @opentelemetry/sdk-node@0.219.0 \
  @opentelemetry/auto-instrumentations-node@0.77.0 \
  @opentelemetry/exporter-trace-otlp-proto@0.219.0 \
  @opentelemetry/resources@2.8.0 \
  @opentelemetry/semantic-conventions@1.41.1 \
  express@5.2.1
```

أمر `npm pkg set type=module` يجعل المشروع يستخدم وحدات ES، وهو ما يفترضه ملف التشغيل أدناه. بعد اكتمال التثبيت، يجب أن يسرد `npm ls --depth=0` كل حزمة بالإصدار الذي طلبته دون أي تحذيرات `ERESOLVE`.

## الخطوة 2 — كتابة ملف تهيئة التشغيل

يجب تهيئة OpenTelemetry **قبل** استيراد (`import`) أي مكتبة مهيأة (مثل `http`، `express`)، لذا فهي توضع في ملف خاص بها يتم تحميله أولاً. أنشئ ملف `instrumentation.js`:

```JavaScript
// instrumentation.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { resourceFromAttributes } from '@opentelemetry/resources';
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
  ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
} from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: 'orders-API',
    [ATTR_SERVICE_VERSION]: '1.0.0',
    [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: 'development',
  }),
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4318/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

process.on('SIGTERM', () => {
  sdk.shutdown().finally(() => process.exit(0));
});
```

هناك ثلاثة تفاصيل مهمة هنا، وكل منها مكان تخطئ فيه الدروس القديمة:

- **`resourceFromAttributes` هو الـ API الحالي.** تم حذف منشئ `new Resource()` في خط SDK 2.x الخاص بـ `@opentelemetry/resources`؛ استدعاؤه الآن سيؤدي لخطأ. الـ `service.name` الذي تحدده هنا هو ما يميز خدمتك في واجهة مستخدم Jaeger.[^otel-js]
- **مفاتيح الاصطلاحات الدلالية (semantic-convention) هي ثوابت مستوردة، وليست سلاسل نصية خام.** `ATTR_SERVICE_NAME` تؤول إلى `'service.name'`، و `ATTR_DEPLOYMENT_ENVIRONMENT_NAME` تؤول إلى `'deployment.environment.name'`.[^semconv] استيرادها يبقيك متوافقًا مع المواصفات بدلاً من كتابة أسماء السمات يدويًا.
- **`sdk.start()` متزامن؛ بينما `sdk.shutdown()` يرجع Promise.** معالج الـ spans الافتراضي يقوم بتجميعها (batching)، لذا فإن أي عملية تخرج فجأة قد تفقد ما لم يتم إرساله (flushed). ربط `shutdown()` بـ `SIGTERM` يضمن إرسال الدفعة المتبقية عند التوقف النظيف.

## الخطوة 3 — كتابة تطبيق Express

الآن التطبيق نفسه. أنشئ ملف `server.js` بمسار واحد يقوم ببعض العمل الداخلي، بالإضافة إلى span يدوي حتى تتمكن من رؤية الأدوات المخصصة (custom instrumentation) بجانب النوع التلقائي:

```JavaScript
// server.js
import express from 'express';
import { trace } from '@opentelemetry/API';

const app = express();
const tracer = trace.getTracer('orders-API');

app.get('/orders/:id', async (req, res) => {
  const span = tracer.startSpan('load-order-from-db');
  await new Promise((resolve) => setTimeout(resolve, 25)); // pretend DB call
  span.setAttribute('order.id', req.params.id);
  span.end();
  res.json({ id: req.params.id, status: 'shipped' });
});

app.listen(3000, () => console.log('orders-API listening on :3000'));
```

لم تضطر لاستيراد أي شيء خاص بـ OpenTelemetry للحصول على الـ spans الخاصة بـ HTTP والمسارات (routes) — لأن `getNodeAutoInstrumentations()` تقوم بتعديل طبقات `http` و Express تلقائياً من أجلك. القياس اليدوي الوحيد هو span الـ `load-order-from-db`، والذي يتداخل داخل span الطلب التلقائي لأنه تم إنشاؤه بينما كان ذلك الطلب في السياق النشط (active context).

## الخطوة 4 — تكوين الـ Collector و Jaeger

أنشئ ملف `otel-collector.yaml`. هذا مسار تتبع (trace pipeline) مصمم للإنتاج: يستقبل OTLP، يحمي الذاكرة، يجمع البيانات في دفعات، ثم يصدرها إلى Jaeger وإلى سجلات الـ Collector الخاصة.

```yaml
# otel-collector.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128
  batch:
    timeout: 5s
    send_batch_size: 1024

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/jaeger, debug]
```

لماذا يوجد كل مكون هنا:

- **`memory_limiter` مدرج أولاً.** يقوم بفحص استخدام الـ heap كل `check_interval` ويرفض البيانات عندما تقترب العملية من `limit_mib`، بحيث تؤدي زيادة حركة المرور المفاجئة إلى التخلص من الحمل الزائد بدلاً من قتل الـ Collector بسبب نفاد الذاكرة (OOM). التوجيه الرسمي هو وضعه قبل أي معالج آخر لحماية المسار بالكامل.[^memlimiter]
- **`batch` يجمع الـ spans قبل التصدير**، ويرسلها بعد `timeout` أو بمجرد تراكم `send_batch_size` من الـ spans، أيهما يحدث أولاً. التجميع يقلل عدد الطلبات الصادرة بشكل كبير.[^collector-config]
- **`otlp/jaeger` يصدر عبر OTLP gRPC إلى `jaeger:4317`.** خيار `tls.insecure: true` يعطل TLS للشبكة المحلية — وهو مناسب داخل شبكة Compose خاصة، وليس للإنترنت العام.[^collector-config] صيغة `type/name` (مثل `otlp/jaeger`) تسمح لك بتعريف أكثر من مصدر من نفس النوع لاحقاً.
- **`debug` يطبع الـ spans إلى stdout الخاص بالـ Collector** بمستوى `verbosity: detailed`، وهو أمر لا يقدر بثمن أثناء عملية الربط. لاحظ الاسم: كان هذا المصدر يسمى `logging` قبل إصدار Collector v0.86.0 وتم تغيير اسمه إلى `debug`. الدروس التعليمية التي لا تزال تستخدم `logging` ستفشل مع إصدار Collector الحالي.[^collector-config]

الآن ملف Compose الذي يشغل كلتا الخدمتين. أنشئ `Docker-compose.yaml`:

```yaml
# Docker-compose.yaml
services:
  jaeger:
    image: cr.jaegertracing.io/jaegertracing/jaeger:2.19.0
    container_name: jaeger
    ports:
      - "16686:16686"
    networks: [otel]

otel-collector:
    image: otel/opentelemetry-collector-contrib:0.153.0
    container_name: otel-collector
    command: ["--config=/etc/otelcol-contrib/config.yaml"]
    volumes:
      - ./otel-collector.yaml:/etc/otelcol-contrib/config.yaml
    ports:
      - "4317:4317"
      - "4318:4318"
    depends_on: [jaeger]
    networks: [otel]

networks:
  otel:
```

هناك بعض الأشياء التي يطبقها هذا الملف بشكل صحيح وتفتقر إليها الأدلة القديمة:

- **استخدام Jaeger v2، وليس v1.** وصل Jaeger v1 إلى نهاية عمره الافتراضي في 2025-12-31؛ صورة `jaeger` (الإصدار الرئيسي v2) هي الخط المدعوم وهي نفسها مبنية على إطار عمل OpenTelemetry Collector.[^jaeger-v2] يقبل Jaeger v2 بروتوكول OTLP تلقائياً — لم تعد هناك حاجة لضبط علامة `COLLECTOR_OTLP_ENABLED=true`، لأن OTLP مفعل افتراضياً.[^jaeger-v2]
- **يقوم الـ Collector بالتصدير إلى Jaeger باستخدام مصدر `otlp`، وليس مصدر `jaeger`.** تمت إزالة مصدر `jaeger` الأصلي من توزيعات الـ Collector بعد الإصدار v0.85.0؛ توجيهات الهجرة الخاصة بالمشروع هي إرسال OTLP، والذي يستقبله Jaeger الآن مباشرة.[^jaeger-exporter]
- **الـ Collector فقط هو من ينشر المنافذ 4317/4318 لجهازك المضيف.** يعرض Jaeger فقط منفذ واجهة المستخدم 16686؛ يتصل الـ Collector بـ Jaeger عبر شبكة `otel` الخاصة، لذا يتحدث تطبيقك مع `localhost:4318` ولا يتصل بـ Jaeger مباشرة أبداً.

## الخطوة 5 — تشغيل المكدس وإنشاء تتبع

قم بتشغيل Jaeger والـ Collector:

```bash
Docker compose up -d
Docker compose ps
```

يجب أن تظهر كلتا الحاويتين في حالة تشغيل (running). في نافذة terminal ثانية، ابدأ التطبيق مع تحميل ملف القياس **قبل** الخادم عبر `--import`:

```bash
node --import ./instrumentation.js server.js
```

المخرجات المتوقعة:

```text
orders-API listening on :3000
```

الآن أرسل طلباً حتى يكون هناك شيء لتتبعه:

```bash
curl http://localhost:3000/orders/42
```

الاستجابة المتوقعة:

```json
{"id":"42","status":"shipped"}
```

خلال ثانية أو ثانيتين، سيقوم مصدر `debug` الخاص بالـ Collector بتسجيل الـ spans المستلمة، وستصل نفس الـ spans إلى Jaeger. يمكنك مراقبة معالجة الـ Collector لها باستخدام `Docker compose logs -f otel-collector`.

إذا كنت تريد إجراء فحص سلامة (sanity check) بدون واجهة خلفية أولاً، فاستبدل مصدر OTLP مؤقتاً بمصدر console. تشغيل نفس التطبيق مع `ConsoleSpanExporter` يطبع كل span مكتمل؛ طلب `curl` واحد إلى `/orders/42` ينتج تتبعاً متصلاً واحداً تشترك الـ spans الخاصة به في `traceId`:

```text
{
  name: 'GET',
  kind: 1,                       // SERVER span (the HTTP request)
  attributes: { 'http.method': 'GET', 'http.status_code': 200 },
  resource: { 'service.name': 'orders-API' }
}
{
  name: 'request handler - /orders/:id',
  attributes: { 'http.route': '/orders/:id' }   // the Express route span
}
{
  name: 'load-order-from-db',
  kind: 0,                       // your custom span, nested in the request
  attributes: { 'order.id': '42' }
}
```

المخرجات أعلاه مختصرة — مصدر الـ console الحقيقي يطبع معرفات الـ trace/span، والطوابع الزمنية، والمزيد من الخصائص — ولكن **أسماء** الـ span، و **نوع** الـ SERVER، و `http.route` الخاص بالمسار، و `order.id` الخاص بك هي بالضبط ما يصدره القياس.

## الخطوة 6 — قراءة التتبع في Jaeger

افتح `http://localhost:16686`. في قائمة **Service** المنسدلة، اختر `orders-API` (الـ `service.name` من موردك)، واترك العملية كـ "all"، ثم انقر على **Find Traces**.

سترى تتبعاً واحداً لكل طلب. افتحه وستحصل على شلال (waterfall): الـ root span الخاص بالخادم `GET /orders/:id`، و span فرعي لمعالج مسار Express، و span الـ `load-order-from-db` الخاص بك بالأسفل، بعرض 25 مللي ثانية تقريباً — وهو التأخير الاصطناعي الذي أضفته. انقر فوق أي span لفحص علاماته (tags)، بما في ذلك `service.name` و `http.route` و `order.id`.

اقرأ الشلال بنفس الطريقة التي تقرأ بها الرسم البياني اللهبي (flame graph). عرض كل شريط يمثل الوقت الفعلي، لذا فإن span الـ `load-order-from-db` الذي يهيمن بوضوح على الطلب هو إشارتك إلى أن خطوة "قاعدة البيانات" — وليس عبء إطار العمل — هي المسؤولة عن التأخير (latency). عندما يكون الطلب بطيئاً في بيئة الإنتاج، تجيب هذه الرؤية على سؤال "أين البطء" في ثوانٍ بدلاً من إجبارك على استنتاج ذلك من سجلات متفرقة. الفجوة بين نهاية الـ span ونهاية الـ parent الخاص به هي وقت غير محسوب، والذي يشير عادةً إلى عمل متزامن لم يتم قياسه بعد.

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

## الخطوة 7 — تعزيز بيئة الإنتاج

المسار الذي بنيته مهيأ بالفعل للإنتاج، ولكن هناك ثلاثة إعدادات تستحق الضبط المتعمد قبل الإطلاق.

**قم بأخذ عينات (Sample)، ولا تخزن كل شيء.** في حالات حركة المرور الحقيقية، يكون تخزين 100% من التتبعات مكلفاً ونادراً ما يكون مفيداً. اضبط أداة أخذ العينات (sampler) الخاصة بـ SDK باستخدام متغير بيئة بحيث تحتفظ، على سبيل المثال، بـ 10% من التتبعات بناءً على البداية (head-based) — دون الحاجة لتغيير الكود:

```bash
OTEL_TRACES_SAMPLER=traceidratio \
OTEL_TRACES_SAMPLER_ARG=0.1 \
node --import ./instrumentation.js server.js
```

`traceidratio` يتخذ قرار الإبقاء/الإسقاط بشكل حتمي بناءً على معرف التتبع (trace ID)، لذا — طالما أن كل خدمة تستخدم نفس النسبة — فإما أن يتم الإبقاء على التتبع بالكامل أو إسقاطه بالكامل عبر جميع الخدمات، ولن تحصل أبداً على نصف تتبع.[^otel-js]

**وجه المصدر (exporter) باستخدام متغيرات البيئة، وليس عناوين URL مكتوبة برمجياً.** في مرحلة التشغيل التجريبي (bootstrap)، قمت بضبط عنوان URL داخلياً للتوضيح، ولكن متغير `OTEL_EXPORTER_OTLP_ENDPOINT` القياسي يسمح لفرق العمليات بإعادة توجيه التطبيق حسب البيئة دون لمس الكود. تقرأ مصادر OTLP هذا المتغير كنقطة نهاية أساسية وتضيف مسار الإشارة (`/v1/traces`):[^otel-js]

```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 \
node --import ./instrumentation.js server.js
```

**انتبه لحدود الدفعات (batch) والذاكرة.** قيم `batch` و `memory_limiter` في ملف `otel-collector.yaml` هي نقاط انطلاق وليست قواعد ثابتة. ارفع قيمة `send_batch_size` للحصول على إنتاجية أعلى، واضبط `limit_mib` على حوالي 80% من الذاكرة التي تمنحها فعلياً لحاوية Collector حتى يبدأ المحدد في العمل قبل أن يتدخل نظام التشغيل.[^memlimiter]

## التحقق

فحص المسار الناجح من البداية للنهاية:

```bash
# 1. البيئة تعمل
Docker compose ps          # jaeger + otel-collector كلاهما يعملان

# 2. التطبيق يستجيب
curl -s http://localhost:3000/orders/42
# => {"id":"42","status":"shipped"}

# 3. الـ Collector استلم الـ spans (debug exporter)
Docker compose logs otel-collector | grep -i "TracesExporter\|spans"

# 4. Jaeger يحتوي على التتبع
#    المتصفح: http://localhost:16686 -> الخدمة: orders-API -> Find Traces
```

إذا أظهرت الخطوة 3 أن Collector يسجل الـ spans المستلمة وأدرجت الخطوة 4 تتبعاً تحت `orders-API`، فإن المسار يعمل بشكل صحيح.

## الأخطاء الشائعة

**لا توجد spans في Jaeger، لكن التطبيق يعمل بشكل جيد.** السبب الأكثر شيوعاً هو انتهاء العملية قبل إرسال الدفعة (batch flush). يقوم معالج `BatchSpanProcessor` الافتراضي بالاحتفاظ بالـ spans في الذاكرة وتصديرها بناءً على مؤقت؛ لذا فإن السكربتات قصيرة العمر أو الضغط على `Ctrl+C` بدون إغلاق نظيف يؤدي إلى فقدانها. تأكد من تشغيل `sdk.shutdown()` عند الخروج (كما في الخطوة 2)، أو انتقل مؤقتاً إلى console exporter لإثبات أن الـ spans يتم إنتاجها من الأساس.

**`Cannot find module` أو يبدأ التطبيق بدون أي أدوات قياس (instrumentation).** يجب تحميل ملف أدوات القياس قبل كود تطبيقك. استخدم `node --import ./instrumentation.js server.js`؛ إذا كنت تستخدم CommonJS بدلاً من ES modules، فإن العلم المكافئ هو `--require ./instrumentation.js`. تحميله *بعد* عمل `import` لـ Express يعني أن المكتبات قد تم تحميلها بالفعل دون تعديل (unpatched).

**الـ Collector لا يبدأ: `unknown type: "logging"`.** أنت تستخدم نسخة حديثة من Collector مع إعدادات قديمة. تم تغيير اسم المصدر `logging` إلى `debug` في الإصدار v0.86.0. قم بتغيير الاسم في المصدر وفي مرجع المسار (pipeline).[^collector-config]

**الـ Collector لا يبدأ: `unknown type: "jaeger"` في المصادر.** نفس مشكلة القدم في جانب التصدير: تمت إزالة مصدر `jaeger` الأصلي بعد الإصدار v0.85.0. أرسل OTLP إلى `jaeger:4317` باستخدام مصدر `otlp` بدلاً من ذلك، تماماً كما في الخطوة 4.[^jaeger-exporter]

**فشل الاتصال (Connection refused) من التطبيق إلى `localhost:4318`.** حاوية Collector لا تنشر منفذ OTLP HTTP، أو أنها توقفت بسبب إعدادات خاطئة. تحقق من `Docker compose ps` و `Docker compose logs otel-collector`؛ أي خطأ مطبعي في الإعدادات يجعل Collector يخرج فوراً بدلاً من العمل بالإعدادات الافتراضية.

**`bind: address already in use` على المنفذ 4317 أو 4318.** هناك شيء آخر على جهازك يمتلك منافذ OTLP بالفعل — غالباً Collector سابق لم يتم إغلاقه أو وكيل قياس آخر. ابحث عنه باستخدام `lsof -i :4318`، أوقفه، ثم قم بتشغيل `Docker compose up -d` مرة أخرى. تغيير منفذ المضيف المنشور (على سبيل المثال `"14318:4318"`) سيعمل أيضاً، طالما وجهت مصدر التطبيق إلى منفذ المضيف الجديد.

**كل طلب يظهر كأنه تتبعين مكررين.** هذا يعني أن SDK بدأ مرتين — عادةً لأن `instrumentation.js` تم تمريره إلى `--import` *وتم* استيراده مرة أخرى من داخل `server.js`. قم بتحميله مرة واحدة فقط، من خلال علم المحمل (loader flag)، وقم بإزالة أي `import './instrumentation.js'` من كود تطبيقك.

## الخطوات التالية وقراءات إضافية

- بمجرد تدفق التتبعات، تصبح الأسئلة الأصعب تنظيمية: الاستبقاء (retention)، والعددية (cardinality)، والتكلفة. تغطي المبادئ الواردة في دليلنا حول [تصميم منصة مراقبة حديثة](https://nerdleveltech.com/designing-a-modern-observability-platform-principles-patterns-pitfalls) المقايضات التي لا يمكن لدرس تعليمي واحد تغطيتها.
- التتبع هو إشارة واحدة؛ إذا كانت أعباء عملك تعمل على Kubernetes، فقم بدمج هذا مع [عمليات نشر Kubernetes بدون وقت توقف](https://nerdleveltech.com/Kubernetes-zero-downtime-deployments-tutorial) حتى لا يؤدي النشر أبداً إلى فقدان الـ spans في منتصف العملية.
- للاطلاع على نفس المشكلة في بيئة تشغيل الطرفية (edge-runtime)، تعرف على كيفية ربط [مراقبة Cloudflare Workers](https://nerdleveltech.com/cloudflare-workers-observability-workers-logs-sentry-tutorial) مع Workers Logs و Sentry.

المراجع المعتمدة:

[^otlp]: مواصفات OTLP — المنافذ الافتراضية 4317 (gRPC) و 4318 (HTTP): <https://opentelemetry.io/docs/specs/otlp/>

[^collector-config]: OpenTelemetry Collector — الإعدادات (المستقبلات، المعالجات، المصادر بما في ذلك مصدر `debug` والملاحظة بأنه كان `logging` قبل الإصدار v0.86.0؛ آخر تعديل 29-05-2026): <https://opentelemetry.io/docs/collector/configuration/>

[^node]: إصدارات Node.js (24 Active LTS) وعلم `--import` (مستقر منذ 20.6): <https://nodejs.org/en/about/previous-releases>

[^otel-js]: OpenTelemetry JavaScript — دليل البداية (Node.js)، و SDK، والمصدرون (exporters)، وإعدادات أخذ العينات (sampler): <https://opentelemetry.io/docs/languages/js/getting-started/nodejs/> و <https://opentelemetry.io/docs/languages/sdk-configuration/general/>

[^collector-release]: إصدارات OpenTelemetry Collector (تاجات صور Collector؛ قم بتثبيت إصدار `0.15x` الحالي): <https://GitHub.com/open-telemetry/opentelemetry-collector-releases/releases>

[^jaeger-v2]: تحميلات Jaeger وملاحظات الإصدار الثاني v2 (الأحدث 2.19.0؛ نهاية دعم الإصدار الأول v1 في 2025-12-31؛ الإصدار الثاني v2 مبني على OTel Collector مع تفعيل OTLP افتراضياً): <https://www.jaegertracing.io/download/>

[^semconv]: اتفاقيات التسمية الدلالية (semantic conventions) لـ OpenTelemetry في JS (`ATTR_SERVICE_NAME`، `ATTR_SERVICE_VERSION`، `ATTR_DEPLOYMENT_ENVIRONMENT_NAME`): <https://opentelemetry.io/docs/specs/semconv/>

[^memlimiter]: ملف README لمعالج محدد الذاكرة (Memory Limiter Processor) (`check_interval`، `limit_mib`، `spike_limit_mib`؛ ضعه أولاً في خط الأنابيب): <https://GitHub.com/open-telemetry/opentelemetry-collector/blob/main/processor/memorylimiterprocessor/README.md>

[^jaeger-exporter]: مدونة OpenTelemetry — الانتقال بعيداً عن مصدر Jaeger (Jaeger exporter) في Collector (تمت إزالة مصدر `jaeger` الأصلي؛ أرسل OTLP بدلاً منه): <https://opentelemetry.io/blog/2023/jaeger-exporter-collector-migration/>
