> ## 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.

# سجلات منظمة Structured Logs تبحث فيها بالخصائص مع Seq
- URL: https://arabroot.io/articles/سجلات-منظمة-structured-logs-مع-seq/
- Published: 2026-10-22T14:45:00.000Z
- Updated: 2026-10-23T04:21:39.000Z
- Description: كيف تشغل Seq وترسل إليه السجلات من .NET وأي لغة أخرى، وتبحث فيها بالخصائص، وتضبط الاحتفاظ والتنبيهات، مع ما يسمح به الترخيص المجاني.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية, Seq

في [دليل السجلات للمطورين](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/) كتبنا السجل حدثاً منظماً له حقول، وفي [مسار السجل Log Pipeline](https://arabroot.io/articles/%D9%85%D8%B3%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D8%AC%D9%84-log-pipeline-%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%88%D8%A7%D9%84%D8%AA%D9%86%D8%A8%D9%8A%D9%87/) نقلناه من التطبيق إلى مخزن مركزي. وبقي سؤال يسأله كل فريق صغير، خصوصاً فرق .NET: أين نضع هذه الأحداث بحيث نبحث فيها بالحقول كما نبحث في جدول، ونبني منها تنبيهاً، دون أن نشغل مجموعة كاملة من Elasticsearch وKibana أو نتعلم لغة استعلام جديدة؟ وأحد أشهر الإجابات هو [Seq](https://datalust.co/?ref=arabroot.io)، وهو سيرفر سجلات من شركة Datalust بني من البداية للسجلات المنظمة Structured Logs، ويستقبل الأحداث من .NET عبر Serilog ومن أي لغة أخرى عبر HTTP.

وقبل أن نبدأ، لاحظ أن Seq ليس الخيار الوحيد: فهناك [Grafana Loki](https://arabroot.io/articles/%D8%AC%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logs-%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A7-%D9%85%D8%B9-grafana-loki/) الذي يخزن النص بأقل تكلفة ويفهرس الملصقات Labels فقط، و[Graylog](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-logs-%D9%85%D8%B9-graylog-%D9%88-opensearch/) الذي يجمع السجلات من الأجهزة والسيرفرات فوق OpenSearch، و[مجموعة 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/) و[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/) للبحث النصي الكامل على حجم كبير. أما Seq فمكانه الفريق الذي يكتب سجلات منظمة أصلاً ويريد سيرفراً واحداً يعمل خلال دقائق، وهذا الدليل عنه.

وسوف نتناول في هذا المقال ما يلي:

- ما هو Seq ولمن يناسب، والترخيص والأسعار كما هي في أكتوبر 2026.
- التثبيت مع Docker Compose، ومفاتيح API Keys، ولماذا يجب أن تمنع الإرسال بدون مفتاح.
- ثلاثة طرق لإرسال السجلات: Serilog من .NET، وصيغة CLEF عبر HTTP من أي لغة، وGELF من Container قديم يكتب نصاً.
- البحث بالخصائص Properties، والاستعلام بلغة تشبه SQL، والبحث بالنص العربي.
- الإشارات Signals، وسياسات الاحتفاظ Retention، والتنبيهات Alerts إلى Webhook.
- ماذا قسنا عند تحميل 500 ألف حدث، والنسخ الاحتياطي والاستعادة، والمشكلات التي ظهرت لنا في المختبر وحلولها.

## ما هو Seq ولمن يناسب؟

Seq سيرفر واحد يستقبل الأحداث Events ويخزنها بخصائصها على القرص، ويعطيك واجهة ويب للبحث والاستعلام ولوحات Dashboards وتنبيهات، دون أي مكون آخر: لا يحتاج قاعدة بيانات خارجية ولا محرك بحث ولا ناقل Shipper إلزامي. والشكل التالي يبين الطرق التي تصل بها الأحداث إليه، وما يفعله بها بعد ذلك:

![أربعة مصادر على اليمين: تطبيق .NET يرسل عبر Serilog.Sinks.Seq، وأي لغة أخرى ترسل CLEF عبر HTTP، وContainer قديم يكتب نصاً إلى stdout فيمر عبر GELF driver ثم seq-input-gelf، وOpenTelemetry SDK يرسل OTLP، وكلها تصل إلى سيرفر Seq في الوسط على المنفذ 5341 للاستقبال و80 للواجهة، ويخزن في /data الأحداث بخصائصها، ثم على اليسار البحث بالخصائص والإشارات Signals والاحتفاظ Retention والتنبيهات Alerts](https://arabroot.io/content/images/2026/10/seq-01-paths.webp)

كل الطرق تنتهي إلى حدث له خصائص، ومنها يبنى البحث والإشارات والتنبيهات

والفكرة التي يقوم عليها Seq هي قالب الرسالة Message Template: فعندما تكتب في Serilog `Log.Error(ex, "Checkout {OrderId} failed with {ErrorCode}", orderId, code)` لا يصل إلى السيرفر نص فقط، وإنما يصل القالب نفسه وقيمة كل متغير خاصيةً مستقلة، كما في الشكل التالي:

![في الأعلى سطر Log.Error بقالب فيه OrderId وErrorCode وElapsedMs وCardBrand، وفي الأسفل الحدث كما يصل إلى Seq بصيغة CLEF: الحقول @t و@l و@mt و@x، ثم الخصائص OrderId وErrorCode وElapsedMs وCardBrand، ثم خصائص أضافها الإعداد مثل Application وRequestId وخاصية Environment التي أضافها مفتاح API، وفي الأسفل تحذير من بناء الرسالة بدمج النصوص](https://arabroot.io/content/images/2026/10/seq-02-event.webp)

القالب يجمع الأحداث من النوع نفسه، والخصائص هي ما تبحث به وتحسب عليه

وبالتالي تستطيع أن تبحث بـ `ErrorCode = 'PSP_TIMEOUT'` وأن تحسب متوسط `ElapsedMs` لكل نوع بطاقة، بدل أن تبحث عن كلمة داخل نص. وقد شرحنا لماذا يكتب السجل منظماً من الأصل في [سجلات تساعدك فعلاً](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/)، فلن نعيده هنا.

### نقاط القوة ونقاط الضعف

نقاط القوة:

- **سيرفر واحد Single Binary:** Container واحد ومجلد بيانات واحد، ويعمل خلال دقيقة، وهذا ما رأيناه في المختبر.
- **تكامل .NET:** المكتبة [Serilog.Sinks.Seq](https://github.com/serilog/serilog-sinks-seq?ref=arabroot.io) سطر واحد في الإعداد، والقوالب والاستثناءات Exceptions تصل كما هي.
- **لغة استعلام قريبة من SQL:** `select count(*) from stream where … group by …` يعرفها أي مطور، مع [تعابير بحث Search Expressions](https://datalust.co/docs/query-syntax?ref=arabroot.io) بسيطة للبحث اليومي.
- **تنبيهات وإشارات مدمجة:** لا تحتاج أداة منفصلة للتنبيه، وتصل التنبيهات إلى Email أو Slack أو أي Webhook عبر تطبيقات Seq Apps.

نقاط الضعف:

- **ليس مفتوح المصدر:** Seq منتج تجاري، والنسخة المجانية لمستخدم واحد فقط، وأي فريق يحتاج ترخيصاً مدفوعاً كما سيأتي.
- **التوسع Scaling له حدود:** يعمل جيداً على سيرفر واحد قوي، والتجميع Clustering متاح فقط في خطة Datacenter، لذلك لا يناسب من يجمع عشرات التيرابايت يومياً.
- **البحث النصي الكامل محدود:** البحث عن نص داخل الرسالة يعمل، ولكنه ليس محرك بحث نصي مثل OpenSearch يحلل الكلمات Analyzers ويطبع الحروف العربية Normalization، وهذا ما ظهر لنا في [البحث بالعربية](#arabic).
- **يستهلك ما يجده من موارد:** توصي [متطلبات النظام](https://datalust.co/docs/system-requirements?ref=arabroot.io) بتشغيله على سيرفر مخصص، لأنه يحاول استخدام ما يتاح له من الذاكرة والمعالج.

### الترخيص والأسعار

Seq يعمل بترخيص تجاري، والقبول به شرط للتشغيل (`ACCEPT_EULA=Y`). والجدول التالي من [صفحة الأسعار](https://datalust.co/pricing?ref=arabroot.io) كما رأيناها في 22 أكتوبر 2026، والأسعار بالدولار سنوياً دون الضريبة:

| الخطة      | السعر السنوي | المستخدمون  | التخزين | عدد السيرفرات                 |
| ---------- | ------------ | ----------- | ------- | ----------------------------- |
| Individual | 0 دولار      | مستخدم واحد | 50 GB   | غير محدد                      |
| Team       | 1,090 دولار  | حتى 10      | 100 GB  | سيرفر واحد                    |
| Standard   | 2,990 دولار  | بلا حد      | بلا حد  | حتى 3                         |
| Datacenter | 10,990 دولار | بلا حد      | بلا حد  | بلا حد، مع التجميع Clustering |

وكل الخطط تشمل مدة احتفاظ ومصادر وتنبيهات بلا حد والدخول الموحد SSO. أما الترخيص المجاني Individual فتنص الصفحة على أنه يسمح لشخص واحد فقط بالدخول إلى واجهة الويب، ويسمح لكل مطور في الشركة أن يشغل نسخته الخاصة ما دام لا يستخدمها غيره. لذلك هو مناسب لجهاز المطور أو لسيرفر يتابعه شخص واحد، أما إذا كان الفريق كله سيفتح الواجهة فالبداية من Team. وقد يتساءل البعض: هل يتوقف Seq إذا تجاوزت 50 GB في الخطة المجانية؟ الصفحة لا توضح ذلك، لذلك اضبط [الاحتفاظ](#retention) حتى لا تقترب من الحد أصلاً.

⚠️

الأسعار والحدود تتغير، لذلك راجع صفحة الأسعار عند الشراء. والأرقام أعلاه مؤرخة بتاريخ مراجعتنا لها.

## المتطلبات

- **السيرفر:** تذكر [متطلبات Seq](https://datalust.co/docs/system-requirements?ref=arabroot.io) أن الحمل الخفيف (أحداث قليلة في الثانية واحتفاظ 30 يوماً) يكفيه معالجان (2 CPU) و3.5 GB من الذاكرة (RAM) و50 GB على قرص SSD، وأن السيرفر المحمل يبدأ من 16 معالجاً و64 GB. وفي مختبرنا استهلك Seq بين 80 و145 MiB وهو فارغ، ووصل إلى 1.2 GiB بعد تحميل 500 ألف حدث والاستعلام عليها.
- **القرص:** قرص سريع بعدد عمليات IOPS مرتفع، ولا يدعم Seq مشاركات SMB ولا ضغط مجلدات NTFS.
- **البرامج:** Docker مع Docker Compose، وGit Bash أو أي Shell لتنفيذ الأوامر.
- **المنافذ Ports:** المنفذ 80 داخل الـ Container للواجهة والـ API، والمنفذ 5341 للاستقبال فقط، والمنفذ 12201/udp إذا شغلت مدخل GELF.
- **نطاق فرعي Subdomain:** مثل `seq.example.com` إذا أردت الوصول من خارج السيرفر عبر HTTPS.

## التثبيت مع Docker Compose

نبدأ بمجلد للمشروع، وفيه ملف `.env` للأسرار. ولا نضع كلمة مرور المدير Admin كما هي، وإنما نضع الهاش Hash الذي يولده Seq نفسه، ونولد معه مفتاح التشفير Secret Key الذي يحمي الحقول الحساسة والنسخ الاحتياطية:

```bash
mkdir -p ~/seq && cd ~/seq
printf 'كلمة-مرور-قوية-هنا' | docker run --rm -i datalust/seq:2026.1.17182 config hash
docker run --rm datalust/seq:2026.1.17182 show-key --generate
```

ثم نكتب الناتجين في `.env`:

```ini
SEQ_ADMIN_HASH=QG4EuGYq6Ecp...الهاش-كاملاً
SEQ_SECRET_KEY=hyqBdQRa8nEN...المفتاح-كاملاً=
SEQ_PUBLIC_URL=https://seq.example.com
SEQ_GELF_KEY=
```

🛑

احفظ `SEQ_SECRET_KEY` خارج السيرفر في مدير كلمات المرور. فالنسخة الاحتياطية من Seq مشفرة بهذا المفتاح، ولا يمكن استعادتها بدونه، وهذا ما تنص عليه [صفحة النسخ الاحتياطي في Docker](https://datalust.co/docs/backup-and-restore-docker?ref=arabroot.io).

والآن ملف `docker-compose.yml` بإصدارات محددة، وفيه Seq ومدخل GELF الذي سنحتاجه لاحقاً:

```yaml
services:
  seq:
    image: datalust/seq:2026.1.17182
    container_name: seq
    restart: unless-stopped
    environment:
      ACCEPT_EULA: "Y"
      SEQ_FIRSTRUN_ADMINPASSWORDHASH: ${SEQ_ADMIN_HASH}
      SEQ_STORAGE_SECRETKEY: ${SEQ_SECRET_KEY}
      SEQ_API_CANONICALURI: ${SEQ_PUBLIC_URL}
    volumes:
      - seq-data:/data
    ports:
      - "127.0.0.1:8080:80"      # الواجهة والـ API، خلف الـ Reverse Proxy
      - "127.0.0.1:5341:5341"    # الاستقبال فقط
    healthcheck:
      test: ["CMD", "curl", "-fs", "http://localhost/health"]
      interval: 15s
      timeout: 5s
      retries: 10

  seq-gelf:
    image: datalust/seq-input-gelf:4.0.02323
    container_name: seq-gelf
    restart: unless-stopped
    depends_on:
      seq:
        condition: service_healthy
    environment:
      SEQ_ADDRESS: http://seq:5341
      SEQ_API_KEY: ${SEQ_GELF_KEY}
      GELF_ADDRESS: udp://0.0.0.0:12201
    ports:
      - "127.0.0.1:12201:12201/udp"

volumes:
  seq-data:
```

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

- `SEQ_FIRSTRUN_ADMINPASSWORDHASH` يستخدم في أول تشغيل فقط لإنشاء المستخدم `admin`، ومع الهاش لا يطلب Seq تغيير كلمة المرور عند أول دخول، بخلاف `SEQ_FIRSTRUN_ADMINPASSWORD` كما في [دليل التشغيل مع Docker](https://datalust.co/docs/docker-deployment-overview?ref=arabroot.io).
- `SEQ_API_CANONICALURI` هو العنوان الذي يضعه Seq في روابط التنبيهات، وبدونه وصلتنا روابط تبدأ بـ `http://localhost:80` لا تفتح عند أحد. ولهذا الإعداد أثر آخر سوف تراه في [المشكلات الشائعة](#troubleshooting).
- المنفذان مربوطان على `127.0.0.1` فقط، لأن الواجهة ستمر عبر الـ Reverse Proxy، وتوصي Datalust بفصل منفذ الاستقبال 5341 عن منفذ الواجهة حتى تفتح للتطبيقات الاستقبال وحده.
- الـ Healthcheck يستخدم `/health` الذي يجيب بـ `{"status":"healthy"}` عندما يكون السيرفر جاهزاً، ولذلك ينتظر مدخل GELF حتى يصبح Seq جاهزاً.

نشغل Seq وحده أولاً، لأن مدخل GELF يحتاج مفتاحاً لم ننشئه بعد:

```bash
docker compose up -d --wait seq
curl -s http://127.0.0.1:8080/health
```

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

```bash
{"status":"healthy","description":"The Seq node is in service."}
```

## مفاتيح API Keys: من يرسل ومن يقرأ

كل تطبيق يرسل إلى Seq يجب أن يكون له مفتاح خاص به، والسبب أن المفتاح يحدد من أين جاء الحدث، ويمكن أن يضيف إليه خصائص ثابتة، وأن يرفض ما دون مستوى معين، وأن يلغى وحده إذا تسرب دون أن تمس بقية التطبيقات. وننشئ المفاتيح بأداة [seqcli](https://github.com/datalust/seqcli?ref=arabroot.io) الرسمية، ونشغلها في Container داخل شبكة Seq:

```bash
docker run -d --name seqcli --network seq_default --entrypoint sleep datalust/seqcli:2026.1.02654 infinity
SEQCLI="docker exec -i seqcli /bin/seqcli/seqcli"

# مفتاح لإدارة Seq من السكربتات
echo 'كلمة-مرور-قوية-هنا' | $SEQCLI apikey create -t lab-admin \
  --permissions=Read,Write,Project,Organization,System \
  -s http://seq --connect-username admin --connect-password-stdin

# مفتاح للاستقبال فقط لتطبيق الطلبات، يضيف Environment=lab لكل حدث
echo 'كلمة-مرور-قوية-هنا' | $SEQCLI apikey create -t orders-api -p Environment=lab \
  --minimum-level Information -s http://seq --connect-username admin --connect-password-stdin
```

كل أمر يطبع المفتاح الجديد فقط، فاحفظه مباشرة. ثم نعرض المفاتيح:

```bash
$SEQCLI apikey list -s http://seq -a "$ADMIN"
```

```bash
apikey-35 lab-admin
apikey-37 orders-api
apikey-default Unauthenticated
```

لاحظ السطر الأخير: `apikey-default Unauthenticated`، وهو يمثل كل حدث يصل بدون مفتاح. وهنا أول ما جربناه: أرسلنا ثلاثة أحداث مرة بدون مفتاح، ومرة بمفتاح غير موجود أصلاً هو `wrongkey123`، فقبلها Seq في المرتين:

```bash
201 {"MinimumLevelAccepted":null}
201 {"MinimumLevelAccepted":null}
```

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

```bash
$SEQCLI setting set -n RequireApiKeyForWritingEvents -v True -s http://seq -a "$ADMIN"
```

وبعد الإعداد أعدنا الإرسال نفسه، فرفض Seq الطلبين الأولين وقبل الطلب الذي يحمل مفتاح التطبيق:

```bash
401 {"Error":"A valid API key is required for writing events."}
401 {"Error":"A valid API key is required for writing events."}
201 {"MinimumLevelAccepted":"Information"}
```

ولاحظ أن `MinimumLevelAccepted` أصبح `Information`، وهو الحد الذي وضعناه في المفتاح، فالمكتبات التي تفهم هذا الرد مثل Serilog تتوقف عن إرسال ما دونه ولا تضيع الشبكة فيه.

💡

لا يستطيع مفتاح API أن ينشئ مفتاحاً آخر حتى لو كانت صلاحياته كاملة: جربنا ذلك فرد Seq بـ `403/Forbidden`. لذلك تنشأ المفاتيح بالدخول بمستخدم كما في الأوامر أعلاه، وهذا منطقي لأن المفتاح المسرب لا يستطيع أن يولد مفاتيح جديدة تبقى بعد إلغائه.

## إرسال السجلات من .NET مع Serilog

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

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Serilog" Version="4.4.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
    <PackageReference Include="Serilog.Sinks.Seq" Version="9.1.0" />
  </ItemGroup>
</Project>
```

والكود في `Program.cs`:

```csharp
using Serilog;
using Serilog.Context;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext()
    .Enrich.WithProperty("Application", "orders-api")
    .WriteTo.Console()
    .WriteTo.Seq("http://seq:5341", apiKey: Environment.GetEnvironmentVariable("SEQ_API_KEY"))
    .CreateLogger();

var rnd = new Random(7);
for (var i = 1; i <= 200; i++)
{
    var orderId = $"ord_{i:D4}";
    using (LogContext.PushProperty("RequestId", Guid.NewGuid().ToString("N")))
    {
        var card = rnd.Next(10) < 4 ? "mada" : "visa";
        var ms = card == "mada" ? rnd.Next(1200, 3200) : rnd.Next(50, 250);
        if (ms > 2000)
        {
            try { throw new TimeoutException($"payment provider did not answer within 2000 ms"); }
            catch (TimeoutException ex)
            {
                Log.Error(ex, "Checkout {OrderId} failed with {ErrorCode} after {ElapsedMs} ms for card {CardBrand}", orderId, "PSP_TIMEOUT", ms, card);
            }
        }
        else
        {
            Log.Information("Checkout {OrderId} completed in {ElapsedMs} ms for card {CardBrand}", orderId, ms, card);
        }
    }
}
Log.CloseAndFlush();
```

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

- الرسالة قالب فيه أسماء بين أقواس، والقيم تمرر بعده، وهذه هي [قوالب الرسائل Message Templates](https://messagetemplates.org/?ref=arabroot.io). لا تكتب `$"Checkout {orderId} failed"` لأن القيمة تذوب في النص فلا تصبح خاصية.
- `Enrich.WithProperty` يضيف `Application` لكل حدث، و`LogContext.PushProperty` يضيف `RequestId` لكل ما يكتب داخل الطلب، وهذا ما يربط أحداث الطلب الواحد.
- `Log.CloseAndFlush()` ضروري في البرامج القصيرة، لأن Serilog يرسل على دفعات Batches، فإذا انتهى البرنامج قبل الدفعة الأخيرة ضاعت.
- عنوان الإرسال هو المنفذ 5341، وهو منفذ الاستقبال، أما الواجهة فلا يحتاجها التطبيق.

شغلنا البرنامج داخل Container من `mcr.microsoft.com/dotnet/sdk:10.0.400` في شبكة Seq، وآخر ما طبعه على الشاشة:

```bash
[01:06:39 ERR] Checkout ord_0200 failed with PSP_TIMEOUT after 3025 ms for card mada
System.TimeoutException: payment provider did not answer within 2000 ms
   at Program.<Main>$(String[] args) in /tmp/b/Program.cs:line 22
```

والحدث نفسه كما خزنه Seq، وقد طلبناه بـ `seqcli search --json`:

```json
{"@t":"2026-10-23T01:06:39.5928136+00:00","@mt":"Checkout {OrderId} failed with {ErrorCode} after {ElapsedMs} ms for card {CardBrand}","@l":"Error","@x":"System.TimeoutException: payment provider did not answer within 2000 ms\n   at Program.<Main>$(String[] args) in /tmp/b/Program.cs:line 22","Application":"orders-api","CardBrand":"mada","ElapsedMs":3025,"Environment":"lab","ErrorCode":"PSP_TIMEOUT","OrderId":"ord_0200","RequestId":"e2da227dbd8c4b68b371a2185b521071"}
```

لاحظ أن `ElapsedMs` رقم وليس نصاً، وأن `Environment` لم يكتبه التطبيق وإنما أضافه مفتاح API، وأن الاستثناء وصل كاملاً في `@x`.

## من أي لغة: صيغة CLEF عبر HTTP

إذا كان تطبيقك بلغة Python أو Go أو Node.js فلا تحتاج مكتبة خاصة بـ Seq، لأن الاستقبال مجرد طلب HTTP. فالمسار `/ingest/clef` يستقبل أحداثاً بصيغة [CLEF](https://github.com/serilog/serilog-formatting-compact?ref=arabroot.io)، وهي سطر JSON لكل حدث فيه حقول تبدأ بـ `@`: `@t` للوقت وهو الحقل الوحيد الإلزامي، و`@m` للرسالة أو `@mt` للقالب، و`@l` للمستوى، و`@x` للاستثناء، كما في [صفحة إرسال الأحداث](https://datalust.co/docs/posting-raw-events?ref=arabroot.io). والمثال التالي بمكتبة Python القياسية فقط، ورسالتان منه بالعربية:

```python
import json, os, urllib.request
from datetime import datetime, timezone

URL = os.environ.get("SEQ_URL", "http://seq:5341/ingest/clef")
KEY = os.environ.get("SEQ_API_KEY", "")
now = datetime.now(timezone.utc).isoformat(timespec="milliseconds")
events = [
    {"@t": now, "@mt": "Shipping quote for {OrderId} took {ElapsedMs} ms", "OrderId": "ord_0042", "ElapsedMs": 180, "Application": "shipping-worker"},
    {"@t": now, "@mt": "تعذر الوصول إلى خدمة الشحن للطلب {OrderId}", "@l": "Error", "OrderId": "ord_0043", "Application": "shipping-worker", "ErrorCode": "SHIPPING_DOWN"},
    {"@t": now, "@mt": "فشل الدفع بسبب انتهاء المهلة للطلب {OrderId}", "@l": "Warning", "OrderId": "ord_0044", "Application": "shipping-worker"},
]
body = "\n".join(json.dumps(e, ensure_ascii=False) for e in events).encode("utf-8")
req = urllib.request.Request(URL, data=body, method="POST", headers={"Content-Type": "application/vnd.serilog.clef"})
if KEY:
    req.add_header("X-Seq-ApiKey", KEY)
try:
    with urllib.request.urlopen(req) as r:
        print(r.status, r.read().decode() or "(empty body)")
except urllib.error.HTTPError as e:
    print(e.code, e.read().decode())
```

والمخرج مع مفتاح صحيح:

```bash
201 {"MinimumLevelAccepted":"Information"}
```

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

- الأحداث مفصولة بسطر جديد في طلب واحد، ونوع المحتوى `application/vnd.serilog.clef`، وهكذا ترسل الدفعة كاملة في طلب HTTP واحد.
- المفتاح في الترويسة Header `X-Seq-ApiKey`، ويمكن وضعه في الرابط `?apiKey=` ولكن الترويسة أفضل لأن الروابط تظهر في سجلات الـ Reverse Proxy.
- `@l` نص مثل `Error` أو `Warning`، وإذا غاب اعتبر Seq الحدث معلوماتياً Information.

ويستقبل Seq أيضاً السجلات والتتبع Traces بصيغة OpenTelemetry على المسار `/ingest/otlp/v1/logs` عبر HTTP/protobuf، وعبر gRPC على HTTPS فقط، كما في [صفحة OpenTelemetry](https://datalust.co/docs/ingestion-with-opentelemetry?ref=arabroot.io). فإذا كان تطبيقك يستخدم OpenTelemetry SDK فوجهه إلى Seq مباشرة بالمفتاح نفسه في الترويسة.

## من Container قديم يكتب نصاً: GELF

لنفرض أن لديك مهمة ليلية قديمة لا تستطيع تعديل كودها، وكل ما تفعله أنها تطبع أسطراً إلى stdout. هنا يأتي دور [مشغل السجلات GELF في Docker](https://docs.docker.com/engine/logging/drivers/gelf/?ref=arabroot.io): Docker نفسه يرسل كل سطر بصيغة GELF عبر UDP، ويستقبله الـ Container [seq-input-gelf](https://github.com/datalust/seq-input-gelf?ref=arabroot.io) ويحوله إلى Seq عبر HTTP. ننشئ له مفتاحاً، ونضعه في `SEQ_GELF_KEY` داخل `.env`، ثم نشغله:

```bash
docker compose up -d seq-gelf
```

ثم نشغل المهمة القديمة مع مشغل GELF:

```bash
docker run --rm --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 --log-opt tag=nightly-report \
  alpine:3.22 sh -c 'echo "report started"; echo "ERROR: export to sftp failed, retry 3/3"; echo "تم إنشاء التقرير الليلي"'
```

ووصلت الأسطر الثلاثة إلى Seq ومعها خصائص أضافها Docker، وهذا أحدها:

```json
{"@t":"2026-10-23T01:06:56.0760000+00:00","@mt":"ERROR: export to sftp failed, retry 3/3","@l":"info","Environment":"lab","command":"sh -c echo \"report started\"; …","container_name":"obs3-legacy-job","host":"docker-desktop","image_name":"alpine:3.22","tag":"nightly-report"}
```

لاحظ `"@l":"info"`: السطر يقول ERROR ولكن Seq خزنه معلوماتياً، لأن stdout لا يحمل مستوى، فكل ما يعرفه مشغل GELF هو النص. لذلك فإن إشارة Errors لن تجد هذا السطر، والتنبيه المبني على المستوى لن ينطلق. فإذا اضطررت لهذا الطريق فابحث بالنص (`@Message like 'ERROR%'`)، أو حلل الأسطر في ناقل مثل Vector قبل Seq كما شرحنا في [مسار السجل](https://arabroot.io/articles/%D9%85%D8%B3%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D8%AC%D9%84-log-pipeline-%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%88%D8%A7%D9%84%D8%AA%D9%86%D8%A8%D9%8A%D9%87/).

⚠️

صفحة GELF في توثيق Seq تكتب القيمة الافتراضية لـ `GELF_ADDRESS` بالشكل `0.0.0.0:12201/udp`، وعندما استخدمناها بهذا الشكل امتلأ سجل الـ Container بالخطأ `GELF input failed` و`invalid socket address syntax` ولم يصل شيء. والصيغة التي عملت هي `udp://0.0.0.0:12201` كما في ملف README للمستودع.

## البحث والاستعلام

في Seq طريقتان للسؤال: تعبير بحث Search Expression يرجع الأحداث نفسها، واستعلام بلغة تشبه SQL يرجع جدولاً من الأرقام. والتعبير يكتب في مربع البحث كما هو:

```sql
Application = 'orders-api' and @Level = 'Error'
```

![شاشة Events في Seq وفي مربع البحث ErrorCode = 'PSP_TIMEOUT'، وتحته قائمة أحداث حمراء كلها Checkout ord_XXXX failed with PSP_TIMEOUT after XXXX ms for card mada، وعلى اليمين قائمة الإشارات Signals فيها Errors وWarnings وExceptions](https://arabroot.io/content/images/2026/10/seq-03-events.webp)

البحث بخاصية واحدة يرجع كل أحداث انتهاء المهلة، وكلها لبطاقة mada

أما الاستعلام فيبدأ بـ `select` ويستخدم `stream` مصدراً للأحداث، كما في [صفحة SQL Queries](https://datalust.co/docs/sql-queries?ref=arabroot.io). والمثال التالي يحسب عدد العمليات ومتوسط زمنها وأعلى زمن لكل نوع بطاقة:

```sql
select count(*) as orders, mean(ElapsedMs) as avg_ms, percentile(ElapsedMs, 95) as p95_ms
from stream
where Application = 'orders-api'
group by CardBrand
```

ونتيجته بعد تشغيل البرنامج مرة واحدة (200 عملية):

```bash
"CardBrand","orders","avg_ms","p95_ms"
"visa","113","144.7433628318584070796460177","237.5153722811128"
"mada","87","2078.2758620689655172413793103","3011.871780688884"
```

وهنا يظهر الفرق بين السجل النصي والسجل المنظم: من 200 سطر عرفنا في ثانية أن متوسط بطاقة mada نحو ثانيتين مقابل 145 ملي ثانية لـ visa، وعرفنا من استعلام آخر أن الـ 45 فشلاً كلها لبطاقة mada. والشاشة نفسها تعرض الجدول أو رسماً بيانياً منه:

![شاشة Events في Seq وفي مربع البحث استعلام select count(*) as orders, mean(ElapsedMs) as avg_ms, max(ElapsedMs) as max_ms from stream where Application = 'orders-api' group by CardBrand، والنتيجة جدول بسطرين: mada بعدد 261 ومتوسط 2078 وأعلى 3198، وvisa بعدد 339 ومتوسط 144 وأعلى 241، وتحته Computed in 3.9 ms](https://arabroot.io/content/images/2026/10/seq-04-sql.webp)

الاستعلام نفسه في الواجهة بعد ثلاث مرات تشغيل للبرنامج، ومعه أزرار التحويل إلى رسم بياني

### البحث بالعربية

Seq يخزن النص بترميز UTF-8 ويبحث فيه كما هو، فالبحث عن عبارة عربية بين علامتي تنصيص يعمل:

```bash
$SEQCLI search -s http://seq -a "$ADMIN" -f '"انتهاء المهلة"' -c 3
```

```bash
[2026-10-23T01:06:47.1520000+00:00 WRN] فشل الدفع بسبب انتهاء المهلة للطلب ord_0044
```

![شاشة Events في Seq وفي مربع البحث عبارة انتهاء المهلة بين علامتي تنصيص، والنتيجة حدث واحد بلون التحذير: فشل الدفع بسبب انتهاء المهلة للطلب ord_0044](https://arabroot.io/content/images/2026/10/seq-05-arabic.webp)

البحث بعبارة عربية يجد الحدث الذي أرسله سكربت Python

ولكن البحث يطابق الحروف كما هي، فعندما بحثنا عن `"المهله"` بالهاء بدل التاء المربوطة لم يرجع شيئاً، وكذلك الحال مع الهمزات (أ وا) والألف المقصورة (ى وي)، لأن Seq ليس فيه مطبع للحروف العربية Arabic Normalizer كالذي في محركات البحث النصي. والقاعدة هنا: لا تعتمد على البحث في نص الرسالة العربية، وإنما ضع في الحدث خاصية ثابتة بالإنجليزية مثل `ErrorCode = 'PSP_TIMEOUT'` وابحث بها، واترك النص العربي للقراءة فقط. وعلى 500 ألف حدث في المختبر رجع الاستعلام `message_ar like '%انتهاء المهلة%'` بـ 6271 حدثاً، وهو العدد الصحيح تماماً الذي حسبناه من الملف الأصلي.

## الإشارات Signals

الإشارة Signal فلتر محفوظ باسم، تختاره بضغطة من القائمة على يمين الشاشة بدل أن تكتب التعبير كل مرة. وتجمع Seq الإشارات بطريقة ذكية كما في [صفحة الإشارات](https://datalust.co/docs/signals?ref=arabroot.io): إذا اخترت إشارتين من مجموعة واحدة (تطبيقين مثلاً) أخذ ما يطابق أياً منهما، وإذا اخترت إشارتين من مجموعتين مختلفتين (تطبيق وبيئة) أخذ ما يطابقهما معاً، وتذكر الصفحة أن Seq يبني فهارس Indexes للإشارات فتصبح الاستعلامات التي تستخدمها أسرع على البيانات الكبيرة. أنشأنا ثلاث إشارات بـ seqcli:

```bash
$SEQCLI signal create -t 'orders-api' -f "Application = 'orders-api'" -c OrderId -c ElapsedMs -s http://seq -a "$ADMIN"
$SEQCLI signal create -t 'Payment timeouts' -f "ErrorCode = 'PSP_TIMEOUT'" -c OrderId -c CardBrand -s http://seq -a "$ADMIN"
$SEQCLI signal create -t 'Debug noise' -f "@Level in ['Debug', 'Verbose']" -s http://seq -a "$ADMIN"
```

```bash
signal-38 orders-api
signal-39 Payment timeouts
signal-40 Debug noise
```

والخيار `-c` يحدد الأعمدة التي تظهر بجانب كل حدث عند اختيار الإشارة. والإشارة الثالثة لن نبحث بها، وإنما سنحذف بها في الخطوة التالية.

## سياسات الاحتفاظ Retention

بدون سياسة احتفاظ يبقى كل حدث إلى أن يمتلئ القرص. وفي Seq تحذف [سياسة الاحتفاظ](https://datalust.co/docs/retention-policies?ref=arabroot.io) الأحداث بعد عمر معين، إما كلها أو ما يطابق إشارة معينة، وهذا يسمح بأن تحتفظ بالأخطاء شهراً وبسجلات Debug يومين فقط:

```bash
$SEQCLI retention create --data-source stream --after 30d --delete-all -s http://seq -a "$ADMIN"
$SEQCLI retention create --data-source stream --after 2d --delete signal-40 -s http://seq -a "$ADMIN"
```

![شاشة Storage في Seq: رسم للبيانات المخزنة في آخر يوم، وتحته إحصاءات التخزين، وعلى اليمين قسم RETENTION POLICIES فيه سياستان: After 2 days delete Debug noise in stream، وAfter 30 days delete all data in stream](https://arabroot.io/content/images/2026/10/seq-07-storage.webp)

السياستان في شاشة Storage: الأولى تحذف إشارة Debug noise بعد يومين، والثانية تحذف كل شيء بعد 30 يوماً

وفي هذه الخطوة لاحظ التالي:

- الخيار `--delete` يأخذ رقم الإشارة `signal-40` وليس تعبيراً، وعندما كتبنا التعبير مكانه فشل الأمر بخطأ `` Syntax error … unexpected id `in` ``. وكذلك يطلب الأمر `--data-source stream` صراحة في هذا الإصدار.
- الإشارة المستخدمة في الاحتفاظ يجب أن تكون مشتركة Shared وليست شخصية.
- الحذف يعمل في الخلفية، فلن ترى المساحة تنخفض فوراً، وتنصح الصفحة بثلاث إلى خمس سياسات كحد أعلى، لأن كل سياسة تستهلك وقتاً من المعالج والقرص.
- يتوقف Seq عن استقبال الأحداث إذا نزلت المساحة الحرة عن حد *Minimum free storage space* في الإعدادات، فالحذف بالعمر هو ما يمنع الوصول إلى هذا الحد.

## التنبيهات Alerts إلى Webhook

التنبيه في Seq استعلام يعمل دورياً على نافذة زمنية، فإذا تحقق شرط `having` أرسل إشعاراً عبر تطبيق من تطبيقات Seq، كما في [صفحة التنبيهات](https://datalust.co/docs/alerts?ref=arabroot.io). وسوف نرسل الإشعار إلى Webhook بسيط، فنثبت أولاً تطبيق [Seq.App.HttpRequest](https://github.com/datalust/seq-app-httprequest?ref=arabroot.io) وننشئ منه نسخة Instance تشير إلى عنوان الـ Webhook:

```bash
$SEQCLI app install --package-id Seq.App.HttpRequest -s http://seq -a "$ADMIN"
$SEQCLI appinstance create -t 'Ops webhook' --app hostedapp-45 -p Url=http://hooks.example.com/seq-alert \
  -p BodyIsTemplate=True -p MediaType=text/plain \
  -p Body=$'Seq alert: {@m}\nTitle: {Alert.Title}\nResults: {Source.Results}\nLink: {Alert.Url}' \
  -s http://seq -a "$ADMIN"
```

ثم التنبيه نفسه: إذا زادت حالات انتهاء المهلة لنوع بطاقة واحد عن 5 في دقيقة واحدة، مع إيقاف التكرار عشر دقائق:

```bash
$SEQCLI alert create -t 'Payment timeouts spike' --where "ErrorCode = 'PSP_TIMEOUT'" \
  --select "count(*) as timeouts" --group-by CardBrand --window 1m --having "timeouts > 5" \
  --notification-level Error --suppression-time 10m --notification-app appinstance-46 \
  -s http://seq -a "$ADMIN"
```

شغلنا برنامج .NET مرة أخرى، فوصل إلى الـ Webhook بعد نحو دقيقة الطلب التالي:

```bash
POST /seq-alert Seq alert: Alert condition triggered by Payment timeouts spike
Title: Payment timeouts spike
Results: [["time","CardBrand","timeouts"],["2026-10-23T01:40:49.3279324Z","mada",45]]
Link: http://localhost:80/#/alerts/alert-32
```

![شاشة Alerts في Seq: على اليسار خط زمني فيه ثلاث مرات انطلق فيها التنبيه Payment timeouts spike، وفي الوسط بطاقة التنبيه بلون أحمر ورسم صغير يرتفع في النهاية وتحته checked just now](https://arabroot.io/content/images/2026/10/seq-06-alerts.webp)

شاشة التنبيهات تعرض كل مرة انطلق فيها التنبيه، وآخر مرة فحص فيها الشرط

في التجربة أعلاه لاحظ التالي:

- النتيجة تحمل `mada` و`45`، أي أن الإشعار نفسه يخبرك بنوع البطاقة وعدد الحالات، بفضل `--group-by CardBrand`.
- الرابط بدأ بـ `http://localhost:80` لأن هذه التجربة كانت قبل أن نضبط `SEQ_API_CANONICALURI`، وبعد ضبطه يصبح الرابط بعنوانك الحقيقي.
- بدون `Body` أرسل التطبيق طلباً بجسم فارغ تماماً، أي إشعاراً لا يقول شيئاً، لذلك اكتب القالب دائماً.

والخطأ الذي وقعنا فيه هنا يستحق الذكر: كتبنا في البداية `MediaType=text/plain; charset=utf-8`، فانطلق التنبيه ولم يصل شيء إلى الـ Webhook. والسبب ظهر داخل Seq نفسه حدثاً بمستوى Error:

```bash
The event event-20f63d9a… could not be sent to Ops webhook.
System.FormatException: The format of value 'text/plain; charset=utf-8' is invalid.
```

أي أن التطبيق يريد نوع المحتوى بدون أي معامل، وعندما كتبناه `text/plain` وصل الإشعار. والدرس هنا: أنشئ إشارة على أخطاء Seq نفسه، لأن فشل الإشعار لا يظهر إلا هناك.

## ماذا قسنا عند تحميل 500 ألف حدث؟

حتى نعرف كيف يتصرف Seq مع حجم أقرب للواقع، ولدنا ملفاً فيه 500 ألف حدث JSON موزعة على 12 ساعة لثماني خدمات، منها 3% أخطاء و5% فيها نص عربي، وحجمه 152 MB. ثم أرسلناه إلى Seq بأداة Vector 0.60.0 بعد تحويل الحقول إلى صيغة CLEF، مرتين على جهاز اختبار بثمانية معالجات يشاركه أعمال أخرى: الأولى والجهاز تحت حمل ثقيل، والثانية وهو أهدأ. والجدول التالي يبين الاثنين، لذلك خذ الأرقام على أنها مقياس تقريبي:

| القياس                                            | التشغيل الأول (جهاز مشغول) | التشغيل الثاني (جهاز أهدأ)             |
| ------------------------------------------------- | -------------------------- | -------------------------------------- |
| زمن الوصول حتى يصبح الـ 500 ألف قابلاً للبحث      | 276 ثانية                  | 25 ثانية، أي نحو 20 ألف حدث في الثانية |
| حجم مجلد الأحداث /data/Stream بعد التحميل مباشرة  | 142 MB                     | 135 MB                                 |
| الحجم نفسه بعد نحو نصف ساعة من الفهرسة في الخلفية | 71 MB                      | 69 MB، أي أقل من نصف حجم ملف JSON      |
| ذاكرة Seq وهو فارغ / بعد التحميل والاستعلامات     | 145 MiB / 390 MiB          | 80 MiB / 1.2 GiB                       |
| البحث عن حدث واحد بـ request\_id                  | بين 0.4 و1.4 ثانية         | بين 0.2 و0.3 ثانية                     |
| عدد الأخطاء لكل خدمة group by service             | بين 0.2 و0.4 ثانية         | 0.7 ثانية أول مرة ثم 0.01 ثانية        |
| البحث عن timeout داخل الرسالة (3765 نتيجة)        | بين 0.2 و0.9 ثانية         | 0.2 ثانية أول مرة ثم 0.01 ثانية        |

والنتائج كلها صحيحة مقارنة بالملف الأصلي، فعدد الأخطاء لكل خدمة طابق ما حسبناه بسكربت Python، وكذلك عدد أسطر `timeout` والعبارة العربية. وفي الجدول ثلاث ملاحظات: الأولى أن زمن الإدخال تغير عشرة أضعاف بحسب حمل الجهاز، لذلك قس على سيرفرك قبل أن تعتمد على أي رقم. والثانية أن الحجم على القرص انخفض إلى النصف بعد الفهرسة، فلا تحكم على حاجتك من التخزين في أول ساعة. والثالثة أن الذاكرة ارتفعت إلى 1.2 GiB لأن Seq يستخدم ما يجده من ذاكرة لتسريع الاستعلامات المكررة، وهذا ما تقصده متطلبات النظام عندما تنصح بسيرفر مخصص له.

## الوصول عبر نطاق مع TLS

الواجهة يجب ألا تفتح على الإنترنت بدون HTTPS، لأن كلمة المرور والجلسة تمران فيها. والمثال التالي بـ Caddy، وهو يصدر الشهادة تلقائياً، ويفصل الواجهة عن الاستقبال على نطاقين فرعيين:

```nginx
seq.example.com {
    reverse_proxy 127.0.0.1:8080
}

seq-ingest.example.com {
    reverse_proxy 127.0.0.1:5341
}
```

بعد ذلك تكتب التطبيقات خارج السيرفر `https://seq-ingest.example.com` عنواناً للإرسال، ويبقى `SEQ_API_CANONICALURI` مساوياً لـ `https://seq.example.com`. وتفاصيل `reverse_proxy` في [توثيق Caddy](https://caddyserver.com/docs/caddyfile/directives/reverse%5Fproxy?ref=arabroot.io). ولم نختبر إصدار الشهادة هنا لأنه يحتاج نطاقاً عاماً، أما بقية الأجزاء فقد عملت محلياً كما وصفنا.

## التحقق من النجاح

- `curl -s http://127.0.0.1:8080/health` يرجع `"status":"healthy"`، و`docker compose ps` يعرض Seq بحالة `healthy`.
- إرسال حدث بدون مفتاح يرجع `401`، ومع مفتاح التطبيق يرجع `201`.
- `$SEQCLI query -q "select count(*) as events from stream group by Application"` يعرض كل تطبيق ترسل منه.
- تنبيه تجريبي بشرط سهل التحقق يصل إلى الـ Webhook أو البريد، ولا يظهر في Seq حدث `could not be sent to`.
- شاشة Storage تعرض سياسات الاحتفاظ التي أنشأتها.

## النسخ الاحتياطي والاستعادة

هنا نقطة يغفل عنها كثيرون: النسخة الاحتياطية في Seq تحفظ الإعدادات فقط، أي المستخدمين ومفاتيح API والإشارات والتنبيهات والتطبيقات، ولا تحفظ الأحداث، كما تنص [صفحة النسخ الاحتياطي](https://datalust.co/docs/backup-and-restore-docker?ref=arabroot.io). ويكتب Seq نسخة كل ليلة في `/data/Backups` ويحتفظ بآخر سبع. وفي المختبر كان حجم النسخة 15 KB:

```bash
docker exec seq ls -la /data/Backups
```

```bash
-rw-r--r-- 1 root root 15024 Oct 23 02:07 seq_20261023.seqbac
```

وقد يتساءل البعض: لماذا لا أشغل الأمر `seqsvr backup` متى شئت؟ جربناه والسيرفر يعمل، فرفض لأن مخزن الإعدادات مقفل:

```bash
Could not create backup: Flare native storage failed (IOError), error attempting to lock the store.
```

لذلك اعتمد على النسخة الليلية، ووقتها يضبط بالإعداد `BackupUtcTimeOfDay` بتوقيت UTC. وانسخ الملف خارج السيرفر ضمن نسخك الاحتياطية المعتادة. أما الاستعادة على Volume جديد فقد اختبرناها كما يلي، ويجب أن يكون الملف داخل `Backups/` في الـ Volume الجديد:

```bash
docker cp seq:/data/Backups/seq_20261023.seqbac .
docker volume create seq-restore
docker run --rm -v seq-restore:/data -v "$PWD:/in:ro" --entrypoint sh datalust/seq:2026.1.17182 \
  -c 'mkdir -p /data/Backups && cp /in/seq_20261023.seqbac /data/Backups/'
docker run --rm -v seq-restore:/data datalust/seq:2026.1.17182 \
  restore -y -s /data --secret-key="$SEQ_SECRET_KEY" --backup=/data/Backups/seq_20261023.seqbac
```

```bash
Restoration is complete. Any Seq app instances must be explicitly re-enabled.
```

ثم شغلنا Seq على الـ Volume المستعاد، فرجعت الإشارات الثلاث والتنبيه، وعمل مفتاح API القديم نفسه، وكان عدد الأحداث صفراً كما هو متوقع. ولاحظ أن تطبيقات الإشعار تعود معطلة حتى لا تنطلق التنبيهات من سيرفر الاختبار، فأعد تفعيلها بنفسك. وإذا أردت حفظ الأحداث نفسها فاستخدم تطبيق أرشفة مثل JSON Archive الذي تذكره الصفحة، أو انسخ الـ Volume كاملاً والسيرفر متوقف.

🛑

بدون `SEQ_SECRET_KEY` الصحيح لا تفيدك النسخة الاحتياطية شيئاً، وأمر الاستعادة يذكر أن الملف يجب أن يكون من الإصدار نفسه من Seq الذي تستعيد إليه، لذلك احفظ مع كل نسخة رقم الإصدار.

## التحديث إلى إصدار أحدث

1. تأكد أن لديك نسخة ليلية حديثة من `/data/Backups` ومعها المفتاح، لأن [صفحة التحديث](https://datalust.co/docs/upgrading?ref=arabroot.io) تطلب ذلك وتنصح بتجنب الرجوع لإصدار أقدم Downgrade.
2. اقرأ ملاحظات الإصدار، ثم غير الـ Tag في `docker-compose.yml` إلى الإصدار الجديد بالرقم الكامل، ولا تستخدم `latest`.
3. نفذ `docker compose pull seq && docker compose up -d --wait seq`، والـ Volume نفسه يبقى.
4. حدث تطبيقات Seq من Settings ثم Apps، لأن الصفحة تطلب تحديثها بعد تحديث السيرفر.

## مشكلات شائعة وحلولها

- **`401/Unauthorized. Please log in` من seqcli رغم صحة كلمة المرور:** إذا كان `SEQ_API_CANONICALURI` يبدأ بـ `https://` فإن Seq يضع على كوكي الجلسة Session Cookie العلامة `secure`، فلا يرسلها seqcli عندما يتصل بـ `http://seq` داخل الشبكة. ورأينا ذلك في ترويسة `Set-Cookie` نفسها. الحل أن تتصل بالعنوان الحقيقي عبر HTTPS، أو أن تستخدم مفتاح API بدل الدخول بالمستخدم.
- **الأحداث تصل من أي مكان:** فعل `RequireApiKeyForWritingEvents` كما في [مفاتيح API](#api-keys)، وإلا قبل Seq حتى المفاتيح المختلقة.
- **Vector يرسل والسيرفر يرد 401:** في Vector 0.60.0 أرسل الناقل الترويسة `X-Seq-ApiKey` بالنص الحرفي `${SEQ_API_KEY}` بدل قيمة المتغير، ورأينا ذلك عندما وجهناه إلى سيرفر يطبع الترويسات. فإذا ظهر الخطأ `Default retry strategy: Unauthorized` فتحقق مما يصل فعلاً، أو اكتب المفتاح في ملف الإعداد عند التشغيل.
- **مدخل GELF يكرر `invalid socket address syntax`:** اكتب `GELF_ADDRESS=udp://0.0.0.0:12201` بالبروتوكول في البداية.
- **الإشعار لا يصل والتنبيه انطلق:** ابحث في Seq عن `could not be sent to`، فسبب الفشل يكون هناك بالتفصيل.
- **البحث العربي لا يجد ما تتوقعه:** اختلاف الهمزات أو التاء المربوطة يكفي ليفشل البحث، فابحث بخاصية ثابتة بدل النص.
- **السطر يقول ERROR ومستواه info:** stdout لا يحمل مستوى، فاكتب السجل منظماً أو حلله في ناقل قبل Seq.

## الخلاصة

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

- Seq سيرفر واحد للسجلات المنظمة، يعمل خلال دقائق ويبحث بالخصائص بلغة تشبه SQL، وهو الأنسب لفريق .NET أو أي فريق يكتب أحداثاً منظمة ويريد أداة واحدة للبحث والتنبيه.
- ليس مفتوح المصدر، والخطة المجانية لمستخدم واحد، والفريق يبدأ من 1,090 دولاراً سنوياً لعشرة مستخدمين بحسب صفحة الأسعار في أكتوبر 2026.
- فعل `RequireApiKeyForWritingEvents` من اليوم الأول، فقد قبل Seq في المختبر أحداثاً بمفتاح مختلق قبل تفعيله.
- أي لغة ترسل إليه بصيغة CLEF عبر HTTP، وContainer قديم عبر GELF ولكن بدون مستويات، وOpenTelemetry عبر OTLP.
- البحث العربي يطابق الحروف كما هي، فاجعل الخصائص الثابتة هي ما تبحث به.
- 500 ألف حدث شغلت قرابة 70 MB بعد الفهرسة، والاستعلامات عليها رجعت في أقل من ثانية ونصف حتى والجهاز مشغول.
- النسخة الاحتياطية للإعدادات فقط وتحتاج المفتاح نفسه والإصدار نفسه، والأحداث تحتاج خطة منفصلة.

## تدريب عملي

- أنشئ تنبيهاً على متوسط `ElapsedMs` بدل العدد: `--select "mean(ElapsedMs) as avg_ms"` و`--having "avg_ms > 1500"`، ثم شغل البرنامج وراقب الـ Webhook.
- أرسل حدثاً بمستوى `Debug` بمفتاح التطبيق، ولاحظ أن Seq يرفضه بسبب `--minimum-level Information` في المفتاح، ثم غير الحد وأعد الإرسال.

وفي المقال القادم بإذن الله نقارن Seq وLoki وGraylog وOpenSearch بالأرقام على البيانات نفسها.

[![](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/log-pipeline-cover-cover.webp)دليلمسار السجل Log Pipeline من التطبيق إلى البحث والتنبيهكيف ينتقل السجل من التطبيق إلى المخزن عبر الناقل Shipper، وكيف تخفي البيانات الشخصية وتوجه الأخطاء وتحمي السجلات إذا توقفت الوجهة.](https://arabroot.io/articles/%D9%85%D8%B3%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D8%AC%D9%84-log-pipeline-%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%88%D8%A7%D9%84%D8%AA%D9%86%D8%A8%D9%8A%D9%87/)[![](https://arabroot.io/content/images/2026/10/loki-cover-cover.webp)دليلجمع السجلات Logs مركزياً مع Grafana Lokiكيف تشغل Grafana Loki مع Alloy وGrafana، وتختار التسميات Labels التي لا توقف الإدخال، ثم تبحث وتنبه وتحذف بلغة LogQL.](https://arabroot.io/articles/%D8%AC%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logs-%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A7-%D9%85%D8%B9-grafana-loki/)[![](https://arabroot.io/content/images/2026/10/graylog-cover-cover.webp)دليلالسجلات المركزية Logs مع Graylog وOpenSearchكيف تثبت Graylog فوق OpenSearch، وتوجه الرسائل بال Streams وتخفي بياناتها بال Pipelines وتنبه منها، وما الذي لا يجده بحثه العربي.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-logs-%D9%85%D8%B9-graylog-%D9%88-opensearch/)[![](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/)

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

- أكتوبر 2026: كتابة الدليل واختباره على Seq 2026.1.17182 مع seqcli 2026.1.02654 وseq-input-gelf 4.0.02323 وSerilog.Sinks.Seq 9.1.0 على .NET 10، ومراجعة صفحة الأسعار في 22 أكتوبر 2026.