> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# مسار السجل Log Pipeline من التطبيق إلى البحث والتنبيه
- URL: https://arabroot.io/articles/مسار-السجل-log-pipeline-من-التطبيق-الى-البحث-والتنبيه/
- Published: 2026-10-14T12:30:00.000Z
- Updated: 2026-10-15T12:30:59.000Z
- Description: كيف ينتقل السجل من التطبيق إلى المخزن عبر الناقل Shipper، وكيف تخفي البيانات الشخصية وتوجه الأخطاء وتحمي السجلات إذا توقفت الوجهة.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية

في [المقال السابق](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-%D8%AA%D8%B3%D8%A7%D8%B9%D8%AF%D9%83-%D9%81%D8%B9%D9%84%D8%A7-%D9%85%D9%86-%D8%AD%D8%AF%D8%AB-%D8%AE%D8%B7%D8%A7-%D8%A7%D9%84%D9%89-%D8%B3%D8%A8%D8%A8-%D8%A7%D9%84%D9%85%D8%B4%D9%83%D9%84%D8%A9/) أعدنا كتابة سجلات خدمة الطلبات حتى تجيب عن أسئلة الحادثة، وانتهى دور التطبيق عند سطر JSON يكتبه إلى stdout. ولكن بين هذا السطر واللحظة التي تبحث فيه أو يصلك تنبيه منه طريق طويل: من يقرأ السطر من السيرفر؟ ومن يحوله إلى حقول؟ وماذا يحدث إذا كان فيه بريد عميل نسي المطور أن يحذفه؟ وأين يذهب سطر الخطأ وأين يذهب سطر النجاح العادي؟ وماذا يحدث للسطور إذا توقف المخزن ساعة؟ وهذا الطريق هو ما يسمى مسار السجلات Log Pipeline، وهو موضوع هذا المقال.

المواضيع في السلسلة:

1. [السجلات Logging للمطورين: ماذا تسجل وماذا لا تسجل](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/)
2. [سجلات تساعدك فعلاً: من «حدث خطأ» إلى سبب المشكلة](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-%D8%AA%D8%B3%D8%A7%D8%B9%D8%AF%D9%83-%D9%81%D8%B9%D9%84%D8%A7-%D9%85%D9%86-%D8%AD%D8%AF%D8%AB-%D8%AE%D8%B7%D8%A7-%D8%A7%D9%84%D9%89-%D8%B3%D8%A8%D8%A8-%D8%A7%D9%84%D9%85%D8%B4%D9%83%D9%84%D8%A9/)
3. مسار السجل من التطبيق إلى البحث والتنبيه Log Pipeline (الموضوع الحالي)

وقد شرحنا في المقال الأول [أين تذهب السجلات على السيرفر](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/#where-logs-go) و[فكرة الجمع المركزي](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/#central-logging) باختصار، ونقلنا السجلات إلى Loki بإعداد بسيط. أما هنا فنتعمق في الجزء الذي بين التطبيق والمخزن. وسوف نناقش في هذا المقال ما يلي:

- محطات السجل من التطبيق إلى البحث والتنبيه، والفرق بين الناقل Shipper والمجمع Aggregator.
- أدوات النقل المفتوحة: Vector وFluent Bit وGrafana Alloy وOpenTelemetry Collector وrsyslog، وما حدث لأداة Promtail.
- التحليل Parsing والإثراء Enrichment، وإخفاء البيانات الشخصية في الطريق.
- التوجيه بالمستوى أو بالعميل Tenant، والذاكرة المؤقتة Buffering والضغط العكسي Backpressure.
- نموذج بيانات السجلات في OpenTelemetry، وطبقات الاحتفاظ Retention Tiers والتكلفة.
- مختبر بأداة Vector: تحليل وإخفاء وتوجيه، ونكسر الإخفاء بصيغة لم يتوقعها التعبير المنتظم Regex، ثم نوقف وجهة الأخطاء ونعيد تشغيل الناقل ونتأكد أن شيئاً لم يضع.

## محطات السجل Log Pipeline

كل سطر يمر بالمحطات نفسها تقريباً مهما اختلفت الأدوات، والصورة التالية تبينها بالترتيب:

![ست محطات مرقمة من اليمين: التطبيق يكتب سطر JSON إلى stdout أو ملف، ثم الناقل Shipper مثل Vector وFluent Bit وAlloy وOTel Collector وrsyslog ومعه ذاكرة مؤقتة على القرص، ثم المعالجة بالتحليل والإثراء والإخفاء، ثم التوجيه بالمستوى أو العميل، ثم ثلاث وجهات: الأخطاء للتنبيه فوراً، ومخزن البحث Loki أو OpenSearch لأيام قليلة، والأرشيف في Object Storage لأشهر بتكلفة أقل، ثم البحث والتنبيه، وفي الأسفل: أرخص سطر هو الذي لم يرسل، وأخطر سطر هو الذي وصل ببيانات شخصية](https://arabroot.io/content/images/2026/10/log-pipeline-01-pipeline.webp)

كل قرار عن السطر، من إسقاطه إلى إخفاء ما فيه، أرخص وأأمن كلما جاء أقرب إلى المصدر

وفيما يلي دور كل محطة باختصار:

1. **التطبيق:** يكتب سطراً منظماً إلى stdout أو إلى ملف، ولا يعرف شيئاً عن الباقي، كما في [مبدأ السجلات في Twelve-Factor App](https://12factor.net/logs?ref=arabroot.io).
2. **الناقل Shipper:** برنامج صغير على كل سيرفر، أو Container بجانب التطبيق، يقرأ الملفات أو سجلات Docker أو journald، ويحفظ موضعه في كل ملف حتى لا يقرأ السطر مرتين بعد إعادة التشغيل.
3. **المعالجة:** تحويل النص إلى حقول، وإضافة ما لا يعرفه التطبيق مثل اسم السيرفر والعميل، وإخفاء ما لا يجب أن يخرج.
4. **التوجيه Routing:** قرار أين يذهب كل سطر، بحسب المستوى أو الخدمة أو العميل.
5. **المخازن:** مخزن بحث سريع لأيام، وأرشيف رخيص لأشهر، ووجهة للأخطاء تطلق التنبيه.
6. **البحث والتنبيه:** الواجهة التي يقرأ منها الفريق، والقواعد التي ترسل التنبيه.

وقد يتساءل البعض: لماذا لا يرسل التطبيق سجلاته مباشرة إلى المخزن؟ والإجابة أن ذلك يربط التطبيق بالمخزن، فإذا توقف المخزن أو أبطأ انتظر التطبيق أو أسقط السطور أو امتلأت ذاكرته، أما الناقل فهو برنامج مستقل يتحمل هذه المشكلات عنه، ويمكنك أن تغير المخزن دون أن تلمس سطراً من كود التطبيق.

وفي الأنظمة الكبيرة تظهر طبقتان من الأدوات:

- **الوكيل Agent:** ناقل خفيف على كل سيرفر، يقرأ ويرسل بأقل معالجة ممكنة.
- **المجمع Aggregator:** مجموعة سيرفرات في الوسط تستقبل من كل الوكلاء، وتقوم بالمعالجة الثقيلة والتوجيه والذاكرة المؤقتة الكبيرة، فتغير القواعد في مكان واحد بدل مئة سيرفر.

وفي فريق صغير بسيرفر أو اثنين يكفي ناقل واحد على كل سيرفر يقوم بكل شيء، كما في المختبر.

## أدوات النقل والجمع Shippers and Collectors

الأدوات الخمس التالية مفتوحة المصدر وتقوم بالدور نفسه تقريباً، والفرق في اللغة التي كتبت بها، والرخصة، والطريقة التي تكتب بها قواعد المعالجة. والجدول التالي من مستودعاتها الرسمية في أكتوبر 2026:

| الأداة                                                                                                        | اللغة | الرخصة                                       | آخر إصدار                 | ما يميزها                                                                    |
| ------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------- |
| [Vector](https://github.com/vectordotdev/vector?ref=arabroot.io)                                              | Rust  | MPL-2.0                                      | 0.59.0 في 6 أكتوبر 2026   | لغة VRL للتحويل، وذاكرة مؤقتة على القرص لكل وجهة، وتملكه Datadog             |
| [Fluent Bit](https://github.com/fluent/fluent-bit?ref=arabroot.io)                                            | C     | Apache-2.0                                   | 5.1.3 في 1 أكتوبر 2026    | خفيف جداً، ومشروع متخرج في CNCF ضمن Fluentd، ومنتشر في Kubernetes            |
| [Grafana Alloy](https://github.com/grafana/alloy?ref=arabroot.io)                                             | Go    | Apache-2.0                                   | 1.20.1 في 28 سبتمبر 2026  | توزيعة من OpenTelemetry Collector مع Prometheus، وبديل Promtail              |
| [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector-releases?ref=arabroot.io) | Go    | Apache-2.0                                   | 0.162.0 في 29 سبتمبر 2026 | يجمع السجلات والمقاييس والتتبع بنموذج واحد، ومكوناته في توزيعة core وcontrib |
| [rsyslog](https://github.com/rsyslog/rsyslog?ref=arabroot.io)                                                 | C     | GPL-3.0 مع أجزاء برخصتي LGPL-3.0 وApache-2.0 | 8.2608.0 في 18 أغسطس 2026 | موجود مسبقاً في أغلب توزيعات Linux، ويفهم syslog بكل صيغه                    |

وفي الجدول أعلاه لاحظ التالي:

- **Grafana Alloy:** يصف توثيقه نفسه بأنه [«توزيعة من OpenTelemetry Collector مع مسارات Prometheus مدمجة»](https://grafana.com/docs/alloy/latest/introduction/?ref=arabroot.io)، ويجمع السجلات والمقاييس والتتبع وال Profiles، وهو الطريق الذي تريده Grafana لكل من يرسل إلى Loki.
- **Promtail:** أعلنت Grafana في [إصدار Loki 3.4 في 13 فبراير 2025](https://grafana.com/blog/grafana-loki-3-4-standardized-storage-config-sizing-guidance-and-promtail-merging-into-alloy/?ref=arabroot.io) إيقاف تطوير Promtail ودمج قدراته في Alloy مع دعم طويل حتى نهاية حياته، وتقول [صفحة Promtail الرسمية](https://grafana.com/docs/loki/latest/send-data/promtail/?ref=arabroot.io) اليوم إنه وصل نهاية حياته End of Life في 2 مارس 2026، وإن الدعم التجاري انتهى ولن تصدر له تحديثات، وإن الاستثناء الوحيد هو عميل lambda-promtail. وفي توثيق Alloy أداة تحول إعداد Promtail إلى إعداد Alloy بأمر واحد.
- **OpenTelemetry Collector:** المكونات التي تحتاجها للسجلات أغلبها في توزيعة [contrib](https://opentelemetry.io/docs/collector/distributions/?ref=arabroot.io)، مثل قارئ الملفات filelog بحالة Beta، ومعالج الإخفاء [redaction](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/redactionprocessor?ref=arabroot.io) بحالة Alpha للسجلات، وموصل التوجيه [routing](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector?ref=arabroot.io)، لذلك راجع حالة كل مكون قبل أن تعتمد عليه في الإنتاج.
- **rsyslog:** يحمل المستودع ثلاثة ملفات رخص، COPYING بنص GPL-3.0 وCOPYING.LESSER وCOPYING.ASL20، فإذا كنت ستعدل فيه أو توزعه فاقرأ أي الملفات تحت أي رخصة.

وأي أداة تختار؟ إذا كان مخزنك Loki فAlloy هو الأقرب، وإذا كنت تبني على OpenTelemetry في التتبع كما في [مقال التتبع الموزع](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%AA%D8%A8%D8%B9-%D8%A7%D9%84%D9%85%D9%88%D8%B2%D8%B9-distributed-tracing-%D9%85%D8%B9-opentelemetry/) فالمجمع نفسه يكفي للسجلات، وإذا كان عندك Kubernetes بموارد محدودة فFluent Bit، وإذا أردت لغة تحويل قوية وذاكرة مؤقتة على القرص بإعداد بسيط فVector، وهو الذي نستخدمه في المختبر، وإذا كانت عندك أجهزة شبكة ترسل syslog فrsyslog موجود أصلاً على السيرفر. والمقارنة بالأرقام بين المخازن نفسها لها مقال قادم في السلسلة بإذن الله.

## التحليل Parsing والإثراء Enrichment

التحليل هو تحويل السطر إلى حقول، والإثراء هو إضافة حقول لا يعرفها التطبيق، والحالات التي سوف تقابلها كما يلي:

- **سطر JSON صحيح:** وهو الأسهل، يحلل كما هو، وهذا سبب آخر لأن يكتب التطبيق [سجلاً منظماً](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/#structured-logs).
- **سطر نصي بصيغة معروفة:** مثل سجل Nginx أو syslog، ويحلل بتعبير منتظم أو بمحلل جاهز، كما فعلنا بأداة grok في [مختبر ELK](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/#lab-logstash).
- **سطر لا يمكن تحليله:** مثل Stack Trace طبعه التطبيق خارج المكتبة، ولا تسقطه أبداً، وإنما ضع عليه علامة مثل `parse_error` وأرسله كما هو، والسبب أن السطر الذي لم تفهمه قد يكون أهم سطر في الحادثة.

أما الإثراء فيضيف ما يلي عادة:

- **من أين جاء:** اسم السيرفر وال Container وال Pod، والبيئة.
- **لمن هو:** العميل Tenant أو الفريق المالك، من مسار الملف أو من تسميات ال Container.
- **متى وصل:** وقت استلام الناقل للسطر، بجانب وقت الحدث نفسه، والفرق بينهما يكشف التأخير في المسار.

## إخفاء البيانات الشخصية في الطريق

القاعدة الأولى بقيت كما شرحناها في [ما لا يدخل السجل أبداً](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/#never-log): لا تكتب البيانات الشخصية في السجل أصلاً، والطبقة الثانية في إعداد مكتبة السجل نفسها. أما الإخفاء في الناقل فطبقة ثالثة، ولها ثلاثة أسباب:

- **تحمي من كل التطبيقات معاً:** بما فيها برامج لا تملك كودها، مثل قاعدة بيانات تطبع الاستعلام كاملاً في رسالة الخطأ.
- **تعمل قبل أن ينسخ السطر:** فالسطر الذي يخرج من الناقل يصل إلى المخزن والأرشيف والنسخ الاحتياطية، والإخفاء بعد ذلك يعني أن تبحث في كل هذه الأماكن.
- **قاعدة واحدة يراجعها فريق الأمن:** بدل أن تكون موزعة في كود كل خدمة.

ولكن الإخفاء في الناقل يعتمد غالباً على تعابير منتظمة تبحث عن أنماط مثل البريد ورقم الجوال ورقم البطاقة، وهذه لها حدود واضحة سوف نراها في المختبر: التعبير لا يعرف إلا الصيغ التي كتبتها، فرقم الجوال بمسافات أو بمفتاح الدولة يمر، والرقم الطويل الذي ليس رقم بطاقة قد يخفى خطأ. لذلك فالإخفاء في الناقل شبكة أمان، وليس بديلاً عن ألا تكتب هذه البيانات من الأصل.

## التوجيه بالمستوى أو بالعميل Tenant

ليس كل سطر يستحق المكان نفسه، والتوجيه يقرر لكل سطر وجهته، وأشهر القواعد كما يلي:

- **بالمستوى:** الأخطاء إلى وجهة تطلق التنبيه وتحتفظ بها مدة أطول، والباقي إلى مخزن البحث العادي، وDEBUG يسقط في الناقل نفسه في الإنتاج.
- **بالعميل أو الفريق Tenant:** في نظام يخدم عدة جهات تذهب سجلات كل جهة إلى مساحة منفصلة بصلاحياتها ومدة احتفاظها، مثل ترويسة `X-Scope-OrgID` في Loki، والأساس نفسه شرحناه في [مقال تعدد المستأجرين](https://arabroot.io/articles/%D8%AA%D8%B9%D8%AF%D8%AF-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%A7%D8%AC%D8%B1%D9%8A%D9%86-multi-tenancy-%D9%81%D9%8A-saas-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).
- **بالنوع:** السجلات الأمنية إلى مخزن يملكه فريق الأمن، وسجل التدقيق إلى تخزين لا يحذف منه، كما في [سجل التدقيق Audit Trail](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84-%D8%A7%D9%84%D8%AA%D8%AF%D9%82%D9%8A%D9%82-audit-trail-%D9%85%D9%86-%D9%81%D8%B9%D9%84-%D9%85%D8%A7%D8%B0%D8%A7-%D9%88%D9%85%D8%AA%D9%89/#off-host).
- **بأخذ العينات:** سطور النجاح المتكررة، مثل فحص الصحة، تؤخذ منها عينة أو تسقط، كما شرحنا في [أخذ العينات في المقال السابق](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-%D8%AA%D8%B3%D8%A7%D8%B9%D8%AF%D9%83-%D9%81%D8%B9%D9%84%D8%A7-%D9%85%D9%86-%D8%AD%D8%AF%D8%AB-%D8%AE%D8%B7%D8%A7-%D8%A7%D9%84%D9%89-%D8%B3%D8%A8%D8%A8-%D8%A7%D9%84%D9%85%D8%B4%D9%83%D9%84%D8%A9/#sampling).

## الذاكرة المؤقتة Buffering والضغط العكسي Backpressure

المخزن سوف يتوقف يوماً، لترقية أو لامتلاء القرص أو لمشكلة في الشبكة، والسؤال هو: ماذا يفعل الناقل بالسطور التي تصل أثناء التوقف؟ والخيارات ثلاثة:

- **يحفظها في الذاكرة:** وهذا سريع، ولكنها تضيع إذا أعيد تشغيل الناقل أو امتلأت الذاكرة.
- **يحفظها على القرص:** وهذا أبطأ قليلاً، ولكنها تبقى بعد إعادة التشغيل حتى يعود المخزن.
- **يتوقف عن القراءة:** وهو الضغط العكسي Backpressure، فيبقى السطر في ملفه على السيرفر حتى يفرغ مكان، وهذا آمن ما دام الملف لم يدور ويحذف قبل أن يقرأ.

وكل أداة تسمي ذلك باسمها، والجدول التالي من توثيقها الرسمي:

| الأداة                  | الافتراضي                                           | على القرص                                       | عند الامتلاء                                        |
| ----------------------- | --------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------- |
| Vector                  | ذاكرة بحجم 500 حدث لكل وجهة                         | buffer.type: disk، والحد الأدنى قرابة 256 MiB   | block افتراضياً، أو drop\_newest                    |
| Fluent Bit              | الذاكرة، مع mem\_buf\_limit الذي يوقف المدخل مؤقتاً | storage.type filesystem                         | يتوقف المدخل، أو يسقط الأقدم في وضع memrb           |
| OpenTelemetry Collector | طابور في الذاكرة sending\_queue                     | sending\_queue.storage مع امتداد file\_storage  | يسقط ما لا يدخل الطابور، إلا مع block\_on\_overflow |
| rsyslog                 | طابور في الذاكرة                                    | طوابير على القرص أو بمساعدة القرص Disk-Assisted | بحسب إعداد الطابور                                  |

والمصادر: [نموذج الذاكرة المؤقتة في Vector](https://vector.dev/docs/architecture/buffering-model/?ref=arabroot.io)، و[التخزين المؤقت في Fluent Bit](https://docs.fluentbit.io/manual/data-pipeline/buffering?ref=arabroot.io)، و[طابور الإرسال في OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md?ref=arabroot.io)، و[الطوابير في rsyslog](https://docs.rsyslog.com/doc//concepts/queues.html?ref=arabroot.io). ولاحظ تفصيلين من توثيق Vector: الأول أن الكتابة على القرص تزامن Sync كل 500 مللي ثانية وليس مع كل حدث، فقد تضيع أجزاء من الثانية الأخيرة إذا انقطعت الكهرباء، والثاني أن Vector يوقف نفسه إذا فشلت الكتابة على القرص، لذلك احسب مساحة القرص للحد الأقصى لكل ذاكرة مؤقتة. وتوثيق OpenTelemetry Collector يقول عن الطابور الدائم إنه إذا توقف المجمع «فعند إعادة التشغيل تؤخذ العناصر ويستمر الإرسال».

## نموذج بيانات السجلات في OpenTelemetry

كل أداة كانت تسمي الحقول بطريقتها، فالوقت `@timestamp` في مكان و`ts` في مكان آخر، والمستوى نص في أداة ورقم في أخرى، وهذا ما يحاول حله [نموذج بيانات السجلات في OpenTelemetry](https://opentelemetry.io/docs/specs/otel/logs/data-model/?ref=arabroot.io)، وحالته اليوم مستقر Stable. والنموذج يعرف السجل LogRecord بالحقول التالية، والصورة بعدها تبين كيف تقابل حقول سطرنا من المقال السابق:

- **Timestamp وObservedTimestamp:** وقت وقوع الحدث، ووقت رؤية الناقل له.
- **TraceId وSpanId وTraceFlags:** الربط بال Trace، كما في [ربط السجلات بال Trace](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%AA%D8%A8%D8%B9-%D8%A7%D9%84%D9%85%D9%88%D8%B2%D8%B9-distributed-tracing-%D9%85%D8%B9-opentelemetry/#logs-and-traces).
- **SeverityText وSeverityNumber:** المستوى نصاً ورقماً، والرقم هو ما يسمح بالمقارنة بين أدوات تسمي المستويات بأسماء مختلفة.
- **Body:** الرسالة نفسها.
- **Resource:** من أنتج السجل، مثل اسم الخدمة وإصدارها والسيرفر.
- **InstrumentationScope:** المكتبة أو الجزء الذي كتب السطر.
- **Attributes:** تفاصيل هذا الحدث بالذات، مثل معرف العميل ونوع البطاقة.
- **EventName:** نوع الحدث، والتوثيق يقول إن السجل الذي له EventName غير فارغ هو حدث Event.

![عمودان: في اليمين حقول سطرنا JSON وفي اليسار حقول LogRecord في OpenTelemetry، وأسهم بينها: ts إلى Timestamp، وlevel إلى SeverityText وSeverityNumber حيث warn يساوي 13 وerror يساوي 17، وevent إلى EventName، وtrace_id إلى TraceId وSpanId وTraceFlags، وservice وversion وenv إلى Resource، وcustomer_id وcard_brand وغيرها إلى Attributes، وerror إلى Body، وreceived_at إلى ObservedTimestamp](https://arabroot.io/content/images/2026/10/log-pipeline-02-otel-model.webp)

الفرق بين الحقول التي تصف من أنتج السجل Resource والحقول التي تصف الحدث نفسه Attributes هو أهم ما في النموذج

وقد يتساءل البعض: هل يجب أن يكتب التطبيق بهذه الأسماء؟ والإجابة لا، فالتطبيق يكتب بقاموس فريقك، والناقل يحول الأسماء إلى النموذج عند الحاجة، ولكن الفصل بين Resource وAttributes يستحق أن تطبقه من الآن، لأن المخازن تعامل الحقول التي تصف المصدر معاملة مختلفة، فهي قليلة القيم وتصلح للفهرسة، أما حقول الحدث فكثيرة القيم ولا تصلح، كما شرحنا في [تحذير الكاردينالية في Loki](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/#stores).

## طبقات الاحتفاظ Retention Tiers والتكلفة

شرحنا في [الاحتفاظ والتكلفة](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/#retention) أن الحساب يبدأ من حجمك اليومي، والذي نضيفه هنا أن السجل لا يحتاج المكان نفسه طوال عمره، فسجل اليوم تبحث فيه كل ساعة، وسجل الشهر الماضي تبحث فيه مرة في الأسبوع، وسجل السنة الماضية قد لا تفتحه إلا لمراجعة أمنية، والصورة التالية تبين الطبقات:

![أربع طبقات من اليمين: الطبقة الساخنة Hot بسيرفر وبحث سريع وفهرس كامل ومثال 7 أيام وأعلى تكلفة لكل GB، ثم الطبقة الدافئة Warm بأقراص أرخص وبحث أبطأ ومثال 30 يوماً، ثم الأرشيف البارد في Object Storage بملفات مضغوطة ومثال سنة للسجلات الأمنية، ثم الحذف التلقائي بعد المدة بما فيها النسخ الاحتياطية، وتحتها سهم عمر السجل](https://arabroot.io/content/images/2026/10/log-pipeline-04-retention.webp)

المدد في الصورة أمثلة، والمدة الحقيقية تحددها سياسة جهتك والأنظمة التي تخضع لها

ولكل مخزن طريقته في ذلك، فLoki يحذف بعد مدة بأداة الضغط Compactor كما في [توثيق الاحتفاظ في Loki](https://grafana.com/docs/loki/latest/operations/storage/retention/?ref=arabroot.io)، وElasticsearch ينقل الفهارس بين الطبقات بسياسة ILM كما في [مقال ELK](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/#data-streams)، وOpenSearch بسياسة ISM كما في [مقال OpenSearch](https://arabroot.io/articles/opensearch-%D8%A7%D9%84%D8%A8%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D9%84-elasticsearch/#lab-setup).

ولنأخذ مثالاً بالأرقام من المختبر السابق: الحدث الواسع كان 766 بايتاً لكل طلب، فلو كانت خدمتك تستقبل مليوني طلب في اليوم، فالحجم الخام قرابة 1.5 GB يومياً، وفي الطبقة الساخنة لسبعة أيام قرابة 10.7 GB قبل الضغط والفهرسة، ولثلاثين يوماً في الطبقة الدافئة قرابة 46 GB. وهذا حساب توضيحي بافتراض عدد الطلبات، والحجم الحقيقي على القرص يختلف بحسب ضغط المخزن وفهرسته، لذلك قس حجمك الفعلي في مخزنك قبل أن تحدد المدد. ولاحظ أن كل قرار في الناقل يغير هذا الرقم مباشرة: إسقاط DEBUG، وأخذ عينة من النجاح المتكرر، وحذف حقول لا يقرؤها أحد.

## المختبر: مسار واحد بأداة Vector

في هذا المختبر نبني مساراً واحداً بأداة [Vector](https://vector.dev/?ref=arabroot.io) 0.59.0 يقرأ ملفات سجلات لعميلين، ويحلل JSON، ويضيف اسم العميل، ويخفي البريد ورقم الجوال وما يشبه رقم البطاقة، ويرسل الأخطاء إلى وجهة والباقي إلى وجهة أخرى، مع ذاكرة مؤقتة على القرص لوجهة الأخطاء. والوجهتان مخزنان صغيران، كل منهما Vector آخر يستقبل عبر HTTP ويكتب ما يصله في ملف، حتى نرى بالضبط ما وصل. وكل الأسماء تبدأ بالبادئة `lab-log-pipe-`، ولا يوجد أي منفذ مفتوح على الجهاز.

### ملفات المختبر

الملف `compose.yaml`:

```yaml
name: lab-log-pipe
services:
  vector:
    image: timberio/vector:0.59.0-debian
    container_name: lab-log-pipe-vector
    volumes:
      - ./vector.yaml:/etc/vector/vector.yaml:ro
      - ./logs:/logs:ro
      - lab-log-pipe-data:/var/lib/vector
  alerts:
    image: timberio/vector:0.59.0-debian
    container_name: lab-log-pipe-alerts
    volumes: ["./receiver.yaml:/etc/vector/vector.yaml:ro", "./out/alerts:/out"]
  archive:
    image: timberio/vector:0.59.0-debian
    container_name: lab-log-pipe-archive
    volumes: ["./receiver.yaml:/etc/vector/vector.yaml:ro", "./out/archive:/out"]
volumes:
  lab-log-pipe-data:
    name: lab-log-pipe-data
```

والملف `receiver.yaml` هو المخزن الصغير الذي تستخدمه الوجهتان:

```yaml
# A tiny log store: receives newline-delimited JSON over HTTP and appends it to a file.
sources:
  in:
    type: http_server
    address: 0.0.0.0:8080
    framing: { method: newline_delimited }
    decoding: { codec: json }

transforms:
  clean:
    type: remap
    inputs: ["in"]
    source: |
      del(.path)
      del(.source_type)
      del(.timestamp)

sinks:
  out:
    type: file
    inputs: ["clean"]
    path: /out/received.jsonl
    encoding: { codec: json }
```

والملف `vector.yaml` هو المسار نفسه، وهذه نسخته الأولى:

```yaml
data_dir: /var/lib/vector

sources:
  app_files:
    type: file
    include: ["/logs/*/*.log"]

transforms:
  parse:
    type: remap
    inputs: ["app_files"]
    source: |
      file = string!(.file)
      parsed, err = parse_json(.message)
      if err == null && is_object(parsed) {
        . = object!(parsed)
      } else {
        . = {"level": "unknown", "parse_error": true, "message": .message}
      }
      # enrichment: which tenant the file belongs to, which collector shipped it, and when
      .tenant = parse_regex(file, r'^/logs/(?P<t>[^/]+)/').t ?? "unknown"
      .collector = get_hostname!()
      .received_at = now()
      # redaction: personal data in any string field, at any depth
      . = map_values(., recursive: true) -> |v| {
        if is_string(v) {
          redact(string!(v), filters: [
            r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}',
            r'05\d{8}',
            r'\b\d{16}\b'
          ])
        } else { v }
      }

  by_level:
    type: route
    inputs: ["parse"]
    route:
      errors: '.level == "error" || .level == "fatal"'

sinks:
  alerts:
    type: http
    inputs: ["by_level.errors"]
    uri: http://lab-log-pipe-alerts:8080/
    encoding: { codec: json }
    framing: { method: newline_delimited }
    buffer:
      type: disk
      max_size: 268435488
      when_full: block

  archive:
    type: http
    inputs: ["by_level._unmatched"]
    uri: http://lab-log-pipe-archive:8080/
    encoding: { codec: json }
    framing: { method: newline_delimited }
```

في الإعداد أعلاه لاحظ التالي:

- المصدر [file](https://vector.dev/docs/reference/configuration/sources/file/?ref=arabroot.io) يقرأ كل ملف في `/logs/<tenant>/`، ويحفظ موضعه في كل ملف داخل `data_dir`، لذلك ثبتنا هذا المجلد على Volume.
- التحويل `parse` بلغة VRL يحلل السطر، وإذا لم يكن JSON صحيحاً لا يسقطه، وإنما يرسله بالمستوى `unknown` والعلامة `parse_error`.
- الإثراء يأخذ اسم العميل من اسم المجلد، ويضيف اسم الناقل ووقت الاستلام.
- الدالة [redact](https://vector.dev/docs/reference/vrl/functions/?ref=arabroot.io#redact) تستبدل كل ما يطابق التعابير بالنص `[REDACTED]`، والدالة `map_values` تطبقها على كل حقل نصي في الحدث مهما كان عمقه، وليس على حقول بأسمائها، لأن البيانات الشخصية تظهر غالباً حيث لا تتوقعها، مثل رسالة خطأ.
- التحويل [route](https://vector.dev/docs/reference/configuration/transforms/route/?ref=arabroot.io) يرسل الأخطاء إلى المخرج `errors`، وكل ما لا يطابق إلى المخرج `_unmatched`.
- وجهة الأخطاء وحدها لها ذاكرة مؤقتة على القرص بأقل حجم يقبله Vector، أي 256 MiB تقريباً، ومع الامتلاء تنتظر `block` ولا تسقط.

### الخطوة 1: سجلات لعميلين، وتحليل وتوجيه

نشغل ال Containers، ثم نكتب في ملف المتجر `shop` خمسة سطور، منها سطر نصي ليس JSON، وفي ملف العيادة `clinic` سطرين:

```bash
mkdir -p logs/shop logs/clinic out/alerts out/archive
docker compose up -d
cat > logs/shop/app.log <<'EOF'
{"ts":"2026-10-15T09:00:01.120Z","level":"info","event":"order.checkout","order_id":"ord_1a","customer_id":"cus_1042","outcome":"ok","duration_ms":182.4}
{"ts":"2026-10-15T09:00:02.310Z","level":"error","event":"order.checkout","order_id":"ord_1b","customer_id":"cus_1043","outcome":"failed","error_code":"PSP_TIMEOUT","error":"provider timeout for sara@example.com"}
{"ts":"2026-10-15T09:00:03.004Z","level":"warn","event":"customer.contact.invalid","customer_id":"cus_1044","contact":"0551234567"}
{"ts":"2026-10-15T09:00:04.550Z","level":"error","event":"payment.request.rejected","order_id":"ord_1c","body":"card=4111111111111111&exp=12/29"}
Traceback (most recent call last): KeyError 'customer_id'
EOF
cat > logs/clinic/app.log <<'EOF'
{"ts":"2026-10-15T09:00:05.000Z","level":"info","event":"appointment.booked","patient_id":"pat_77","contact":"+966 55 123 4567"}
{"ts":"2026-10-15T09:00:06.000Z","level":"error","event":"sms.send.failed","error":"gateway rejected +966551234567","card_hint":"4111 1111 1111 1111"}
EOF
```

ثم نقرأ ما وصل إلى كل وجهة. وقد شغلنا الأوامر من مجلد المختبر، واختصرنا المسار الكامل للمجلد في مخرج `wc` لاحقاً إلى `out/`، أما محتوى السطور فكما هو:

```bash
cat out/archive/received.jsonl
```

```json
{"collector":"265b6caef827","contact":"+966 55 123 4567","event":"appointment.booked","level":"info","patient_id":"pat_77","received_at":"2026-10-14T21:59:15.478473659Z","tenant":"clinic","ts":"2026-10-15T09:00:05.000Z"}
{"collector":"265b6caef827","customer_id":"cus_1042","duration_ms":182.4,"event":"order.checkout","level":"info","order_id":"ord_1a","outcome":"ok","received_at":"2026-10-14T21:59:15.478706945Z","tenant":"shop","ts":"2026-10-15T09:00:01.120Z"}
{"collector":"265b6caef827","contact":"[REDACTED]","customer_id":"cus_1044","event":"customer.contact.invalid","level":"warn","received_at":"2026-10-14T21:59:15.478926031Z","tenant":"shop","ts":"2026-10-15T09:00:03.004Z"}
{"collector":"265b6caef827","level":"unknown","message":"Traceback (most recent call last): KeyError 'customer_id'","parse_error":true,"received_at":"2026-10-14T21:59:15.479125219Z","tenant":"shop"}
```

```bash
cat out/alerts/received.jsonl
```

```json
{"card_hint":"4111 1111 1111 1111","collector":"265b6caef827","error":"gateway rejected +966551234567","event":"sms.send.failed","level":"error","received_at":"2026-10-14T21:59:15.478636949Z","tenant":"clinic","ts":"2026-10-15T09:00:06.000Z"}
{"collector":"265b6caef827","customer_id":"cus_1043","error":"provider timeout for [REDACTED]","error_code":"PSP_TIMEOUT","event":"order.checkout","level":"error","order_id":"ord_1b","outcome":"failed","received_at":"2026-10-14T21:59:15.478726943Z","tenant":"shop","ts":"2026-10-15T09:00:02.310Z"}
{"body":"card=[REDACTED]&exp=12/29","collector":"265b6caef827","event":"payment.request.rejected","level":"error","order_id":"ord_1c","received_at":"2026-10-14T21:59:15.479015025Z","tenant":"shop","ts":"2026-10-15T09:00:04.550Z"}
{"collector":"265b6caef827","event":"test.one","level":"error","order_id":"ord_x","received_at":"2026-10-14T22:00:49.048167845Z","tenant":"shop","ts":"2026-10-15T09:00:07.000Z"}
```

في المخرج أعلاه لاحظ التالي:

- التوجيه يعمل: الأخطاء الثلاثة في وجهة التنبيه، وINFO وWARN والسطر غير المفهوم في الأرشيف، وكل سطر يحمل `tenant` الصحيح من اسم المجلد.
- سطر ال Traceback لم يضع، ووصل بالعلامة `parse_error` فتستطيع أن تبحث عنه وتعرف أي تطبيق يطبع خارج مكتبة السجلات.
- البريد داخل رسالة الخطأ أخفي، ورقم الجوال بالصيغة المحلية `0551234567` أخفي، ورقم البطاقة داخل النص `card=…&exp` أخفي.
- السطر الأخير `test.one` ليس من البيانات أعلاه، وقصته في الملاحظة التالية.

⚠️

في تشغيلنا الأول وصلت سطور الأرشيف خلال ثوان، أما الأخطاء الثلاثة التي تمر بالذاكرة المؤقتة على القرص فبقيت في ملف ال Buffer ولم تصل، ورأينا الملف `buffer-data-0.dat` بحجم 1016 بايتاً دون أي تحذير في سجل Vector، حتى كتبنا سطر خطأ رابعاً للاختبار `test.one` بعد قرابة دقيقة ونصف، فوصلت الأربعة معاً. ولم نجد في التوثيق ما يفسر ذلك، ولم يتكرر بعد إعادة تشغيل Vector في الخطوات التالية، لذلك لا نعرف سببه، والدرس أن تقيس زمن وصول التنبيه في مسارك الحقيقي بعد كل تغيير في الذاكرة المؤقتة، ولا تفترض أن الإعداد الآمن هو الإعداد السريع.

### الخطوة 2: نكسر الإخفاء بصيغة لم يتوقعها التعبير

انظر مرة أخرى إلى سطري العيادة في المخرج أعلاه، فقد مرا كما هما:

- `"contact":"+966 55 123 4567"` في الأرشيف، لأن التعبير `05\d{8}` لا يعرف الرقم بمفتاح الدولة ولا بالمسافات.
- `"gateway rejected +966551234567"` في وجهة الأخطاء، للسبب نفسه.
- `"card_hint":"4111 1111 1111 1111"`، لأن التعبير `\b\d{16}\b` يريد ستة عشر رقماً متصلة، والبطاقة هنا بمسافات كما يكتبها أغلب الناس.

وهذا هو الخطأ الأشهر في الإخفاء بالتعابير المنتظمة: يكتب أحدهم التعبير ويختبره على المثال الذي في رأسه، ثم تصل البيانات بصيغ أخرى. والإصلاح أن نكتب التعبير للصيغ التي نعرف أنها موجودة، فنستبدل السطرين في `vector.yaml` كما يلي:

```yaml
            r'(?:\+966|00966|\b0)\s?5\d(?:[\s-]?\d){7}\b',
            r'\b(?:\d[ -]?){12,18}\d\b'
```

الأول يقبل البداية `+966` أو `00966` أو `0`، ثم 5 ورقماً، ثم سبعة أرقام بينها مسافة أو شرطة اختيارية، والثاني يقبل من 13 إلى 19 رقماً بينها مسافة أو شرطة اختيارية. ثم نعيد تشغيل Vector ونكتب سطوراً جديدة، ومنها سطر فيه رقم ليس رقم بطاقة:

```bash
docker restart lab-log-pipe-vector
cat >> logs/clinic/app.log <<'EOF'
{"ts":"2026-10-15T09:01:05.000Z","level":"info","event":"appointment.booked","patient_id":"pat_78","contact":"+966 55 123 4567"}
{"ts":"2026-10-15T09:01:06.000Z","level":"error","event":"sms.send.failed","error":"gateway rejected +966551234567","card_hint":"4111-1111-1111-1111"}
{"ts":"2026-10-15T09:01:07.000Z","level":"info","event":"report.exported","rows":1250,"export_id":"exp_20261015","took_ms":1791272925247}
EOF
tail -n 3 out/archive/received.jsonl
tail -n 2 out/alerts/received.jsonl
```

```json
{"collector":"265b6caef827","level":"unknown","message":"Traceback (most recent call last): KeyError 'customer_id'","parse_error":true,"received_at":"2026-10-14T21:59:15.479125219Z","tenant":"shop"}
{"collector":"265b6caef827","contact":"[REDACTED]","event":"appointment.booked","level":"info","patient_id":"pat_78","received_at":"2026-10-14T22:02:15.020456407Z","tenant":"clinic","ts":"2026-10-15T09:01:05.000Z"}
{"collector":"265b6caef827","event":"report.exported","export_id":"exp_20261015","level":"info","received_at":"2026-10-14T22:02:15.020902071Z","rows":1250,"tenant":"clinic","took_ms":1791272925247,"ts":"2026-10-15T09:01:07.000Z"}
{"collector":"265b6caef827","event":"test.one","level":"error","order_id":"ord_x","received_at":"2026-10-14T22:00:49.048167845Z","tenant":"shop","ts":"2026-10-15T09:00:07.000Z"}
{"card_hint":"[REDACTED]","collector":"265b6caef827","error":"gateway rejected [REDACTED]","event":"sms.send.failed","level":"error","received_at":"2026-10-14T22:02:15.020761482Z","tenant":"clinic","ts":"2026-10-15T09:01:06.000Z"}
```

الصيغ الثلاث أخفيت الآن، ووصل الخطأ هذه المرة بعد ثوان. ولاحظ السطر `report.exported`: الحقل `took_ms` رقم من 13 خانة يطابق تعبير البطاقة الجديد، ولكنه لم يخف لأن قيمته رقم وليست نصاً، والإخفاء يعمل على النصوص فقط. أما إذا كان الرقم نفسه نصاً فسوف يخفى خطأ، وهذا ما رأيناه في الخطوة التالية مع رقم فاتورة. ولا يوجد تعبير يمسك كل الصيغ ولا يخطئ أبداً، والحل أن تختبر قواعد الإخفاء على عينة حقيقية من سجلاتك، وأن تضيف اختبار Luhn لأرقام البطاقات إذا أزعجتك الأخطاء، وأن تتذكر أن ما فات القاعدة قد وصل إلى المخزن.

### الخطوة 3: نوقف وجهة الأخطاء، ثم نعيد تشغيل الناقل

نعد ما وصل حتى الآن، ثم نوقف وجهة الأخطاء ونكتب 2000 سطر، كل رابع سطر منها خطأ، ثم سطراً فيه رقم فاتورة، وننتظر 15 ثانية:

```bash
wc -l out/alerts/received.jsonl out/archive/received.jsonl
docker stop lab-log-pipe-alerts
for i in $(seq 1 2000); do
  if [ $((i % 4)) -eq 0 ]; then
    echo "{\"ts\":\"2026-10-15T09:10:00.000Z\",\"level\":\"error\",\"event\":\"order.checkout\",\"order_id\":\"ord_o$i\",\"outcome\":\"failed\",\"error_code\":\"PSP_TIMEOUT\"}"
  else
    echo "{\"ts\":\"2026-10-15T09:10:00.000Z\",\"level\":\"info\",\"event\":\"order.checkout\",\"order_id\":\"ord_o$i\",\"outcome\":\"ok\"}"
  fi
done >> logs/shop/app.log
echo '{"ts":"2026-10-15T09:10:01.000Z","level":"info","event":"invoice.created","invoice_no":"2026101500012345"}' >> logs/shop/app.log
sleep 15
wc -l out/archive/received.jsonl
tail -n1 out/archive/received.jsonl
docker exec lab-log-pipe-vector sh -c 'ls -la /var/lib/vector/buffer/v2/alerts'
docker logs --since 30s lab-log-pipe-vector 2>&1 | grep -E "WARN|ERROR" | head -2
```

والمخرج سوف يكون كما يلي:

```text
   5 out/alerts/received.jsonl
   6 out/archive/received.jsonl
  11 total
1507 out/archive/received.jsonl
{"collector":"265b6caef827","event":"invoice.created","invoice_no":"[REDACTED]","level":"info","received_at":"2026-10-14T22:02:48.187773727Z","tenant":"shop","ts":"2026-10-15T09:10:01.000Z"}
total 164
drwxr-xr-x 2 root root   4096 Oct 14 21:58 .
drwxr-xr-x 3 root root   4096 Oct 14 21:58 ..
-rw-r----- 1 root root 151896 Oct 14 22:02 buffer-data-0.dat
-rw-r----- 1 root root     24 Oct 14 22:02 buffer.db
-rw-r--r-- 1 root root      0 Oct 14 22:01 buffer.lock
2026-10-14T22:02:51.960151Z  WARN sink{component_kind="sink" component_id=alerts component_type=http}:request{request_id=2}:http: vector::internal_events::http_client: HTTP error. error=error trying to connect: dns error: failed to lookup address information: Name or service not known error_type="request_failed" stage="processing"
2026-10-14T22:02:51.960371Z  WARN sink{component_kind="sink" component_id=alerts component_type=http}:request{request_id=2}: vector::sinks::util::retries: Retrying after error. error=Failed to make HTTP(S) request: error trying to connect: dns error: failed to lookup address information: Name or service not known
```

في المخرج أعلاه لاحظ التالي:

- الأرشيف لم يتأثر بتوقف الوجهة الأخرى، فقد وصل إليه 1501 سطر جديد، 1500 سطر INFO وسطر الفاتورة.
- رقم الفاتورة `2026101500012345` أخفي خطأ، لأنه 16 رقماً في نص، وهذا هو الإخفاء الزائد الذي ذكرناه، وثمنه أن رقم الفاتورة ضاع من السجل.
- الأخطاء الخمسمئة في ملف ال Buffer على القرص بحجم 151896 بايتاً، وVector يحاول الإرسال ويسجل التحذير ثم يكتم تكراره حتى لا يغرق سجله.

والآن نختبر ما لا تحتمله الذاكرة العادية: نعيد تشغيل الناقل نفسه والوجهة ما زالت متوقفة، ثم نعيد الوجهة وننتظر 20 ثانية ونعد:

```bash
docker restart lab-log-pipe-vector
docker exec lab-log-pipe-vector sh -c 'ls -la /var/lib/vector/buffer/v2/alerts | grep dat'
docker start lab-log-pipe-alerts
sleep 20
wc -l out/alerts/received.jsonl
grep -c '"ord_o' out/alerts/received.jsonl
grep -o '"order_id":"ord_o[0-9]*"' out/alerts/received.jsonl | sort -u | wc -l
docker stats --no-stream --format '{{.Name}} {{.MemUsage}}' lab-log-pipe-vector lab-log-pipe-alerts lab-log-pipe-archive
```

```text
-rw-r----- 1 root root 151896 Oct 14 22:02 buffer-data-0.dat
505 out/alerts/received.jsonl
500
500
lab-log-pipe-vector 110.5MiB / 31.29GiB
lab-log-pipe-alerts 65.25MiB / 31.29GiB
lab-log-pipe-archive 74.3MiB / 31.29GiB
```

وصلت الأخطاء الخمسمئة كلها، وكل رقم طلب مرة واحدة دون تكرار، رغم أن الناقل أعيد تشغيله وهي ما زالت في الطريق، والصورة التالية تلخص الخطوة:

![ثلاث لوحات من اليمين: الأولى نوقف وجهة الأخطاء بالأمر docker stop، و2001 سطر جديد في الملف، و1501 وصلت إلى الأرشيف فوراً، و500 خطأ بقيت في ال Buffer في الملف buffer-data-0.dat بحجم 151896 بايتاً، والثانية نعيد تشغيل الناقل بالأمر docker restart والوجهة ما زالت متوقفة والملف بالحجم نفسه، والثالثة نعيد الوجهة بالأمر docker start وبعد 20 ثانية وصلت الأخطاء كلها دون تكرار 500 من 500](https://arabroot.io/content/images/2026/10/log-pipeline-03-buffer.webp)

الذاكرة المؤقتة على القرص تحمي السطور من توقف الوجهة ومن إعادة تشغيل الناقل معاً

ولاحظ أن ملف ال Buffer بقي بحجمه بعد التفريغ، والسبب أن Vector يكتب في ملفات بيانات ويحذفها أو يعيد استخدامها عندما تقرأ كاملة، وليس بعد كل حدث. ولم نكرر الخطوة نفسها بالذاكرة المؤقتة الافتراضية في الذاكرة، وتوثيق Vector يقول صراحة إنها غير دائمة، أي أنها لا تبقى بعد إعادة التشغيل أو الانهيار، وهذا يكفي لتختار القرص لكل وجهة لا تحتمل فقد السطور.

### حذف المختبر

نحذف ال Containers والشبكة وال Volume بالأسماء التي أنشأناها فقط:

```bash
docker compose down -v
```

ولم نسحب أي Docker Image جديد، فقد كانت timberio/vector:0.59.0-debian موجودة مسبقاً على الجهاز.

## أخطاء شائعة Common Mistakes

- **التطبيق يرسل إلى المخزن مباشرة:** فيتوقف أو يبطئ أو يفقد السطور كلما توقف المخزن.
- **بدء مشروع جديد بأداة Promtail:** وقد انتهت حياتها في 2 مارس 2026 ولن تصلها تحديثات أمنية.
- **إسقاط السطور التي لا يفهمها المحلل:** وهي غالباً ال Stack Trace الذي تحتاجه.
- **الإخفاء بالتعابير المنتظمة وحده:** دون أن تختبره على صيغ حقيقية، فتمر الأرقام بمسافات أو بمفتاح الدولة.
- **الإخفاء بعد التخزين:** فيكون السطر قد نسخ إلى المخزن والأرشيف والنسخ الاحتياطية.
- **الذاكرة المؤقتة في الذاكرة لوجهة مهمة:** فتضيع السطور مع أول إعادة تشغيل أثناء توقف الوجهة.
- **ذاكرة مؤقتة على القرص دون حساب المساحة:** فيمتلئ القرص الذي يكتب فيه التطبيق أيضاً.
- **تدوير الملفات أسرع من قدرة الناقل:** فيحذف الملف قبل أن يقرأ أثناء الضغط العكسي، وتضيع السطور دون أي خطأ.
- **مدة احتفاظ واحدة لكل شيء:** فإما أن تدفع ثمن البحث السريع لسجلات سنة، أو تحذف السجلات الأمنية قبل أن تحتاجها.

## تدريب عملي

- أضف إلى المسار قاعدة تسقط سطور DEBUG وسطور `/healthz` الناجحة، ثم قس عدد السطور التي وصلت إلى الأرشيف قبل القاعدة وبعدها.
- غير `when_full` إلى `drop_newest` واجعل `max_size` صغيراً لذاكرة في الذاكرة `type: memory`، ثم أوقف الوجهة وعد ما ضاع.
- اجمع عينة من سجلاتك الحقيقية وابحث فيها بالتعابير الثلاثة، ثم راجع يدوياً ما أخفي خطأ وما فات.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- التطبيق يكتب سطراً منظماً إلى stdout ويتوقف دوره، والناقل يتحمل عنه القراءة والتحليل والإخفاء والتوجيه وتوقف المخزن.
- Vector وFluent Bit وAlloy وOpenTelemetry Collector وrsyslog كلها مفتوحة وتقوم بالدور نفسه، وPromtail انتهت حياته في 2 مارس 2026 وبديله Alloy.
- الإخفاء في الناقل طبقة أمان ثالثة، وفي المختبر مرت ثلاث صيغ من البيانات الشخصية من التعبير الأول، وأخفى التعبير الثاني رقم فاتورة خطأ، فاختبر قواعدك على بيانات حقيقية.
- الذاكرة المؤقتة على القرص حفظت 500 خطأ من 500 رغم توقف الوجهة وإعادة تشغيل الناقل، والأرشيف لم يتأثر بتوقف وجهة أخرى.
- نموذج بيانات السجلات في OpenTelemetry مستقر، وأهم ما تأخذه منه الفصل بين من أنتج السجل Resource وتفاصيل الحدث Attributes، وطبقات الاحتفاظ تجعل السجل القديم رخيصاً بدل أن تحذفه مبكراً.

وفي المقالات القادمة من السلسلة بإذن الله نقارن المخازن نفسها بالأرقام على البيانات نفسها، ثم نتعمق في Loki وGraylog وSeq.

[![](https://arabroot.io/content/images/2026/10/logging-basics-cover-cover.webp)دليلالسجلات Logging للمطورين: ماذا تسجل وماذا لا تسجلكيف تكتب سجلات Logs تساعدك على فهم ما حدث في الإنتاج، وما الذي لا يدخل السجل أبداً، وكيف تجمعها في مكان واحد.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/logs-that-help-cover-cover.webp)دليلسجلات تساعدك فعلاً: من «حدث خطأ» إلى سبب المشكلةكيف تكتب سجلات تجيب عن أسئلة الحادثة، بتحقيق في عطل حقيقي بسجلات سيئة ثم بحدث واسع Wide Event لكل طلب، مع قياس الفرق.](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-%D8%AA%D8%B3%D8%A7%D8%B9%D8%AF%D9%83-%D9%81%D8%B9%D9%84%D8%A7-%D9%85%D9%86-%D8%AD%D8%AF%D8%AB-%D8%AE%D8%B7%D8%A7-%D8%A7%D9%84%D9%89-%D8%B3%D8%A8%D8%A8-%D8%A7%D9%84%D9%85%D8%B4%D9%83%D9%84%D8%A9/)[![](https://arabroot.io/content/images/2026/10/distributed-tracing-cover-cover.webp)دليلالتتبع الموزع Distributed Tracing مع OpenTelemetryكيف ترى طلباً واحداً يمر بعدة خدمات في شجرة زمنية واحدة، فتعرف أين أبطأ وأين فشل دون أن تخمن من السجلات.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%AA%D8%A8%D8%B9-%D8%A7%D9%84%D9%85%D9%88%D8%B2%D8%B9-distributed-tracing-%D9%85%D8%B9-opentelemetry/)[![](https://arabroot.io/content/images/2026/10/elk-stack-cover-cover.webp)دليلمجموعة ELK: Elasticsearch وLogstash وKibanaكيف تجمع سجلات سيرفراتك في Elasticsearch وتبحث فيها من Kibana بأمان، وما الذي تعنيه رخصته لك اليوم.](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/)[![](https://arabroot.io/content/images/2026/10/opensearch-cover-cover.webp)دليلOpenSearch: البديل المفتوح لـ Elasticsearch وقصة الترخيصلماذا ولد OpenSearch من Elasticsearch، وكيف تشغله على سيرفرك بصلاحيات دقيقة وتنبيهات حقيقية على سجلاتك.](https://arabroot.io/articles/opensearch-%D8%A7%D9%84%D8%A8%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D9%84-elasticsearch/)[![](https://arabroot.io/content/images/2026/10/audit-trail-cover-cover.webp)دليلAudit Trail: كيف تبني سجل تدقيق لا يقبل العبثكيف تصمم سجلاً يجيب عن سؤال من غير هذه الصلاحية ومتى، وتحميه من الحذف والتعديل بعد كتابته.](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84-%D8%A7%D9%84%D8%AA%D8%AF%D9%82%D9%8A%D9%82-audit-trail-%D9%85%D9%86-%D9%81%D8%B9%D9%84-%D9%85%D8%A7%D8%B0%D8%A7-%D9%88%D9%85%D8%AA%D9%89/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ المختبر بأداة Vector 0.59.0 في Docker، والتحقق من إصدارات Fluent Bit وGrafana Alloy وOpenTelemetry Collector وrsyslog ورخصها من مستودعاتها، ومن حالة Promtail من توثيق Grafana.