Pourquoi l'observabilité est indispensable pour les applications Kotlin
\n\nImaginez : votre application Kotlin tourne en production, et soudainement, les utilisateurs se plaignent que les requêtes prennent 10 secondes. Vous consultez les logs — silence. Les métriques montrent 100% du CPU, mais ne vous disent pas quel endpoint spécifique ralentit. Cela vous semble familier ?
\n\nBienvenue dans le 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é vous permet de poser des questions dont vous ne connaissiez même pas l'existence. Et Kotlin, avec sa concision et son écosystème puissant, est idéalement adapté pour implémenter cette culture.
\n\nDans cet article, nous allons détailler les trois piliers de l'observabilité — le tracing, les métriques et les logs — et montrer comment les intégrer avec Kotlin. Pas de blabla, seulement des exemples de code concrets.
\n\nTracing : Le parcours d'une requête à travers les microservices
\n\nLe tracing distribué est comme un GPS pour votre requête. Vous pouvez voir par quels services la requête est passée, combien de temps chaque étape a pris, et où le délai s'est produit. Pour Kotlin, le meilleur allié pour cela 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 // Simuler le 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) // Simuler la 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) // Simuler le paiement\n }\n }\n}\n\nRemarquez span.use — c'est une fonction d'extension de opentelemetry-extension-kotlin qui ferme automatiquement le span après l'exécution du bloc. La beauté de Kotlin en action !
Propagation de contexte via les coroutines
\n\nDans Kotlin, nous utilisons activement les coroutines. OpenTelemetry s'intègre 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 l'image complète : vous pouvez voir que 80% du temps est consacré au paiement