DevOps والتشغيل الأدلة التقنية

سجلات منظمة Structured Logs تبحث فيها بالخصائص مع Seq

كيف تشغل Seq وترسل إليه السجلات من .NET وأي لغة أخرى، وتبحث فيها بالخصائص، وتضبط الاحتفاظ والتنبيهات، مع ما يسمح به الترخيص المجاني.

سجلات منظمة Structured Logs تبحث فيها بالخصائص مع Seq

في دليل السجلات للمطورين كتبنا السجل حدثاً منظماً له حقول، وفي مسار السجل Log Pipeline نقلناه من التطبيق إلى مخزن مركزي. وبقي سؤال يسأله كل فريق صغير، خصوصاً فرق .NET: أين نضع هذه الأحداث بحيث نبحث فيها بالحقول كما نبحث في جدول، ونبني منها تنبيهاً، دون أن نشغل مجموعة كاملة من Elasticsearch وKibana أو نتعلم لغة استعلام جديدة؟ وأحد أشهر الإجابات هو Seq، وهو سيرفر سجلات من شركة Datalust بني من البداية للسجلات المنظمة Structured Logs، ويستقبل الأحداث من .NET عبر Serilog ومن أي لغة أخرى عبر HTTP.

وقبل أن نبدأ، لاحظ أن Seq ليس الخيار الوحيد: فهناك Grafana Loki الذي يخزن النص بأقل تكلفة ويفهرس الملصقات Labels فقط، وGraylog الذي يجمع السجلات من الأجهزة والسيرفرات فوق OpenSearch، ومجموعة ELK وOpenSearch للبحث النصي الكامل على حجم كبير. أما 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
كل الطرق تنتهي إلى حدث له خصائص، ومنها يبنى البحث والإشارات والتنبيهات

والفكرة التي يقوم عليها 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، وفي الأسفل تحذير من بناء الرسالة بدمج النصوص
القالب يجمع الأحداث من النوع نفسه، والخصائص هي ما تبحث به وتحسب عليه

وبالتالي تستطيع أن تبحث بـ ErrorCode = 'PSP_TIMEOUT' وأن تحسب متوسط ElapsedMs لكل نوع بطاقة، بدل أن تبحث عن كلمة داخل نص. وقد شرحنا لماذا يكتب السجل منظماً من الأصل في سجلات تساعدك فعلاً، فلن نعيده هنا.

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

نقاط القوة:

  • سيرفر واحد Single Binary: Container واحد ومجلد بيانات واحد، ويعمل خلال دقيقة، وهذا ما رأيناه في المختبر.
  • تكامل .NET: المكتبة Serilog.Sinks.Seq سطر واحد في الإعداد، والقوالب والاستثناءات Exceptions تصل كما هي.
  • لغة استعلام قريبة من SQL: select count(*) from stream where … group by … يعرفها أي مطور، مع تعابير بحث Search Expressions بسيطة للبحث اليومي.
  • تنبيهات وإشارات مدمجة: لا تحتاج أداة منفصلة للتنبيه، وتصل التنبيهات إلى Email أو Slack أو أي Webhook عبر تطبيقات Seq Apps.

نقاط الضعف:

  • ليس مفتوح المصدر: Seq منتج تجاري، والنسخة المجانية لمستخدم واحد فقط، وأي فريق يحتاج ترخيصاً مدفوعاً كما سيأتي.
  • التوسع Scaling له حدود: يعمل جيداً على سيرفر واحد قوي، والتجميع Clustering متاح فقط في خطة Datacenter، لذلك لا يناسب من يجمع عشرات التيرابايت يومياً.
  • البحث النصي الكامل محدود: البحث عن نص داخل الرسالة يعمل، ولكنه ليس محرك بحث نصي مثل OpenSearch يحلل الكلمات Analyzers ويطبع الحروف العربية Normalization، وهذا ما ظهر لنا في البحث بالعربية.
  • يستهلك ما يجده من موارد: توصي متطلبات النظام بتشغيله على سيرفر مخصص، لأنه يحاول استخدام ما يتاح له من الذاكرة والمعالج.

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

Seq يعمل بترخيص تجاري، والقبول به شرط للتشغيل (ACCEPT_EULA=Y). والجدول التالي من صفحة الأسعار كما رأيناها في 22 أكتوبر 2026، والأسعار بالدولار سنوياً دون الضريبة:

الخطةالسعر السنويالمستخدمونالتخزينعدد السيرفرات
Individual0 دولارمستخدم واحد50 GBغير محدد
Team1,090 دولارحتى 10100 GBسيرفر واحد
Standard2,990 دولاربلا حدبلا حدحتى 3
Datacenter10,990 دولاربلا حدبلا حدبلا حد، مع التجميع Clustering

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

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

المتطلبات

  • السيرفر: تذكر متطلبات Seq أن الحمل الخفيف (أحداث قليلة في الثانية واحتفاظ 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 الذي يحمي الحقول الحساسة والنسخ الاحتياطية:

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:

SEQ_ADMIN_HASH=QG4EuGYq6Ecp...الهاش-كاملاً
SEQ_SECRET_KEY=hyqBdQRa8nEN...المفتاح-كاملاً=
SEQ_PUBLIC_URL=https://seq.example.com
SEQ_GELF_KEY=
🛑
احفظ SEQ_SECRET_KEY خارج السيرفر في مدير كلمات المرور. فالنسخة الاحتياطية من Seq مشفرة بهذا المفتاح، ولا يمكن استعادتها بدونه، وهذا ما تنص عليه صفحة النسخ الاحتياطي في Docker.

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

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.
  • SEQ_API_CANONICALURI هو العنوان الذي يضعه Seq في روابط التنبيهات، وبدونه وصلتنا روابط تبدأ بـ http://localhost:80 لا تفتح عند أحد. ولهذا الإعداد أثر آخر سوف تراه في المشكلات الشائعة.
  • المنفذان مربوطان على 127.0.0.1 فقط، لأن الواجهة ستمر عبر الـ Reverse Proxy، وتوصي Datalust بفصل منفذ الاستقبال 5341 عن منفذ الواجهة حتى تفتح للتطبيقات الاستقبال وحده.
  • الـ Healthcheck يستخدم /health الذي يجيب بـ {"status":"healthy"} عندما يكون السيرفر جاهزاً، ولذلك ينتظر مدخل GELF حتى يصبح Seq جاهزاً.

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

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

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

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

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

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

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

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

$SEQCLI apikey list -s http://seq -a "$ADMIN"
apikey-35 lab-admin
apikey-37 orders-api
apikey-default Unauthenticated

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

201 {"MinimumLevelAccepted":null}
201 {"MinimumLevelAccepted":null}

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

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

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

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

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

<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:

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. لا تكتب $"Checkout {orderId} failed" لأن القيمة تذوب في النص فلا تصبح خاصية.
  • Enrich.WithProperty يضيف Application لكل حدث، وLogContext.PushProperty يضيف RequestId لكل ما يكتب داخل الطلب، وهذا ما يربط أحداث الطلب الواحد.
  • Log.CloseAndFlush() ضروري في البرامج القصيرة، لأن Serilog يرسل على دفعات Batches، فإذا انتهى البرنامج قبل الدفعة الأخيرة ضاعت.
  • عنوان الإرسال هو المنفذ 5341، وهو منفذ الاستقبال، أما الواجهة فلا يحتاجها التطبيق.

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

[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:

{"@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، وهي سطر JSON لكل حدث فيه حقول تبدأ بـ @: @t للوقت وهو الحقل الوحيد الإلزامي، و@m للرسالة أو @mt للقالب، و@l للمستوى، و@x للاستثناء، كما في صفحة إرسال الأحداث. والمثال التالي بمكتبة 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())

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

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. فإذا كان تطبيقك يستخدم OpenTelemetry SDK فوجهه إلى Seq مباشرة بالمفتاح نفسه في الترويسة.

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

لنفرض أن لديك مهمة ليلية قديمة لا تستطيع تعديل كودها، وكل ما تفعله أنها تطبع أسطراً إلى stdout. هنا يأتي دور مشغل السجلات GELF في Docker: Docker نفسه يرسل كل سطر بصيغة GELF عبر UDP، ويستقبله الـ Container seq-input-gelf ويحوله إلى Seq عبر HTTP. ننشئ له مفتاحاً، ونضعه في SEQ_GELF_KEY داخل .env، ثم نشغله:

docker compose up -d seq-gelf

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

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، وهذا أحدها:

{"@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 كما شرحنا في مسار السجل.

⚠️
صفحة 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 يرجع جدولاً من الأرقام. والتعبير يكتب في مربع البحث كما هو:

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
البحث بخاصية واحدة يرجع كل أحداث انتهاء المهلة، وكلها لبطاقة mada

أما الاستعلام فيبدأ بـ select ويستخدم stream مصدراً للأحداث، كما في صفحة SQL Queries. والمثال التالي يحسب عدد العمليات ومتوسط زمنها وأعلى زمن لكل نوع بطاقة:

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 عملية):

"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
الاستعلام نفسه في الواجهة بعد ثلاث مرات تشغيل للبرنامج، ومعه أزرار التحويل إلى رسم بياني

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

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

$SEQCLI search -s http://seq -a "$ADMIN" -f '"انتهاء المهلة"' -c 3
[2026-10-23T01:06:47.1520000+00:00 WRN] فشل الدفع بسبب انتهاء المهلة للطلب ord_0044
شاشة Events في Seq وفي مربع البحث عبارة انتهاء المهلة بين علامتي تنصيص، والنتيجة حدث واحد بلون التحذير: فشل الدفع بسبب انتهاء المهلة للطلب ord_0044
البحث بعبارة عربية يجد الحدث الذي أرسله سكربت Python

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

الإشارات Signals

الإشارة Signal فلتر محفوظ باسم، تختاره بضغطة من القائمة على يمين الشاشة بدل أن تكتب التعبير كل مرة. وتجمع Seq الإشارات بطريقة ذكية كما في صفحة الإشارات: إذا اخترت إشارتين من مجموعة واحدة (تطبيقين مثلاً) أخذ ما يطابق أياً منهما، وإذا اخترت إشارتين من مجموعتين مختلفتين (تطبيق وبيئة) أخذ ما يطابقهما معاً، وتذكر الصفحة أن Seq يبني فهارس Indexes للإشارات فتصبح الاستعلامات التي تستخدمها أسرع على البيانات الكبيرة. أنشأنا ثلاث إشارات بـ seqcli:

$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"
signal-38 orders-api
signal-39 Payment timeouts
signal-40 Debug noise

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

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

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

$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
السياستان في شاشة 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، كما في صفحة التنبيهات. وسوف نرسل الإشعار إلى Webhook بسيط، فنثبت أولاً تطبيق Seq.App.HttpRequest وننشئ منه نسخة Instance تشير إلى عنوان الـ Webhook:

$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 في دقيقة واحدة، مع إيقاف التكرار عشر دقائق:

$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 بعد نحو دقيقة الطلب التالي:

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
شاشة التنبيهات تعرض كل مرة انطلق فيها التنبيه، وآخر مرة فحص فيها الشرط

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

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

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

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 MB135 MB
الحجم نفسه بعد نحو نصف ساعة من الفهرسة في الخلفية71 MB69 MB، أي أقل من نصف حجم ملف JSON
ذاكرة Seq وهو فارغ / بعد التحميل والاستعلامات145 MiB / 390 MiB80 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، وهو يصدر الشهادة تلقائياً، ويفصل الواجهة عن الاستقبال على نطاقين فرعيين:

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. ولم نختبر إصدار الشهادة هنا لأنه يحتاج نطاقاً عاماً، أما بقية الأجزاء فقد عملت محلياً كما وصفنا.

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

  • 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 والإشارات والتنبيهات والتطبيقات، ولا تحفظ الأحداث، كما تنص صفحة النسخ الاحتياطي. ويكتب Seq نسخة كل ليلة في /data/Backups ويحتفظ بآخر سبع. وفي المختبر كان حجم النسخة 15 KB:

docker exec seq ls -la /data/Backups
-rw-r--r-- 1 root root 15024 Oct 23 02:07 seq_20261023.seqbac

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

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

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

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
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 ومعها المفتاح، لأن صفحة التحديث تطلب ذلك وتنصح بتجنب الرجوع لإصدار أقدم 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، وإلا قبل 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 بالأرقام على البيانات نفسها.

دليلالسجلات Logging للمطورين: ماذا تسجل وماذا لا تسجلكيف تكتب سجلات Logs تساعدك على فهم ما حدث في الإنتاج، وما الذي لا يدخل السجل أبداً، وكيف تجمعها في مكان واحد.دليلسجلات تساعدك فعلاً: من «حدث خطأ» إلى سبب المشكلةكيف تكتب سجلات تجيب عن أسئلة الحادثة، بتحقيق في عطل حقيقي بسجلات سيئة ثم بحدث واسع Wide Event لكل طلب، مع قياس الفرق.دليلمسار السجل Log Pipeline من التطبيق إلى البحث والتنبيهكيف ينتقل السجل من التطبيق إلى المخزن عبر الناقل Shipper، وكيف تخفي البيانات الشخصية وتوجه الأخطاء وتحمي السجلات إذا توقفت الوجهة.دليلجمع السجلات Logs مركزياً مع Grafana Lokiكيف تشغل Grafana Loki مع Alloy وGrafana، وتختار التسميات Labels التي لا توقف الإدخال، ثم تبحث وتنبه وتحذف بلغة LogQL.دليلالسجلات المركزية Logs مع Graylog وOpenSearchكيف تثبت Graylog فوق OpenSearch، وتوجه الرسائل بال Streams وتخفي بياناتها بال Pipelines وتنبه منها، وما الذي لا يجده بحثه العربي.دليلOpenSearch: البديل المفتوح لـ Elasticsearch وقصة الترخيصلماذا ولد OpenSearch من 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.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.