لماذا المراقبة (Observability) أمر لا غنى عنه لتطبيقات Kotlin
\n\nتخيل: تطبيق Kotlin الخاص بك يعمل في بيئة الإنتاج، وفجأة يشتكي المستخدمون من أن الطلبات تستغرق 10 ثوانٍ. تذهب إلى السجلات — صمت. تظهر المقاييس استخدام 100% لوحدة المعالجة المركزية، لكنها لا تخبرك أي نقطة نهاية (endpoint) هي التي تبطئ. هل هذا مألوف؟
\n\nمرحبًا بك في عالم حيث المراقبة (observability) ليست خيارًا، بل ضرورة. على عكس المراقبة التقليدية التي تجيب ببساطة بـ "نعم/لا" على سؤال "هل النظام حي؟"، تتيح لك المراقبة طرح أسئلة لم تكن تعرفها من قبل. و Kotlin، بفضل إيجازها ونظامها البيئي القوي، مثالي لتبني هذه الثقافة.
\n\nفي هذه المقالة، سنشرح الركائز الثلاث للمراقبة — التتبع، المقاييس والسجلات — ونوضح كيفية دمجها مع Kotlin. لا ماء، فقط أمثلة كود حقيقية.
\n\nالتتبع: رحلة الطلب عبر الخدمات المصغرة
\n\nالتتبع الموزع (tracing) يشبه نظام GPS لطلبك. ترى من خلال أي الخدمات مر الطلب، كم من الوقت استغرق كل خطوة، وأين حدث التأخير. بالنسبة لـ Kotlin، أفضل صديق في هذا المجال هو OpenTelemetry (OTel).
\n\nإعداد OpenTelemetry في Kotlin
\n\nأضف التبعيات في ملف build.gradle.kts:
dependencies {\n implementation("io.opentelemetry:opentelemetry-api:1.36.0")\n implementation("io.opentelemetry:opentelemetry-sdk:1.36.0")\n implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.36.0")\n implementation("io.opentelemetry:opentelemetry-extension-kotlin:1.36.0")\n}\n\nالآن لننشئ متتبعًا بسيطًا:
\n\nimport io.opentelemetry.api.GlobalOpenTelemetry\nimport io.opentelemetry.api.trace.Span\nimport io.opentelemetry.api.trace.Tracer\nimport io.opentelemetry.context.Scope\n\nclass OrderService(private val tracer: Tracer = GlobalOpenTelemetry.getTracer("order-service")) {\n\n fun processOrder(orderId: String) {\n val span: Span = tracer.spanBuilder("processOrder")\n .setAttribute("order.id", orderId)\n .startSpan()\n\n span.use { // use() ستغلق span تلقائيًا\n try {\n // محاكاة العمل\n Thread.sleep(100)\n validateOrder(orderId)\n chargePayment(orderId)\n } catch (e: Exception) {\n span.recordException(e)\n span.setStatus(StatusCode.ERROR, "Order processing failed")\n throw e\n }\n }\n }\n\n private fun validateOrder(orderId: String) {\n val span: Span = tracer.spanBuilder("validateOrder")\n .setAttribute("order.id", orderId)\n .startSpan()\n\n span.use {\n Thread.sleep(50) // محاكاة التحقق\n }\n }\n\n private fun chargePayment(orderId: String) {\n val span: Span = tracer.spanBuilder("chargePayment")\n .setAttribute("order.id", orderId)\n .startSpan()\n\n span.use {\n Thread.sleep(150) // محاكاة الدفع\n }\n }\n}\n\nلاحظ استخدام span.use — هذه دالة امتداد (extension function) من opentelemetry-extension-kotlin، والتي تغلق span تلقائيًا بعد تنفيذ الكتلة. جمال Kotin في العمل!
نقل السياق عبر الكوروتينات
\n\nفي Kotlin، نستخدم الكوروتينات بنشاط. يتعامل OpenTelemetry معها بشكل ممتاز من خلال آلية السياق (Context):
\n\nimport kotlinx.coroutines.withContext\nimport io.opentelemetry.api.trace.Span\nimport io.opentelemetry.context.Context\n\nsuspend fun processOrderAsync(orderId: String) = withContext(Context.current()) {\n val span = tracer.spanBuilder("processOrderAsync").startSpan()\n span.use {\n // منطقك غير المتزامن هنا\n }\n}\n\nيكشف التتبع الصورة الكاملة: ترى أن 80% من الوقت يذهب إلى الدفع