Observabilidade e linhagem
As sete perguntas, métricas com percentil, tracing aninhado e de onde veio cada número.
Observabilidade é conseguir responder, sem abrir o código:
- executou?
- quanto tempo demorou?
- quantos registros processou?
- quantos falharam?
- qual etapa falhou?
- quando?
- qual versão estava rodando?
Log sozinho responde a primeira e a sexta. As outras cinco exigem número — e é isso que separa observabilidade de logging.
O painel#
adopt Arcane.Observar as O
p := O.painel("etl-vendas", "2.1.0")
O.contar(p, "linhas_lidas", 40000) // só sobe
O.medir(p, "latencia_ms", 12.4) // guarda a distribuição
O.marcar(p, "versao_do_esquema", 7) // o último vale
out O.relatorio(p)A média esconde#
medida p50 p95 p99 máx
latencia_ms 2.000 2.000 40.000 40.000Um pipeline com média de 3,9s e p99 de 40s tem um problema que a média nunca mostra — e é o p99 que o usuário sente. medir guarda a distribuição e devolve p50, p95 e p99.
O cálculo é interpolação linear — a mesma de numpy.percentile. Não é detalhe: um p99 calculado de outro jeito daria um número diferente do painel que a equipe já olha, e ninguém saberia qual acreditar.
Tracing: onde o tempo foi#
extrair := O.abrir(p, "extrair")
// …
O.fechar(p, extrair)
transformar := O.abrir(p, "transformar")
limpar := O.abrir(p, "limpar", transformar) // aninhado
O.fechar(p, limpar)
O.fechar(p, transformar) transformar 0.048s 60.3% ██████████████
enriquecer 0.035s 44.4% ██████████
limpar 0.013s 15.9% ███
extrair 0.025s 31.7% ███████
✗ carregar 0.006s 8.0% █carregar levou 40s não ajuda. Dos 40s, 38 foram no INSERT resolve — e é o aninhamento que mostra isso.
A forma que não deixa trecho aberto#
O.cronometrar(p, "carregar", lambda => carregar(dados))Mesmo quando a ação estoura, o trecho é fechado — marcado como falha, e com a mensagem. E resumo avisa se sobrou trecho aberto no fim, que é quase sempre um ensure que faltou.
Linhagem: de onde veio esse número#
O.derivar(p, "vendas_bruto", ["api_erp"], "extração diária")
O.derivar(p, "vendas_prata", ["vendas_bruto"], "limpeza e deduplicação")
O.derivar(p, "painel_diario", ["vendas_prata", "metas"], "agregação por dia")Quando um número no painel está errado, a pergunta não é onde está o bug: é de onde veio esse número. Sem linhagem registrada, a resposta sai de ler o código de trás para a frente — e o código mudou desde que aquele número foi calculado.
O.origem(p, "painel_diario")
// vendas_prata → painel_diario (agregação por dia)
// metas → painel_diario (agregação por dia)
// vendas_bruto → vendas_prata (limpeza e deduplicação)
// api_erp → vendas_bruto (extração diária)E a pergunta inversa, que é a mais cara#
O.impacto(p, "vendas_bruto")
// ["vendas_prata", "painel_diario"]Se eu mexer aqui, o que quebra? — a pergunta que trava refatoração em pipeline grande.
Sair#
O.relatorio(p) // tudo em texto, para o log
O.prometheus(p) // o formato de exposição, para o coletor
O.salvar(p, "obs/execucao.json")
O.alertar(p, regras)Alertas#
regras := {
"latencia_ms": {"acima": 10, "estatistica": "p99", "texto": "a cauda está longa"},
"erros": {"acima": 0, "texto": "houve falha"},
}Expor no Kiln#
route GET "/metricas":
respond text O.prometheus(painel)