DEV Community

Cover image for Prometheus na prática - scraping, o endpoint /metrics e uma mini app Python expondo métricas
Rafael Dutra for apsis-cc

Posted on

Prometheus na prática - scraping, o endpoint /metrics e uma mini app Python expondo métricas

1. Retomando: da stack vazia à primeira fonte de dados

No primeiro artigo desta série, subimos Prometheus, Loki e Grafana com Docker Compose, mas sem nenhuma aplicação real enviando dados — o Prometheus só monitorava a si mesmo. Este artigo resolve isso: vamos entender como o Prometheus efetivamente coleta métricas de uma aplicação (scraping) e construir uma mini aplicação em Python puro, sem nenhum framework, que expõe métricas nesse formato.

2. Pull vs push: o modelo de scraping do Prometheus

Existem duas formas de uma ferramenta de métricas obter dados de uma aplicação:

  • Push — a aplicação envia ativamente os valores para um servidor central sempre que quiser (ou em intervalos definidos por ela mesma). StatsD funciona assim.
  • Pull (scraping) — a aplicação apenas expõe seus valores atuais em um endpoint HTTP, e é o servidor de métricas que periodicamente visita esse endpoint e lê o estado atual. É o modelo do Prometheus.

O Prometheus escolheu pull deliberadamente, por algumas razões práticas:

  • Descoberta centralizada de falhas — se o Prometheus não consegue fazer scraping de um alvo, ele mesmo já sabe e registra isso (métrica up == 0), sem depender da aplicação perceber que parou de enviar dados.
  • Facilidade de testar manualmente — como o endpoint é só HTTP, dá para abrir /metrics num navegador ou com curl e ver exatamente o que será coletado, sem precisar simular o lado do Prometheus.
  • Controle central do intervalo de coleta — quem decide a frequência é quem consome os dados (o Prometheus), não cada aplicação individualmente.

A exceção fica por conta de jobs de curta duração (um script batch que roda e termina em segundos, sem ficar no ar para ser "scrapeado") — para esses casos existe o Pushgateway, um componente auxiliar que aceita push e fica exposto para o Prometheus fazer pull dele. Para aplicações de vida longa como as desta série (servidores HTTP), o modelo pull padrão é suficiente.

3. O formato de exposição: o que vive em /metrics

Um endpoint /metrics do Prometheus é simplesmente texto plano, em um formato específico chamado exposition format. Cada métrica segue esta estrutura:

# HELP app_requests_total Total de requisições recebidas
# TYPE app_requests_total counter
app_requests_total 42
Enter fullscreen mode Exit fullscreen mode
  • A linha # HELP é uma descrição legível por humanos (opcional, mas boa prática).
  • A linha # TYPE declara o tipo da métrica — counter, gauge, histogram ou summary (o artigo 3 desta série detalha cada tipo).
  • A linha de dados é nome_da_metrica{labels_opcionais="valor"} valor.

Nenhuma biblioteca é estritamente necessária para gerar esse texto — é só formatação de string. Isso torna o formato fácil de implementar em qualquer linguagem, mesmo sem um cliente oficial, o que é exatamente o que esta mini aplicação vai fazer.

4. Uma mini app em Python puro com http.server

Usando apenas a biblioteca padrão do Python (módulo http.server, sem Flask, Django ou qualquer outro framework), uma aplicação mínima que conta requisições e expõe essa contagem em /metrics:

# app.py
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

start_time = time.time()
request_count = 0


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        global request_count

        if self.path == "/metrics":
            uptime = time.time() - start_time
            body = (
                "# HELP app_requests_total Total de requisicoes recebidas\n"
                "# TYPE app_requests_total counter\n"
                f"app_requests_total {request_count}\n"
                "# HELP app_uptime_seconds Tempo desde que o processo iniciou\n"
                "# TYPE app_uptime_seconds gauge\n"
                f"app_uptime_seconds {uptime:.2f}\n"
            )
            self.send_response(200)
            self.send_header("Content-Type", "text/plain; version=0.0.4")
            self.end_headers()
            self.wfile.write(body.encode("utf-8"))
            return

        request_count += 1
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(b"ola do app em python\n")

    def log_message(self, format, *args):
        pass  # silencia o log padrao do http.server no stdout


if __name__ == "__main__":
    server = ThreadingHTTPServer(("0.0.0.0", 8000), Handler)
    print("app rodando em http://0.0.0.0:8000")
    server.serve_forever()
Enter fullscreen mode Exit fullscreen mode

Rodando localmente:

python3 app.py
Enter fullscreen mode Exit fullscreen mode

Cada requisição a qualquer path diferente de /metrics incrementa o contador app_requests_total — importante notar que a própria requisição ao /metrics não conta como requisição de negócio, propositalmente, para não distorcer a métrica. Testando:

curl http://localhost:8000/
curl http://localhost:8000/
curl http://localhost:8000/metrics
Enter fullscreen mode Exit fullscreen mode

A última chamada deve mostrar app_requests_total 2.

5. Configurando o prometheus.yml para coletar a nova app

Com o endpoint funcionando, o próximo passo é adicionar um job ao prometheus.yml do artigo anterior, apontando para essa aplicação:

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "python-app"
    static_configs:
      - targets: ["python-app:8000"]
Enter fullscreen mode Exit fullscreen mode

E incluir a aplicação no docker-compose.yml, na mesma rede dos outros serviços (por isso o hostname python-app funciona no target acima — é o nome do serviço no Compose):

# docker-compose.yml (trecho adicionado ao do artigo anterior)
services:
  python-app:
    image: python:3.12-slim
    working_dir: /app
    volumes:
      - ./app.py:/app/app.py:ro
    command: ["python3", "app.py"]
    ports:
      - "8000:8000"
Enter fullscreen mode Exit fullscreen mode

Depois de docker compose up -d, o Prometheus passa a fazer scraping da aplicação a cada 15 segundos (o scrape_interval global). Para confirmar que o alvo está saudável, em http://localhost:9090/targets o job python-app deve aparecer com estado UP.

6. Consultando os dados no Prometheus

Com alguns minutos de coleta (gerando tráfego manualmente com curl em loop, se necessário), já é possível consultar no campo de expressão do Prometheus (http://localhost:9090/graph):

# valor bruto do contador, acumulado desde que o processo subiu
app_requests_total

# taxa de requisições por segundo, calculada sobre a janela de 1 minuto
rate(app_requests_total[1m])
Enter fullscreen mode Exit fullscreen mode

rate() é a função mais usada sobre contadores no PromQL: como um contador só cresce (e zera se o processo reiniciar), o valor bruto não diz muito sozinho — o que importa é a velocidade de crescimento, que é exatamente o que rate() calcula, já lidando corretamente com o caso de reinício do contador.

7. Conclusão e próximos passos

Vimos por que o Prometheus escolhe pull em vez de push, como o formato de exposição em /metrics é apenas texto simples — implementável sem biblioteca alguma —, construímos uma mini aplicação Python que expõe uma métrica real, e configuramos o Prometheus para coletá-la periodicamente. No próximo artigo, a mini aplicação passa a ser em PHP puro, e o foco vai para os três tipos de métrica — counters, gauges e histograms — aplicados a métricas de negócio, não só contagem de requisições.


Imagem de capa: Logo oficial do Prometheus — repositório prometheus/prometheus, licença Apache 2.0 (convertido para PNG via wsrv.nl)

Referências:

  1. Prometheus — Exposition Formats
  2. Prometheus — Configuration
  3. Prometheus — Querying Basics
  4. Python Docs — http.server

Top comments (0)