-
Notifications
You must be signed in to change notification settings - Fork 0
Monitoring
We want to remotely monitor the health, activity, and related metrics of
- back-end application code
- web server, e.g. Nginx (production) or Uvicorn (development)
- database
- front-end
For the first three items, the initial implementation will use Prometheus for metrics and Grafana for visual display of the data.
| Component | Monitor | URL |
|---|---|---|
| FastAPI WS |
prometheus-fastapi-instrumentor + custom code in main.py
|
http://localhost:8080/metrics |
| Nginx |
/nginx_status location, accessible only inside Docker network |
http://nginx:8080/nginx_status |
| Nginx Exportor |
nginx-exporter running in a separate container exposed port 9113 |
http://localhost:9113/metrics |
| Prometheus Server & UI | Collects ("scrapes") metrics | http://localhost:9090 |
| Grafana UI | http://localhost:3000 |
Note that Docker resolves container names like nginx to IP addresses.
Use docker network ls to discover network names, docker network inspect homelog_default to discover container IP addresses.
As of this writing, the relevant files and directories are:
project/
├── docker-compose.yml Configure containers, exposed ports, and volumes
├── backend/
│ ├── Dockerfile FastAPI web service + Uvicorn
│ └── main.py Configure and expose metrics for Prometheus
├── nginx/ Nginx proxies the backend web service
│ └── nginx.conf Exposes metrics at path `/nginx_status`
└── monitor/
└── prometheus.yml
Defining a Grafana dashboard from scratch is time-consuming. You can import dashboards by ID from Grafanalabs and connect them to your own data source.
Discover, explore, and download dashboards: https://grafana.com/grafana/dashboards/
Nginx Dashboards for nginx-prometheus-exporter are fairly standard:
| Dashboard ID | Description |
|---|---|
| 12708 | Official Nginx exporter dashboard, designed for the exporter, 2020-07-26 |
| 12452 | Another dashboard designed explicitly for nginx-prometheus-exporter, 2022-11-21 |
This method is recommended by Deepseek :-) as it provides "comprehensive metrics out of the box with minimal configuration".
-
Use pip to install the
prometheus-fastapi-instrumentatorpackage. -
In
main.py(where the FastAPI object is created), add:from fastapi import FastAPI from prometheus_fastapi_instrumentator import Instrumentator app = FastAPI() # Instrument the application instrumentator = Instrumentator( <options> ) instrucmentor.instrument(app) instrumentator.expose(app)
3. (Optional) Advanced configuration.
```python
from fastapi import FastAPI, Request
from prometheus_fastapi_instrumentator import Instrumentator, metrics
app = FastAPI()
# Custom instrumentation with more metrics
instrumentator = Instrumentator(
should_group_status_codes=True,
should_ignore_untemplated=True,
should_respect_env_var=True,
should_instrument_requests_inprogress=True,
excluded_handlers=["/metrics", "/health"],
env_var_name="ENABLE_METRICS",
inprogress_name="inprogress",
inprogress_labels=True,
)
# Add default metrics
instrumentator.add(
metrics.request_size(
should_include_handler=True,
should_include_method=True,
should_include_status=True,
)
).add(
metrics.response_size(
should_include_handler=True,
should_include_method=True,
should_include_status=True,
)
).add(
metrics.latency(
bucket_type="exponential",
should_include_handler=True,
should_include_method=True,
should_include_status=True,
)
).add(
metrics.requests(
should_include_handler=True,
should_include_method=True,
should_include_status=True,
)
)
# Instrument the app
instrumentator.instrument(app)
instrumentator.expose(app)
Step 1: Install the client. Use pip to install prometheus-client package.
Step 2: Create custom metrics
from fastapi import FastAPI, Request, Response
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST, Counter, Histogram, Gauge
import time
app = FastAPI()
# Define metrics
REQUEST_COUNT = Counter(
'http_requests_total',
'Total HTTP Requests',
['method', 'endpoint', 'status_code']
)
REQUEST_LATENCY = Histogram(
'http_request_duration_seconds',
'HTTP Request Latency',
['method', 'endpoint']
)
IN_PROGRESS = Gauge(
'http_requests_in_progress',
'HTTP Requests in Progress',
['method', 'endpoint']
)
@app.middleware("http")
async def monitor_requests(request: Request, call_next):
method = request.method
endpoint = request.url.path
# Skip metrics endpoint
if endpoint == "/metrics":
return await call_next(request)
IN_PROGRESS.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
status_code = response.status_code
except Exception as e:
status_code = 500
raise e
finally:
latency = time.time() - start_time
REQUEST_LATENCY.labels(method=method, endpoint=endpoint).observe(latency)
REQUEST_COUNT.labels(method=method, endpoint=endpoint, status_code=status_code).inc()
IN_PROGRESS.labels(method=method, endpoint=endpoint).dec()
return response
@app.get("/metrics")
async def metrics():
return Response(
content=generate_latest(),
media_type=CONTENT_TYPE_LATEST
)This method also requires the prometheus-client package.
from fastapi import FastAPI, Request, Response, Depends
from prometheus_client import (
generate_latest, CONTENT_TYPE_LATEST, Counter, Histogram,
Gauge, Summary, REGISTRY, CollectorRegistry
)
import time
from typing import Callable
app = FastAPI(title="My FastAPI App")
# Custom metrics registry
registry = CollectorRegistry()
# Application metrics
REQUEST_COUNT = Counter(
'fastapi_requests_total',
'Total count of HTTP requests',
['method', 'endpoint', 'status_code'],
registry=registry
)
REQUEST_DURATION = Histogram(
'fastapi_request_duration_seconds',
'HTTP request duration in seconds',
['method', 'endpoint'],
buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
registry=registry
)
REQUESTS_IN_PROGRESS = Gauge(
'fastapi_requests_in_progress',
'Number of HTTP requests in progress',
['method', 'endpoint'],
registry=registry
)
ACTIVE_USERS = Gauge(
'fastapi_active_users',
'Number of active users',
registry=registry
)
# Business logic metrics
ITEMS_CREATED = Counter(
'fastapi_items_created_total',
'Total number of items created',
registry=registry
)
ERROR_COUNT = Counter(
'fastapi_errors_total',
'Total number of errors',
['error_type'],
registry=registry
)
@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
method = request.method
endpoint = request.url.path
if endpoint == "/metrics":
return await call_next(request)
REQUESTS_IN_PROGRESS.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
status_code = response.status_code
except Exception as e:
status_code = 500
ERROR_COUNT.labels(error_type=type(e).__name__).inc()
raise e
finally:
duration = time.time() - start_time
REQUEST_DURATION.labels(method=method, endpoint=endpoint).observe(duration)
REQUEST_COUNT.labels(method=method, endpoint=endpoint, status_code=status_code).inc()
REQUESTS_IN_PROGRESS.labels(method=method, endpoint=endpoint).dec()
return response
@app.get("/metrics")
async def metrics():
return Response(
content=generate_latest(registry),
media_type=CONTENT_TYPE_LATEST
)Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
EXPOSE 8001 # For metrics endpoint if on different port
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Docker Compose Configuration File
version: '3.8'
services:
fastapi-app:
build: .
ports:
- "8000:8000"
environment:
- PROMETHEUS_MULTIPROC_DIR=/tmp
volumes:
- /tmp:/tmp
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./monitor/prometheus.yml:/etc/prometheus/prometheus.yml# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'fastapi-app'
static_configs:
- targets: ['fastapi-app:8000']
metrics_path: '/metrics'
scrape_interval: 10suvicorn main:app --host 0.0.0.0 --port 8000curl http://localhost:8000/metrics# Make some requests to generate metrics
curl http://localhost:8000/
curl http://localhost:8000/items/1
curl http://localhost:8000/healthWith this setup, you'll get metrics like:
-
http_requests_total- Request counts by method, endpoint, status -
http_request_duration_seconds- Request latency distribution -
http_requests_in_progress- Current in-progress requests - Custom business metrics