Por qué la observabilidad es imprescindible para las aplicaciones Kotlin
\n\nImagina: tu aplicación Kotlin funciona en producción y, de repente, los usuarios se quejan de que las solicitudes tardan 10 segundos. Revisas los registros: silencio. Las métricas muestran un 100% de CPU, pero no indican qué endpoint específico está ralentizando. ¿Te suena familiar?
\n\nBienvenido al mundo donde la observabilidad no es una opción, sino una necesidad. A diferencia del monitoreo tradicional, que simplemente responde «sí/no» a la pregunta «¿el sistema está vivo?», la observabilidad permite hacer preguntas que antes ni siquiera imaginabas. Y Kotlin, con su concisión y su potente ecosistema, es ideal para adoptar esta cultura.
\n\nEn este artículo, analizaremos los tres pilares de la observabilidad —trazado, métricas y registros— y mostraremos cómo integrarlos con Kotlin. Sin rodeos, solo ejemplos de código reales.
\n\nTrazado: el viaje de una solicitud a través de los microservicios
\n\nEl trazado distribuido (tracing) es como un GPS para tu solicitud. Ves por qué servicios pasó la solicitud, cuánto tiempo tomó cada paso y dónde ocurrió la demora. Para Kotlin, el mejor aliado en esto es OpenTelemetry (OTel).
\n\nConfiguración de OpenTelemetry en Kotlin
\n\nAñade las dependencias en 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\nAhora creamos un trazador 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() cierra automáticamente el span\n try {\n // Simulación de trabajo\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) // Simulación de validación\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) // Simulación de pago\n }\n }\n}\n\nObserva span.use — es una función de extensión de opentelemetry-extension-kotlin que cierra automáticamente el span después de ejecutar el bloque. ¡La belleza de Kotlin en acción!
Transmisión de contexto a través de corrutinas
\n\nEn Kotlin usamos activamente corrutinas. OpenTelemetry se lleva muy bien con ellas mediante el mecanismo de 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 // Tu lógica asíncrona\n }\n}\n\nEl trazado revela el panorama completo: ves que el 80% del tiempo se destina al pago