Pourquoi l'observabilité est un must-have pour les applications Kotlin
\n\nImaginez : votre application Kotlin tourne en production, et soudain, les utilisateurs se plaignent que les requêtes prennent 10 secondes. Vous consultez les logs — silence. Les métriques montrent 100% CPU, mais ne disent pas quel endpoint exact ralentit. Cela vous dit quelque chose ?
\n\nBienvenue dans un monde où l'observabilité n'est pas une option, mais une nécessité. Contrairement à la surveillance traditionnelle, qui répond simplement « oui/non » à la question « le système est-il vivant ? », l'observabilité permet de poser des questions auxquelles vous n'aviez pas pensé auparavant. Et Kotlin, avec sa concision et son écosystème puissant, est parfaitement adapté à l'adoption de cette culture.
\n\nDans cet article, nous allons décomposer les trois piliers de l'observabilité — tracing, métriques et logs — et montrer comment les associer à Kotlin. Pas de blabla, uniquement des exemples de code concrets.
\n\nTracing : le voyage de la requête à travers les microservices
\n\nLe tracing distribué est comme un GPS pour votre requête. Vous voyez par quels services la requête est passée, combien de temps chaque étape a pris et où le retard s'est produit. Pour Kotlin, le meilleur allié dans ce domaine est OpenTelemetry (OTel).
\n\nConfiguration d'OpenTelemetry dans Kotlin
\n\nAjoutez les dépendances dans 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\nCréons maintenant un traceur simple :
\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() fermera automatiquement le span\n try {\n // Simulation de travail\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) // Simulation de validation\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) // Simulation de paiement\n }\n }\n}\n\nRemarquez span.use — c'est une fonction d'extension provenant de opentelemetry-extension-kotlin, qui ferme automatiquement le span après l'exécution du bloc. La beauté de Kotlin en action !
Transmission contextuelle via les coroutines
\n\nDans Kotlin, nous utilisons activement les coroutines. OpenTelemetry s'entend parfaitement avec elles grâce au mécanisme de Contexte :
\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 // Votre logique asynchrone\n }\n}\n\nLe tracing révèle une image complète : vous voyez que 80% du temps est consacré au paiement