1. Retomando: métricas prontas, falta o segundo pilar
As duas mini aplicações desta série — a de Python (artigo 2) e a de PHP (artigo 3) — já expõem métricas consumidas pelo Prometheus. Métricas respondem "quantos pedidos falharam nos últimos 5 minutos", mas não respondem "por que o pedido 4821 falhou especificamente". Essa é a pergunta que logs respondem, e este artigo cobre como enviá-los para o Loki diretamente das mini apps.
2. Por que a indexação do Loki é diferente
Ferramentas de log tradicionais, como o Elasticsearch, indexam o conteúdo completo de cada linha — todo termo de texto vira pesquisável, o que permite buscas ricas, mas exige estruturas de índice caras em disco e CPU, que crescem proporcionalmente ao volume de log.
O Loki toma uma decisão deliberadamente diferente, inspirada diretamente no modelo do Prometheus: indexa só um conjunto pequeno de labels — como app="python-app", env="production" — e trata o conteúdo da linha de log como texto opaco, comprimido e armazenado em blocos (chunks), sem índice próprio. Quando uma consulta pede um filtro de texto (|= "erro", por exemplo), o Loki varre os chunks relevantes (já filtrados pelos labels) no momento da consulta, em vez de consultar um índice pré-construído.
O resultado prático dessa troca:
- Armazenamento muito mais barato — sem índice invertido de texto completo, o custo de disco cresce bem mais devagar com o volume de logs.
- Buscas por label são muito rápidas — "todos os logs do serviço X, no ambiente Y" é uma operação barata, porque os labels são de fato indexados.
- Buscas por texto livre são mais lentas conforme o volume cresce — porque envolvem varredura, não um lookup de índice. Na prática, isso empurra o usuário a sempre filtrar primeiro por labels (que reduzem drasticamente o volume a varrer) antes de aplicar um filtro de texto.
-
Cardinalidade de labels importa tanto quanto no Prometheus — usar um label com alta cardinalidade (como um
user_idúnico por requisição) quebra a mesma premissa de eficiência nos dois sistemas, e é um erro comum de quem vem de ferramentas que indexam tudo.
3. A API HTTP de push do Loki
Diferente do Prometheus (que faz pull), o Loki recebe logs por push: a aplicação envia ativamente cada lote de linhas para um endpoint HTTP, POST /loki/api/v1/push. O corpo da requisição é um JSON com o seguinte formato:
{
"streams": [
{
"stream": {
"app": "python-app",
"level": "info"
},
"values": [
["1735689600000000000", "requisicao recebida em /"]
]
}
]
}
Pontos importantes desse formato:
-
streamcarrega os labels — o que o Loki de fato indexa. Poucos e de baixa cardinalidade, como visto acima. -
valuesé uma lista de[timestamp, linha], onde o timestamp é uma string em nanosegundos desde epoch (não segundos, nem milissegundos — um detalhe fácil de errar). - É possível enviar múltiplas linhas do mesmo stream de uma vez, o que é mais eficiente do que uma requisição HTTP por linha de log.
4. Enviando logs a partir da mini app Python
Usando só a biblioteca padrão (urllib, sem instalar um client HTTP externo), uma função de log que envia diretamente para o Loki:
# logger.py
import json
import time
import urllib.request
LOKI_URL = "http://loki:3100/loki/api/v1/push"
def log_to_loki(message, level="info", app="python-app"):
timestamp_ns = str(time.time_ns())
payload = {
"streams": [
{
"stream": {"app": app, "level": level},
"values": [[timestamp_ns, message]],
}
]
}
req = urllib.request.Request(
LOKI_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
urllib.request.urlopen(req, timeout=2)
except Exception as exc:
# nunca deixar uma falha de log derrubar a aplicacao
print(f"falha ao enviar log para o Loki: {exc}")
Integrando no app.py do artigo 2, chamando log_to_loki a cada requisição de negócio:
# dentro do do_GET, no branch que nao eh /metrics
log_to_loki(f"requisicao recebida em {self.path}")
5. Enviando logs a partir da mini app PHP
O mesmo endpoint, chamado a partir do PHP puro com file_get_contents e um stream context (sem precisar de cURL nem bibliotecas externas):
<?php
// logger.php
function logToLoki($message, $level = 'info', $app = 'php-app') {
// microtime(true) da segundos com fracao decimal; convertendo para
// nanossegundos desde epoch, o formato que o Loki espera
$timestampNs = sprintf('%.0f', microtime(true) * 1e9);
$payload = json_encode([
'streams' => [[
'stream' => ['app' => $app, 'level' => $level],
'values' => [[$timestampNs, $message]],
]],
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $payload,
'timeout' => 2,
'ignore_errors' => true,
],
]);
@file_get_contents('http://loki:3100/loki/api/v1/push', false, $context);
}
Integrando no app.php do artigo 3, logo após processar um pedido:
require __DIR__ . '/logger.php';
logToLoki("pedido processado em {$state['requests_total']} ms de fila");
6. Consultando os logs no Grafana com LogQL
Com o datasource do Loki já configurado no Grafana desde o artigo 1, em Explore, selecionando o datasource Loki, uma consulta LogQL básica:
{app="python-app"}
Isso retorna todas as linhas de log com o label app="python-app" — o equivalente a um grep pré-filtrado pelos labels indexados. Para refinar com um filtro de texto:
{app="python-app"} |= "erro"
{app="php-app", level="info"} != "healthcheck"
|= filtra linhas que contêm o texto; != filtra linhas que não contêm. Esses filtros de texto rodam sobre o conteúdo já restrito pelos labels — daí a importância de sempre começar a query pelos labels certos, como discutido na seção 2.
7. Conclusão e próximos passos
Vimos por que o Loki indexa só labels e trata o conteúdo da linha como texto não indexado — uma troca deliberada de riqueza de busca por custo —, como enviar logs diretamente via API HTTP sem bibliotecas externas em Python e PHP, e como consultar esses logs com LogQL no Grafana. Com métricas e logs das duas mini apps já fluindo para a stack, falta o terceiro pilar: o próximo artigo introduz o OpenTelemetry e instrumenta as mesmas aplicações para gerar traces.
Imagem de capa: Logo oficial do Prometheus — repositório prometheus/prometheus, licença Apache 2.0 (convertido para PNG via wsrv.nl)
Referências:
Top comments (0)