انتقل إلى المحتوى الرئيسي

معالجة الأخطاء

ينفّذ AuroraSOC طبقات متعددة من معالجة الأخطاء لضمان المرونة في بيئة أمنية حرِجة.

فلسفة معالجة الأخطاء

تنسيق استجابة أخطاء API

تعيد جميع أخطاء API بنية JSON متسقة:

{
"detail": "Human-readable error message"
}

بالنسبة إلى أخطاء التحقق (Pydantic)، يعيد FastAPI ما يلي:

{
"detail": [
{
"loc": ["body", "severity"],
"msg": "String should match pattern '^(critical|high|medium|low|info)$'",
"type": "string_pattern_mismatch"
}
]
}

مرجع رموز الأخطاء

الرمزالمعنىوقت الحدوث
400طلب غير صالحإدخال غير صالح، فشل في التحقق
401غير مصرحJWT مفقود/منتهي الصلاحية، أو مفتاح API غير صالح
403ممنوعمصادقة صحيحة لكن الصلاحيات غير كافية
404غير موجودالمورد غير موجود
409تعارضمورد مكرر (مثل تكرار نوع+قيمة IOC)
429طلبات كثيرة جدًاتم تجاوز حد المعدل
503الخدمة غير متاحةتعطل قاعدة البيانات، وضع متدهور
500خطأ خادم داخلياستثناء غير مُعالَج

التعامل مع عدم توفر قاعدة البيانات

أكثر سيناريوهات الأخطاء حساسية هو توقف PostgreSQL. يتعامل AuroraSOC مع ذلك بسلاسة:

class DatabaseUnavailable(Exception):
"""Raised when PostgreSQL is not reachable."""
pass

@app.exception_handler(DatabaseUnavailable)
async def _handle_db_unavailable(request, exc):
return JSONResponse(
status_code=503,
content={"detail": "Database unavailable — running in degraded mode"},
)

في الوضع المتدهور، تخدم API البيانات من مخازن داخل الذاكرة مملوءة ببيانات تجريبية. يضمن ذلك بقاء لوحة التحكم عاملة للمراقبة حتى أثناء صيانة قاعدة البيانات.

نمط قاطع الدارة

يستخدم المنسق قاطع دارة عند الإرسال إلى خدمات الوكلاء:

class CircuitBreaker:
def __init__(self, failure_threshold: int = 3, reset_timeout: float = 30.0):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.reset_timeout = reset_timeout
self.state = "closed" # closed | open | half-open
self.last_failure_time = 0

async def call(self, func, *args, **kwargs):
if self.state == "open":
if time.time() - self.last_failure_time > self.reset_timeout:
self.state = "half-open"
else:
raise CircuitBreakerOpen("Agent service unavailable")

try:
result = await func(*args, **kwargs)
if self.state == "half-open":
self.state = "closed"
self.failure_count = 0
return result
except Exception:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "open"
raise

إعادة المحاولة مع تزايد أُسّي لفترة الانتظار

يتضمن إرسال المهام إلى الوكلاء إعادة محاولة تلقائية:

async def dispatch_with_retry(self, agent_type, prompt, max_retries=3):
for attempt in range(max_retries):
try:
return await self.circuit_breaker.call(
self._dispatch, agent_type, prompt
)
except Exception:
if attempt == max_retries - 1:
raise
wait = 2 ** attempt # 1s, 2s, 4s
await asyncio.sleep(wait)

أخطاء تحديد معدل الطلبات

عند تجاوز حدود المعدل:

{
"detail": "Rate limit exceeded. Try again in 42 seconds."
}

تستخدم حدود المعدل عدادات النافذة المنزلقة في Redis:

الفئةالحدالنافذة
API العامة100 طلبدقيقة واحدة
التحقيقات10 طلباتدقيقة واحدة
تنفيذ الـ Playbook5 طلباتدقيقة واحدة

التسجيل المهيكل للأخطاء

تُسجَّل جميع الأخطاء عبر structlog مع سياق التتبّع الخاص بـ OpenTelemetry:

logger.error(
"agent_dispatch_failed",
agent_type="threat_hunter",
error=str(e),
case_id=case_id,
attempt=attempt,
trace_id=get_current_span().get_span_context().trace_id,
)

ينتج عن ذلك سجلات JSON مهيكلة يمكن ربطها بآثار التتبّع في Jaeger:

{
"event": "agent_dispatch_failed",
"agent_type": "threat_hunter",
"error": "Connection refused",
"case_id": "550e8400-...",
"attempt": 2,
"trace_id": "abc123...",
"timestamp": "2024-01-15T10:30:00Z",
"level": "error"
}

معالجة أخطاء WebSocket

تتعامل اتصالات WebSocket مع الأخطاء بصمت لتجنّب تعطيل العملاء الآخرين:

async def broadcast(self, data: dict) -> None:
dead: list[str] = []
for cid, ws in self.active.items():
try:
await ws.send_json(data)
except Exception:
dead.append(cid) # Mark for removal
for cid in dead:
self.active.pop(cid, None) # Clean up dead connections

عمليات الإرسال الفاشلة لا تُطلق استثناءً؛ بل تُزال الوصلة الميتة بصمت ويستمر البث إلى العملاء السليمين.

معالجة أخطاء ناقل الأحداث

تستخدم مستهلكات Redis Streams معالجة أخطاء قائمة على الإقرار:

async for msg_id, data in consumer.consume():
try:
await handler(data)
await consumer.ack(msg_id) # Success: acknowledge
except Exception:
pass # Message stays in pending — will be re-delivered

تبقى الرسائل غير المُقَرّ بها في قائمة الإدخالات المعلقة (PEL) ضمن Redis Stream، وتُعاد تسليمها بعد انتهاء مهلة الرؤية الخاصة بمجموعة المستهلك.