◀ العودة إلى المدونة
DevOps

مراقبة التطبيقات باستخدام Sentry

نشر في 20 Apr 2024· 7 min قراءة
#Sentry#Monitoring#PHP

Sentry: لا تفوّت أي خطأ بعد اليوم

Sentry أداة لمراقبة الأخطاء تلتقط الاستثناءات (exceptions) في تطبيقك وتجمّعها وتنبّه إليها في الوقت الفعلي.

من دون أداة من هذا النوع، يُكتشف الخطأ في الإنتاج بإحدى طريقتين: إما أن يبلّغ عنه مستخدم، أو أن يقرأ أحدهم السجلات في نهاية المطاف. وفي الحالتين، يغيب معظم السياق. يغيّر Sentry المعادلة: يُرسَل كل استثناء مع مكدس الاستدعاءات (stack trace) الكامل، وطلب HTTP، والمستخدم المعني، والإصدار المنشور، والبيئة. وتُجمَّع التكرارات المتطابقة في issue واحد، مما يتيح لك أن ترى فوراً أن خطأً ما يطال مئات المستخدمين منذ آخر عملية نشر، بدلاً من تلقي مئات الرسائل الإلكترونية.

يتوفر Sentry كخدمة SaaS وكنسخة مستضافة ذاتياً. والـ SDK والإعدادات المعروضة هنا متطابقة في الحالتين؛ ولا يتغير سوى الـ DSN.

التثبيت في Symfony

composer require sentry/sentry-symfony

تثبّت الحزمة الـ SDK الخاص بـ PHP والـ bundle الخاص بـ Symfony. ومع Symfony Flex، تنشئ الوصفة (recipe) ملف الإعدادات وتضيف المتغير SENTRY_DSN إلى الملف .env. الـ DSN هو العنوان الذي يرسل إليه الـ SDK الأحداث؛ وتجده في إعدادات مشروع Sentry. اتركه فارغاً في بيئة التطوير: من دون DSN، لا يُرسَل أي حدث.

# .env
SENTRY_DSN=
APP_VERSION=dev

# .env.local on the production server (or environment variables)
SENTRY_DSN=https://publicKey@o0.ingest.sentry.io/0
APP_VERSION=1.4.2

الإعدادات

# config/packages/sentry.yaml
sentry:
    dsn: '%env(SENTRY_DSN)%'
    options:
        environment: '%kernel.environment%'
        release: '%env(APP_VERSION)%'
        traces_sample_rate: 0.2
        profiles_sample_rate: 0.1

لكل خيار دور محدد:

  • environment يفصل أخطاء الإنتاج وما قبل الإنتاج والاختبار في الواجهة، لتسهيل التصفية والتنبيه على الإنتاج فقط.
  • release يربط كل خطأ بالإصدار المنشور. وهذا ما يتيح لـ Sentry الإشارة إلى أن خطأً ظهر مع إصدار معين، أو أن خطأً اعتُبر محلولاً قد عاد للظهور.
  • traces_sample_rate يحدد نسبة الطلبات التي يُسجَّل لها أثر أداء (trace): 0.2 تعني 20٪. أما الأخطاء فتُرسَل كلها دائماً. وعلى موقع كثيف الزيارات، تكفي قيمة منخفضة وتحافظ على حصتك (quota).
  • profiles_sample_rate يفعّل التنميط (profiling) لجزء من الطلبات المتتبَّعة. ويتطلب امتداد PHP excimer على الخادم؛ ومن دونه لا يكون للخيار أي أثر.

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

# config/packages/prod/sentry.yaml
sentry:
    options:
        ignore_exceptions:
            - Symfony\Component\HttpKernel\Exception\NotFoundHttpException
            - Symfony\Component\Security\Core\Exception\AccessDeniedException

سياق المستخدم

معرفة أن خطأً قد وقع أمر مفيد؛ ومعرفة من يطاله أكثر فائدة. يضيف هذا المستمع (listener) المستخدمَ المسجَّل دخوله إلى الـ scope الخاص بـ Sentry مع كل طلب:

use Sentry\State\Scope;

class SentryUserListener
{
    #[AsEventListener(event: KernelEvents::REQUEST)]
    public function onRequest(RequestEvent $event): void
    {
        $user = $this->security->getUser();
        if ($user) {
            \Sentry\configureScope(function (Scope $scope) use ($user): void {
                $scope->setUser([
                    'id' => $user->getId(),
                    'email' => $user->getEmail(),
                ]);
            });
        }
    }
}

انتبه إلى اللائحة العامة لحماية البيانات (RGPD/GDPR): البريد الإلكتروني بيانات شخصية ستُخزَّن لدى Sentry. إذا كان المعرّف (ID) كافياً للعثور على الحساب في لوحة الإدارة، فأرسله وحده. وتحقق أيضاً من الخيار send_default_pii، المعطَّل افتراضياً، الذي يتحكم في الإرسال التلقائي لمعلومات مثل عنوان IP.

إثراء خطأ مُلتقَط يدوياً

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

use Sentry\State\Scope;

try {
    $this->paymentGateway->charge($order);
} catch (PaymentException $e) {
    \Sentry\withScope(function (Scope $scope) use ($e, $order): void {
        $scope->setTag('payment.provider', $order->getProvider());
        $scope->setContext('order', [
            'id' => $order->getId(),
            'amount' => $order->getAmount(),
        ]);
        \Sentry\captureException($e);
    });

    throw new OrderNotPaidException($order, previous: $e);
}

تنشئ withScope نطاقاً مؤقتاً: لا ينطبق الوسم والسياق إلا على هذا الحدث، دون تلويث الأخطاء اللاحقة. الوسوم (tags) مفهرسة وتُستخدم لتصفية الـ issues أو تجميعها؛ أما السياق (context) فيُعرض ببساطة في تفاصيل الحدث.

التنبيهات والإشعارات

  • اضبط تنبيهات Slack للأخطاء الجديدة
  • حدّد عتبات لحجم الأخطاء
  • استخدم آثار الأداء لتحديد الاختناقات
  • ادمج Sentry مع سير عمل GitHub لمتابعة الإصلاحات

الفخ الكلاسيكي هو إرهاق التنبيهات: إذا أطلق كل خطأ إشعاراً، ينتهي الفريق بتجاهلها جميعاً. أنصح بالتنبيه على الـ issues الجديدة وحالات التراجع (regressions) في الإنتاج، وتخصيص تنبيهات الحجم للمسارات الحرجة مثل الدفع أو المصادقة.

Performance Monitoring

إلى جانب الأخطاء، يقيس Sentry مدة الطلبات ومراحلها. يتتبّع الـ bundle الخاص بـ Symfony تلقائياً طلبات HTTP، وبحسب الإعدادات، استعلامات Doctrine والـ cache واستدعاءات HTTP الصادرة. ولعملية وظيفية محددة، يمكنك إنشاء transaction خاصة بك باستخدام كائنات السياق في الإصدار 4 من SDK الخاص بـ PHP، وإحاطة كل مرحلة بـ span عبر الدالة \Sentry\trace():

use Sentry\SentrySdk;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\TransactionContext;

$transaction = \Sentry\startTransaction(
    TransactionContext::make()
        ->setName('process-order')
        ->setOp('task')
);
SentrySdk::getCurrentHub()->setSpan($transaction);

try {
    $orders = \Sentry\trace(
        fn () => $this->orderRepository->findPending(),
        SpanContext::make()->setOp('db.query')->setDescription('Pending orders'),
    );
    // ... process the orders
} finally {
    $transaction->finish();
}

تضمن كتلة finally إغلاق الـ transaction حتى لو وقع استثناء: فالـ transaction التي لا تُنهى لا تُرسَل أبداً.

ربط الأخطاء بعمليات النشر

يكتسب الخيار release قيمته الكاملة عندما يُصرَّح بالإصدار أيضاً في Sentry مع الـ commits الخاصة به. عندها يستطيع Sentry اقتراح الـ commit المسؤول على الأرجح عن خطأ ما. باستخدام sentry-cli، ضمن خط أنابيب النشر:

export SENTRY_AUTH_TOKEN=...   # API token, stored in the CI secrets
export SENTRY_ORG=my-organization
export SENTRY_PROJECT=my-project
VERSION="$(git rev-parse --short HEAD)"

sentry-cli releases new "$VERSION"
sentry-cli releases set-commits "$VERSION" --auto
sentry-cli releases finalize "$VERSION"

يجب تمرير القيمة نفسها إلى التطبيق عبر APP_VERSION، وإلا فلن تُربط الأخطاء بالإصدار الصحيح.

قائمة التحقق قبل الإنتاج

  • تعريف الـ DSN فقط في البيئات التي يجب أن ترسل الأخطاء
  • تعبئة environment وrelease عند كل عملية نشر
  • تجاهل الاستثناءات المتوقعة (404، 403)
  • تقليص البيانات الشخصية إلى الحد الأدنى الضروري
  • نسبة أخذ عينات للآثار ملائمة لحجم الزيارات والحصة
  • تنبيهات مركّزة على الأخطاء الجديدة وحالات التراجع

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