Ollama: Monitoramento e Logging

Monitoramento é o check-up de saúde do Ollama — batimento cardíaco em tempo real, tendências de métricas, alertas de anomalias, prevenindo problemas antes que aconteçam.

💡 Dica: Prometheus + Grafana é a combinação de monitoramento mais mainstream — Prometheus lida com coleta e armazenamento periódico de métricas, enquanto Grafana lida com visualização e alertas. O Ollama não possui um endpoint Prometheus embutido; você precisa combinar node_exporter (métricas de sistema) + DCGM exporter (métricas de GPU) + middleware personalizado (métricas de negócio).

📋 Pré-requisitos: Você deve dominar o seguinte primeiro

1. O Que Você Vai Aprender


2. Uma História Real de Uma Empreendedora SaaS

💡 Dica: GPU OOM é a falha de produção mais comum do Ollama. Configure um alerta antecipado de uso de VRAM > 90% no Grafana para detectar problemas com antecedência. Também monitore temperatura e consumo de energia da GPU via nvidia-smi — superaquecimento também causa quedas repentinas de desempenho.

ℹ️ Info: O Ollama não possui um endpoint de métricas Prometheus embutido (/metrics). Você precisa combinar node_exporter (métricas de sistema) + DCGM exporter (métricas de GPU) + um middleware FastAPI personalizado (métricas de negócio) para implementar monitoramento completo.

(1) O Problema: Ollama Fica Lento Repentinamente Sem Causa Conhecida

O tempo de resposta do SupportBot da Alice disparou de 2 segundos para 15 segundos certo dia, mas ela não sabia se era carregamento lento de modelo, GPU OOM, ou concorrência excessiva. Sem dados de monitoramento, só podia adivinhar.

(2) A Solução: Monitoramento em Tempo Real com Prometheus + Grafana

Após implantar a stack de monitoramento, dashboards do Grafana mostraram utilização de GPU, latência de requisições e uso de memória em tempo real. Uma olhada rápida revelou que outro processo estava consumindo memória de GPU:

BASH
# Prometheus scrapes Ollama metrics
# Grafana visualizes real-time dashboards
# Alertmanager sends notifications when anomalies detected

3. Configuração de Logs do Ollama

⚠️ Nota: Os logs do Ollama registram conteúdo completo de requisição e resposta por padrão, que pode conter informações sensíveis da entrada do usuário (nomes, números de pedido, etc.). Em produção, sanitize os logs na camada de proxy reverso antes de registrar, ou limite o nível de log para evitar registrar corpos de requisição.

(1) Fontes de Logs e Métodos de Visualização

Plataforma Comando de Log Descrição
Linux systemd journalctl -u ollama -f Acompanhamento em tempo real
Docker docker logs -f ollama Logs do Container
macOS cat ~/.ollama/logs/server.log Arquivo de log
Windows Visualizador de Eventos Logs de eventos

(2) Ativando Logs de Debug

Variável de Ambiente Propósito Conteúdo de Saída
OLLAMA_DEBUG Informações detalhadas de debug Detecção de GPU, detalhes de carregamento de modelo
OLLAMA_FLASH_ATTENTION Logs do Flash Attention Status do mecanismo de atenção

▶ Exemplo 1: Visualização e Análise de Logs

BASH
# Linux: real-time Ollama logs
sudo journalctl -u ollama -f --no-pager

# Filter for errors
sudo journalctl -u ollama | grep -i "error\|fail\|oom\|panic"

# Docker: real-time container logs
docker logs -f ollama --tail 100

# Enable debug logging
sudo systemctl edit ollama
[Service]
Environment="OLLAMA_DEBUG=1"
sudo systemctl restart ollama

# Check for GPU issues in logs
sudo journalctl -u ollama --since "1 hour ago" | grep -i "cuda\|gpu\|memory"

Saída:

TEXT
# Ollama command executed successfully

4. Métricas-Chave de Monitoramento

(1) Quatro Categorias de Métricas

Categoria Métricas Método de Coleta Limiar de Alerta
GPU Utilização, uso de VRAM, temperatura nvidia-smi / DCGM > 95% utilização
Inferência Latência, Tokens/s, taxa de requisições Exporter personalizado > 10s latência
Sistema CPU, RAM, IO de disco node_exporter > 90% RAM
Aplicação Taxa de erro, tempo de carregamento de modelo Análise de logs > 5% taxa de erro

(2) Métricas Personalizadas do Ollama

Métrica Método de Coleta Significado
request_latency Timing da API Latência da requisição
tokens_per_second Estatísticas de resposta Contagem de tokens gerados
model_load_time Timing de carregamento Tempo de carregamento do modelo
active_models Consulta à API Contagem de modelos carregados

▶ Exemplo 2: Coleta de Métricas Personalizadas

PYTHON
import ollama
import time
import json
from datetime import datetime
from pathlib import Path

class OllamaMetrics:
    def __init__(self, log_file: str = "ollama_metrics.jsonl"):
        self.log_file = Path(log_file)

    def record_request(self, model: str, prompt: str) -> dict:
        start = time.time()
        try:
            response = ollama.chat(
                model=model,
                messages=[{"role": "user", "content": prompt}],
                stream=False
            )
            duration = time.time() - start
            content = response["message"]["content"]
            metrics = {
                "timestamp": datetime.now().isoformat(),
                "model": model,
                "duration_s": round(duration, 3),
                "response_length": len(content),
                "estimated_tokens": len(content) // 4,
                "tok_per_s": round(len(content) / 4 / duration, 1),
                "status": "success"
            }
        except Exception as e:
            duration = time.time() - start
            metrics = {
                "timestamp": datetime.now().isoformat(),
                "model": model,
                "duration_s": round(duration, 3),
                "status": "error",
                "error": str(e)
            }

        with open(self.log_file, "a") as f:
            f.write(json.dumps(metrics) + "\n")
        return metrics

    def get_stats(self, minutes: int = 30) -> dict:
        """Calculate stats from recent metrics."""
        cutoff = datetime.now().timestamp() - minutes * 60
        durations = []
        errors = 0
        total = 0

        if not self.log_file.exists():
            return {}

        with open(self.log_file) as f:
            for line in f:
                data = json.loads(line)
                ts = datetime.fromisoformat(data["timestamp"]).timestamp()
                if ts >= cutoff:
                    total += 1
                    durations.append(data.get("duration_s", 0))
                    if data.get("status") == "error":
                        errors += 1

        if total == 0:
            return {}

        return {
            "total_requests": total,
            "error_rate": round(errors / total * 100, 1),
            "avg_duration_s": round(sum(durations) / len(durations), 2),
            "max_duration_s": round(max(durations), 2),
            "p95_duration_s": round(sorted(durations)[int(len(durations) * 0.95)], 2)
        }

# Usage
metrics = OllamaMetrics()
metrics.record_request("qwen2.5", "Hello")
print(metrics.get_stats())

Saída:

TEXT
# Function defined successfully

5. Stack de Monitoramento Prometheus + Grafana

(1) Arquitetura de Monitoramento

100%
flowchart LR
    A[Ollama<br/>+ Custom Exporter] --> B[Prometheus<br/>Metrics Store]
    C[node_exporter<br/>System Metrics] --> B
    D[DCGM Exporter<br/>GPU Metrics] --> B
    B --> E[Grafana<br/>Dashboards]
    E --> F[Alertmanager<br/>Notifications]
    F --> G[Email/Slack]

(2) Docker Compose da Stack de Monitoramento

▶ Exemplo 3: Implantação da Stack de Monitoramento

YAML
# docker-compose.monitoring.yml
version: "3.8"

services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    ports: ["9090:9090"]
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    restart: unless-stopped

  grafana:
    image: grafana/grafana
    container_name: grafana
    ports: ["3001:3000"]
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on: [prometheus]
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter
    container_name: node-exporter
    ports: ["9100:9100"]
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:
YAML
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "node"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "ollama_custom"
    static_configs:
      - targets: ["host.docker.internal:8000"]  # Custom exporter

Saída:

TEXT
Monitoring stack deployed successfully, Prometheus/Grafana services running normally

6. Regras de Alerta

(1) Regras de Alerta-Chave

Alerta Condição Severidade Notificação
GPU OOM VRAM > 95% por 2 minutos Crítico Notificação imediata
Alta latência P95 > 10s por 5 minutos Aviso Email
Serviço indisponível Health check falha 3 vezes Crítico Notificação imediata
Pico na taxa de erro Taxa de erro > 10% por 3 minutos Aviso Email
Pouco espaço em disco Espaço disponível < 10% Aviso Email

▶ Exemplo 4: Regras de Alerta do Prometheus

YAML
# alert_rules.yml
groups:
  - name: ollama_alerts
    rules:
      - alert: OllamaHighLatency
        expr: histogram_quantile(0.95, rate(ollama_request_duration_seconds_bucket[5m])) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Ollama P95 latency above 10s"
          description: "Current P95: {{ $value }}s"

      - alert: OllamaServiceDown
        expr: up{job="ollama_custom"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Ollama service is down"
          description: "No response from Ollama for 2 minutes"

      - alert: HighGPUMemory
        expr: DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL > 0.95
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "GPU memory usage above 95%"

Saída:

TEXT
# Alert rules loaded successfully
kubectl exec -it prometheus-0 -- wget -qO- http://localhost:9090/api/v1/rules | head
# {"status":"success","data":{"groups":[{"name":"ollama_alerts","rules":[...]}]}}

▶ Exemplo 5: Diagnóstico de Consultas Lentas

BASH
#!/bin/bash
# Slow query diagnosis script

LOG="ollama_metrics.jsonl"

echo "=== Slow Query Report (last 60 minutes) ==="
echo ""

if [ -f "$LOG" ]; then
    # Find requests with duration > 5 seconds
    python3 -c "
import json, sys
from datetime import datetime, timedelta

cutoff = datetime.now() - timedelta(hours=1)
slow = []
with open('$LOG') as f:
    for line in f:
        data = json.loads(line)
        ts = datetime.fromisoformat(data['timestamp'])
        if ts >= cutoff and data.get('duration_s', 0) > 5:
            slow.append(data)

if slow:
    print(f'Found {len(slow)} slow requests (>5s):')
    for s in slow[:10]:
        print(f\"  {s['timestamp']}: {s['duration_s']}s, model={s.get('model','?')}, status={s.get('status','?')}\")
else:
    print('No slow requests found in the last hour.')
"
else
    echo "No metrics log found. Enable OllamaMetrics first."
fi

echo ""
echo "=== Current GPU Status ==="
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv 2>/dev/null || echo "No GPU detected"

Saída:

TEXT
NAME                    ID              SIZE    
llama3.2:latest        a80...          2.0 GB  
mistral:latest         61...           4.1 GB

7. Exemplo Abrangente: Configuração do Dashboard de Monitoramento

PYTHON
# ============================================
# Comprehensive: Monitoring dashboard config
# Prometheus + Grafana + Custom exporter
# ============================================

import json
from http.server import HTTPServer, BaseHTTPRequestHandler
import ollama
import time
import threading

# Custom Prometheus exporter for Ollama
class OllamaExporter(BaseHTTPRequestHandler):
    metrics = {
        "ollama_request_duration_seconds": [],
        "ollama_requests_total": 0,
        "ollama_errors_total": 0,
        "ollama_active_models": 0,
    }

    def do_GET(self):
        if self.path == "/metrics":
            self.send_response(200)
            self.send_header("Content-Type", "text/plain")
            self.end_headers()

            output = []
            output.append(f"# HELP ollama_requests_total Total requests")
            output.append(f"# TYPE ollama_requests_total counter")
            output.append(f"ollama_requests_total {self.metrics['ollama_requests_total']}")

            output.append(f"# HELP ollama_errors_total Total errors")
            output.append(f"# TYPE ollama_errors_total counter")
            output.append(f"ollama_errors_total {self.metrics['ollama_errors_total']}")

            output.append(f"# HELP ollama_request_duration_seconds Request duration")
            output.append(f"# TYPE ollama_request_duration_seconds histogram")
            if self.metrics["ollama_request_duration_seconds"]:
                durations = self.metrics["ollama_request_duration_seconds"][-100:]
                avg = sum(durations) / len(durations)
                output.append(f'ollama_request_duration_seconds_avg {avg:.3f}')
                output.append(f'ollama_request_duration_seconds_max {max(durations):.3f}')

            # Active models
            try:
                models = ollama.list()
                active = len(models.get("models", []))
                output.append(f"# HELP ollama_active_models Number of loaded models")
                output.append(f"# TYPE ollama_active_models gauge")
                output.append(f"ollama_active_models {active}")
            except Exception:
                pass

            self.wfile.write("\n".join(output).encode())
        elif self.path == "/health":
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"OK")
        else:
            self.send_response(404)
            self.end_headers()

    @classmethod
    def record_request(cls, duration: float, is_error: bool = False):
        cls.metrics["ollama_requests_total"] += 1
        cls.metrics["ollama_request_duration_seconds"].append(duration)
        if is_error:
            cls.metrics["ollama_errors_total"] += 1

def run_exporter(port: int = 8000):
    server = HTTPServer(("0.0.0.0", port), OllamaExporter)
    print(f"Ollama exporter running on :{port}/metrics")
    server.serve_forever()

# Start exporter in background thread
# threading.Thread(target=run_exporter, daemon=True).start()
# Then configure Prometheus to scrape http://localhost:8000/metrics

❓ Perguntas Frequentes

P: O Ollama possui um endpoint Prometheus embutido? R: O Ollama em si não expõe métricas Prometheus. Você precisa construir seu próprio exporter (como o exemplo desta aula) ou usar a API do Ollama para coletar métricas e enviá-las ao Prometheus.

P: Como configuro rapidamente um dashboard Grafana? R: Grafana possui templates prontos de dashboard Node Exporter e DCGM (Importar por ID: 1860 para Node, 12239 para DCGM). Métricas personalizadas do Ollama requerem construção de dashboard próprio.

P: E se o volume de logs for muito grande? R: Configure rotação de logs (logrotate para systemd) ou use Loki em vez de logs em arquivo. Registre apenas erros e consultas lentas; use métricas em vez de logs para requisições normais.

P: Como ajustar alertas excessivamente frequentes? R: Aumente a duração for (de 1m para 5m), eleve limiares (latência de 5s para 10s), e classifique por severidade (crítico para notificação imediata, aviso apenas para registro).

P: Como monitorar o Ollama dentro do Docker? R: Docker stats para CPU/memória, nvidia-smi para GPU. Ou use cAdvisor para coletar métricas de Container e enviar ao Prometheus.

P: A stack de monitoramento impacta o desempenho? R: O scraping do Prometheus a cada 15 segundos tem virtualmente nenhum impacto no Ollama. Requisições de health check do exporter personalizado são muito pequenas (< 10 tokens).


📖 Resumo


📝 Exercícios

  1. Básico (⭐): Configure logging do Ollama, use journalctl para visualizar logs do serviço Ollama, e filtre mensagens de erro.
  2. Intermediário (⭐⭐): Implante uma stack de monitoramento Prometheus + Grafana, importe um dashboard Node Exporter, e confirme que métricas de sistema estão visíveis.
  3. Avançado (⭐⭐⭐): Implemente um exporter personalizado do Ollama, crie um dashboard específico do Ollama no Grafana (latência, Tokens/s, taxa de erro), e configure pelo menos 2 regras de alerta.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%