Colocar codigo em producao e so o comeco. O verdadeiro desafio e saber o que esta acontecendo com sua aplicacao em tempo real. Observabilidade, composta por logs, metricas e tracing distribuido, e o que permite entender, diagnosticar e resolver problemas rapidamente. Vamos ver como implementar observabilidade completa em aplicacoes Kotlin.
Os Tres Pilares da Observabilidade
1. Logs
Logs registram eventos discretos que acontecem na aplicacao. Sao a forma mais basica de observabilidade, mas quando bem estruturados, sao extremamente poderosos.
// Logging estruturado com SLF4J e Kotlin
import org.slf4j.LoggerFactory
class PedidoService(
private val pedidoRepository: PedidoRepository,
private val pagamentoGateway: PagamentoGateway
) {
private val logger = LoggerFactory.getLogger(PedidoService::class.java)
suspend fun processarPedido(pedidoId: String): Pedido {
logger.info("Iniciando processamento do pedido",
kv("pedidoId", pedidoId),
kv("operacao", "processar_pedido")
)
val pedido = pedidoRepository.findById(pedidoId)
?: run {
logger.warn("Pedido nao encontrado",
kv("pedidoId", pedidoId)
)
throw PedidoNaoEncontradoException(pedidoId)
}
return try {
val resultado = pagamentoGateway.processar(pedido)
logger.info("Pedido processado com sucesso",
kv("pedidoId", pedidoId),
kv("valor", pedido.valor),
kv("status", resultado.status)
)
pedido.copy(status = PedidoStatus.PROCESSADO)
} catch (e: Exception) {
logger.error("Erro ao processar pedido",
kv("pedidoId", pedidoId),
kv("erro", e.message),
e
)
pedido.copy(status = PedidoStatus.ERRO)
}
}
private fun kv(key: String, value: Any?): net.logstash.logback.argument.StructuredArgument {
return net.logstash.logback.argument.StructuredArguments.kv(key, value)
}
}
Logs Estruturados com Logback
Para que logs sejam uteis em producao, eles devem ser estruturados em JSON:
// Extension function para logging idiomatico em Kotlin
inline fun <reified T> T.logger(): Logger {
return LoggerFactory.getLogger(T::class.java)
}
class OrderProcessor {
private val log = logger()
fun process(order: Order) {
log.info("Processing order {} for customer {}",
order.id, order.customerId)
}
}
A configuracao do Logback (logback-spring.xml) deve usar o LogstashEncoder para producao, gerando logs JSON que podem ser facilmente indexados por ferramentas como Elasticsearch ou Loki.
2. Metricas
Metricas sao dados numericos agregados ao longo do tempo. Respondem perguntas como “quantas requisicoes por segundo estamos recebendo?” e “qual a latencia media da API?”.
// Metricas com Micrometer e Spring Boot
@Configuration
class MetricsConfig(private val meterRegistry: MeterRegistry) {
@Bean
fun pedidoCounter(): Counter {
return Counter.builder("pedidos.total")
.description("Total de pedidos processados")
.tag("aplicacao", "pedido-service")
.register(meterRegistry)
}
@Bean
fun pedidoTimer(): Timer {
return Timer.builder("pedidos.duracao")
.description("Duracao do processamento de pedidos")
.publishPercentiles(0.5, 0.95, 0.99) // p50, p95, p99
.register(meterRegistry)
}
}
@Service
class PedidoService(
private val pedidoCounter: Counter,
private val pedidoTimer: Timer,
private val meterRegistry: MeterRegistry
) {
suspend fun criarPedido(request: CriarPedidoRequest): Pedido {
return pedidoTimer.recordSuspend {
try {
val pedido = processarInterno(request)
pedidoCounter.increment()
meterRegistry.counter("pedidos.status",
"status", "sucesso"
).increment()
pedido
} catch (e: Exception) {
meterRegistry.counter("pedidos.status",
"status", "erro",
"tipo_erro", e.javaClass.simpleName
).increment()
throw e
}
}
}
}
// Extension function para medir suspend functions
suspend fun <T> Timer.recordSuspend(block: suspend () -> T): T {
val sample = Timer.start()
try {
return block()
} finally {
sample.stop(this)
}
}
Metricas de Negocios
Alem de metricas tecnicas, monitore metricas de negocio:
@Component
class BusinessMetrics(private val meterRegistry: MeterRegistry) {
fun registrarVenda(valor: Double, categoria: String) {
meterRegistry.counter("vendas.total",
"categoria", categoria
).increment()
meterRegistry.summary("vendas.valor",
"categoria", categoria
).record(valor)
}
fun registrarCarrinhoAbandonado(valor: Double) {
meterRegistry.counter("carrinho.abandonado").increment()
meterRegistry.summary("carrinho.abandonado.valor").record(valor)
}
// Gauge para valores instantaneos
fun monitorarFilaProcessamento(fila: Queue<*>) {
meterRegistry.gauge("fila.processamento.tamanho", fila) {
it.size.toDouble()
}
}
}
3. Tracing Distribuido
Em arquiteturas de microservicos, tracing permite acompanhar uma requisicao atraves de multiplos servicos:
// OpenTelemetry com Spring Boot e Kotlin
@RestController
@RequestMapping("/api/pedidos")
class PedidoController(
private val pedidoService: PedidoService,
private val tracer: Tracer
) {
@PostMapping
suspend fun criarPedido(
@RequestBody request: CriarPedidoRequest
): ResponseEntity<PedidoDTO> {
val span = tracer.spanBuilder("criar-pedido")
.setAttribute("pedido.cliente_id", request.clienteId)
.setAttribute("pedido.itens_count", request.itens.size.toLong())
.startSpan()
return try {
span.makeCurrent().use {
val pedido = pedidoService.criarPedido(request)
span.setAttribute("pedido.id", pedido.id)
span.setAttribute("pedido.valor", pedido.valor)
ResponseEntity.status(HttpStatus.CREATED).body(pedido.toDTO())
}
} catch (e: Exception) {
span.recordException(e)
span.setStatus(StatusCode.ERROR, e.message ?: "Erro desconhecido")
throw e
} finally {
span.end()
}
}
}
Propagacao de Contexto com Coroutines
Um desafio especifico de Kotlin e a propagacao de contexto de tracing com coroutines:
// Propagacao de contexto OpenTelemetry com coroutines
import io.opentelemetry.extension.kotlin.asContextElement
suspend fun processarComTracing(pedidoId: String) {
val span = tracer.spanBuilder("processar-pagamento")
.startSpan()
withContext(span.asContextElement()) {
try {
// O contexto de tracing e propagado automaticamente
val pagamento = pagamentoService.processar(pedidoId)
notificacaoService.enviar(pedidoId, pagamento)
} finally {
span.end()
}
}
}
Stack de Observabilidade
Prometheus + Grafana
A combinacao mais popular para metricas:
// Exposicao de metricas para Prometheus
// application.yml
// management:
// endpoints:
// web:
// exposure:
// include: health,info,prometheus
// metrics:
// export:
// prometheus:
// enabled: true
// tags:
// application: pedido-service
// environment: production
// Metricas customizadas expostas automaticamente
@Component
class CustomMetrics(private val registry: MeterRegistry) {
init {
// Metrica de uptime
registry.gauge("app.uptime.seconds", this) {
ManagementFactory.getRuntimeMXBean().uptime / 1000.0
}
}
}
ELK Stack (Elasticsearch, Logstash, Kibana)
Para logs centralizados, o ELK stack e o padrao da industria. Com Logback configurado para JSON e Filebeat para coleta, os logs de todas as instancias da sua aplicacao ficam pesquisaveis em um unico lugar.
Jaeger ou Zipkin
Para tracing distribuido, Jaeger e Zipkin sao as opcoes mais populares. OpenTelemetry permite trocar entre eles sem mudar o codigo da aplicacao.
Alertas Inteligentes
Observabilidade sem alertas e como ter cameras de seguranca que ninguem assiste:
// Exemplo de cenarios que devem gerar alertas:
// 1. Taxa de erro acima de 5% por mais de 5 minutos
// 2. Latencia p99 acima de 2 segundos
// 3. Uso de memoria acima de 90%
// 4. Fila de processamento crescendo continuamente
// Health check detalhado para alertas
@Component
class DetailedHealthIndicator(
private val dataSource: DataSource,
private val redisTemplate: RedisTemplate<String, String>
) : HealthIndicator {
override fun health(): Health {
val checks = mutableMapOf<String, Any>()
// Verificar banco de dados
val dbHealthy = try {
dataSource.connection.use { it.isValid(3) }
} catch (e: Exception) {
false
}
checks["database"] = if (dbHealthy) "UP" else "DOWN"
// Verificar Redis
val redisHealthy = try {
redisTemplate.connectionFactory?.connection?.ping() != null
} catch (e: Exception) {
false
}
checks["redis"] = if (redisHealthy) "UP" else "DOWN"
return if (dbHealthy && redisHealthy) {
Health.up().withDetails(checks).build()
} else {
Health.down().withDetails(checks).build()
}
}
}
Conclusao
Observabilidade nao e luxo, e necessidade. Em aplicacoes Kotlin de producao, ter logs estruturados, metricas abrangentes e tracing distribuido e o que separa equipes que reagem a problemas de equipes que os previnem.
Comece com logs estruturados (e o mais rapido de implementar), adicione metricas com Micrometer e Prometheus, e por fim implemente tracing distribuido com OpenTelemetry. Cada camada adiciona visibilidade sobre o comportamento da sua aplicacao e reduz o tempo de resolucao de problemas. Invista em observabilidade e durma mais tranquilo sabendo que sua aplicacao esta sendo monitorada.