Colocar código em producao e só o comeco. O verdadeiro desafio e saber o que esta acontecendo com sua aplicação em tempo real. Observabilidade, composta por logs, métricas e tracing distribuído, é o que permite entender, diagnosticar e resolver problemas rapidamente. Vamos ver como implementar observabilidade completa em aplicações Kotlin.
Os Tres Pilares da Observabilidade
1. Logs
Logs registram eventos discretos que acontecem na aplicação. São a forma mais básica de observabilidade, mas quando bem estruturados, são 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 úteis 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 configuração 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. Métricas
Métricas são dados numericos agregados ao longo do tempo. Respondem perguntas como “quantas requisicoes por segundo estamos recebendo?” e “qual a latencia media da API?”.
// Métricas 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)
}
}
Métricas de Negocios
Alem de métricas tecnicas, monitore métricas de negócio:
@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 Distribuído
Em arquiteturas de microsserviços, tracing permite acompanhar uma requisicao através de múltiplos serviços:
// 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 específico 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 métricas:
// Exposicao de métricas para Prometheus
// application.yml
// management:
// endpoints:
// web:
// exposure:
// include: health,info,prometheus
// metrics:
// export:
// prometheus:
// enabled: true
// tags:
// application: pedido-service
// environment: production
// Métricas 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 padrão da industria. Com Logback configurado para JSON e Filebeat para coleta, os logs de todas as instancias da sua aplicação ficam pesquisaveis em um único lugar.
Jaeger ou Zipkin
Para tracing distribuído, Jaeger e Zipkin são as opções mais populares. OpenTelemetry permite trocar entre eles sem mudar o código da aplicação.
Alertas Inteligentes
Observabilidade sem alertas e como ter cameras de segurança 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()
}
}
}
Conclusão
Observabilidade não e luxo, e necessidade. Em aplicações Kotlin de producao, ter logs estruturados, métricas abrangentes e tracing distribuído e o que separa equipes que reagem a problemas de equipes que os previnem.
Comece com logs estruturados (e o mais rápido de implementar), adicione métricas com Micrometer e Prometheus, e por fim implemente tracing distribuído com OpenTelemetry. Cada camada adiciona visibilidade sobre o comportamento da sua aplicação e reduz o tempo de resolução de problemas. Muitas das melhores ferramentas de observabilidade — como Prometheus, Grafana e Jaeger — são escritas em Go, o que torna essa linguagem um excelente complemento para quem trabalha com infraestrutura. Invista em observabilidade e durma mais tranquilo sabendo que sua aplicação esta sendo monitorada.