Ollama: 监控与日志

监控是 Ollama 的健康体检——实时心跳、指标趋势、异常告警,防患于未然。

💡 提示:Prometheus + Grafana 是最主流的监控组合——Prometheus 负责定时采集和存储指标数据,Grafana 负责可视化展示和告警。Ollama 没有内置 Prometheus 端点,需通过 node_exporter(系统指标)+ DCGM exporter(GPU 指标)+ 自定义中间件(业务指标)组合实现。

📋 前置知识:需要先掌握以下内容

1. 你将学到


2. 一个 SaaS 创业者的真实故事

💡 提示: GPU OOM 是 Ollama 最常见的生产故障。建议在 Grafana 中设置 VRAM 使用率 > 90% 的预警告警,提前发现问题。同时监控 nvidia-smi 的 GPU 温度和功耗,过热也会导致性能骤降。

ℹ️ 信息: Ollama 没有内置的 Prometheus metrics 端点(/metrics)。需通过 node_exporter(系统指标)+ DCGM exporter(GPU 指标)+ 自定义 FastAPI 中间件(业务指标)组合实现完整监控。

(1) 痛点:Ollama 突然变慢却不知道原因

Alice 的 SupportBot 某天响应时间从 2 秒飙升到 15 秒,但不知道是模型加载慢、GPU OOM 还是并发太高。没有监控数据只能猜。

(2) 解法:Prometheus + Grafana 实时监控

部署监控栈后,Grafana 仪表盘实时显示 GPU 利用率、请求延迟、内存占用。一眼发现是另一个进程占用了 GPU 内存:

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

3. Ollama 日志配置

⚠️ 注意:Ollama 日志默认记录完整的请求和响应内容,可能包含用户输入的敏感信息(姓名、订单号等)。生产环境务必在反向代理层脱敏后再记录日志,或限制日志级别避免记录请求体。

(1) 日志来源与查看方式

平台 日志命令 说明
Linux systemd journalctl -u ollama -f 实时跟踪
Docker docker logs -f ollama 容器日志
macOS cat ~/.ollama/logs/server.log 日志文件
Windows Event Viewer 事件日志

(2) 调试日志开启

环境变量 用途 输出内容
OLLAMA_DEBUG 详细调试信息 GPU 检测、模型加载细节
OLLAMA_FLASH_ATTENTION Flash Attention 日志 注意力机制状态

▶ 示例 1: 日志查看与分析

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"

输出:

TEXT 📖 仅展示
# Ollama 命令执行成功

4. 关键监控指标

(1) 四大指标类别

类别 指标 采集方式 告警阈值
GPU 利用率、VRAM 使用、温度 nvidia-smi / DCGM > 95% 利用率
推理 延迟、Tokens/s、请求速率 自定义 exporter > 10s 延迟
系统 CPU、RAM、磁盘 IO node_exporter > 90% RAM
应用 错误率、模型加载时间 日志分析 > 5% 错误率

(2) Ollama 自定义指标

指标 采集方法 含义
request_latency API 计时 请求延迟
tokens_per_second 响应统计 生成 token 数
model_load_time 加载计时 模型加载时间
active_models API 查询 当前加载模型数

▶ 示例 2: 自定义指标采集

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())

输出:

TEXT 📖 仅展示
# 函数定义成功

5. Prometheus + Grafana 监控栈

(1) 监控架构

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 监控栈

▶ 示例 3: 监控栈部署

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

输出:

TEXT 📖 仅展示
监控栈部署成功,Prometheus/Grafana 服务运行正常

6. 告警规则

(1) 关键告警规则

告警 条件 级别 通知
GPU OOM VRAM > 95% 持续 2 分钟 严重 立即通知
高延迟 P95 > 10s 持续 5 分钟 警告 邮件
服务不可用 健康检查失败 3 次 严重 立即通知
错误率飙升 错误率 > 10% 持续 3 分钟 警告 邮件
磁盘空间不足 可用空间 < 10% 警告 邮件

▶ 示例 4: 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%"

输出:

TEXT 📖 仅展示
# 告警规则加载成功
kubectl exec -it prometheus-0 -- wget -qO- http://localhost:9090/api/v1/rules | head
# {"status":"success","data":{"groups":[{"name":"ollama_alerts","rules":[...]}]}}

▶ 示例 5: 慢查询诊断

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"

输出:

TEXT 📖 仅展示
NAME                    ID              SIZE    
llama3.2:latest        a80...          2.0 GB  
mistral:latest         61...           4.1 GB

7. 综合示例:监控仪表盘配置

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

❓ 常见问题

Q Ollama 有内置 Prometheus 端点吗?
A Ollama 本身不暴露 Prometheus 指标。需自建 exporter(如本课示例)或用 Ollama API 采集指标推送到 Prometheus。
Q Grafana 仪表盘怎么快速搭建?
A Grafana 有现成的 Node Exporter 和 DCGM 仪表盘模板(Import by ID:1860 for Node,12239 for DCGM)。Ollama 自定义指标需自建仪表盘。
Q 日志量太大怎么办?
A 设置日志轮转(logrotate for systemd)或使用 Loki 替代文件日志。只记录错误和慢查询,正常请求用 metrics 代替 logs。
Q 告警太频繁怎么调?
A 增大 持续时间(从 1m 到 5m),提高阈值(延迟从 5s 到 10s),按严重程度分级(critical 立即通知,warning 只记录)。
Q 如何监控 Docker 内的 Ollama?
A Docker stats 看 CPU/内存,nvidia-smi 看 GPU。或用 cAdvisor 采集容器指标推送到 Prometheus。
Q 监控栈对性能有影响吗?
A Prometheus scraping 每 15 秒一次,对 Ollama 几乎无影响。自定义 exporter 的健康检查请求很小(< 10 tokens)。

📖 小节


📝 作业

  1. 基础题(难度⭐):配置 Ollama 日志,使用 journalctl 查看 Ollama 服务日志,过滤错误信息。
  2. 进阶题(难度⭐⭐):部署 Prometheus + Grafana 监控栈,导入 Node Exporter 仪表盘,确认系统指标可见。
  3. 挑战题(难度⭐⭐⭐):实现自定义 Ollama exporter,在 Grafana 中创建 Ollama 专用仪表盘(延迟、Tokens/s、错误率),配置至少 2 条告警规则。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏