> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Instrumentation basics

> Guide to adding Prometheus metrics to a Flask application, creating counters, exposing metrics via separate server or WSGI middleware, and counting requests safely.

In this lesson/article we'll build a small dummy application and walk through how to add Prometheus instrumentation. We'll use Flask (a lightweight Python web framework) to create a few endpoints and then add metrics using the `prometheus_client` library.

## Minimal Flask app

Here is a minimal Flask app that exposes a single GET endpoint at `"/cars"`:

```python theme={null}
from flask import Flask

app = Flask(__name__)

@app.get("/cars")
def get_cars():
    return ["toyota", "honda", "mazda", "lexus"]

if __name__ == "__main__":
    app.run(port=5001)
```

Focus on the decorator `@app.get("/cars")`: any GET request to `"/cars"` will execute the `get_cars` function. The app is started on port `5001`.

## Install the Prometheus client

Install the Python client library:

```bash theme={null}
pip install prometheus_client
```

## Add a Counter metric

Import and create a Prometheus `Counter` to track total requests:

```python theme={null}
from prometheus_client import Counter

REQUESTS = Counter("http_requests_total", "Total number of requests")
```

## Increment the counter in a handler

Increment the counter inside the request handler so it increases on every request:

```python theme={null}
from flask import Flask
from prometheus_client import Counter

REQUESTS = Counter("http_requests_total", "Total number of requests")

app = Flask(__name__)

@app.get("/cars")
def get_cars():
    REQUESTS.inc()
    return ["toyota", "honda", "mazda", "lexus"]

if __name__ == "__main__":
    app.run(port=5001)
```

## Expose metrics with a separate HTTP server

The simplest way to expose metrics is to use `start_http_server` from the Prometheus client. This starts a separate HTTP server that serves all registered metrics (including the default Python metrics):

```python theme={null}
from prometheus_client import Counter, start_http_server
from flask import Flask

REQUESTS = Counter("http_requests_total", "Total number of requests")

app = Flask(__name__)

@app.get("/cars")
def get_cars():
    REQUESTS.inc()
    return ["toyota", "honda", "mazda", "lexus"]

if __name__ == "__main__":
    # Start Prometheus metrics server on port 8000
    start_http_server(8000)
    # Start Flask app on port 5001
    app.run(port=5001)
```

When run this way:

* Flask App: port `5001`
* Prometheus metrics endpoint: port `8000`

You can fetch the metrics with curl:

```plaintext theme={null}
$ curl 127.0.0.1:8000
# HELP python_gc_objects_uncollectable_total Uncollectable object found during GC
# TYPE python_gc_objects_uncollectable_total counter
python_gc_objects_uncollectable_total{generation="0"} 0.0
python_gc_objects_uncollectable_total{generation="1"} 0.0
python_gc_objects_uncollectable_total{generation="2"} 0.0
# HELP python_gc_collections_total Number of times this generation was collected
# TYPE python_gc_collections_total counter
python_gc_collections_total{generation="0"} 77.0
python_gc_collections_total{generation="1"} 7.0
python_gc_collections_total{generation="2"} 0.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="9",patchlevel="5",version="3.9.5"} 1.0
# HELP http_requests_total Total number of requests
# TYPE http_requests_total counter
http_requests_total 5.0
# HELP http_requests_created Total number of requests
# TYPE http_requests_created gauge
http_requests_created 1.6654499091926205e+09
```

## Expose metrics via Flask (same port)

If you prefer to expose metrics on the same port as your Flask app (e.g., at `http://localhost:5001/metrics`), use the WSGI helper `make_wsgi_app` and `DispatcherMiddleware` from `werkzeug`:

```python theme={null}
from flask import Flask
from prometheus_client import make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware

app = Flask(__name__)

# Add Prometheus middleware to export metrics at /metrics
app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {
    "/metrics": make_wsgi_app()
})

if __name__ == "__main__":
    app.run(port=5001)
```

This makes the Prometheus metrics available at `http://localhost:5001/metrics`. Use whichever approach fits your deployment model: a separate metrics server (good for simple setups) or WSGI middleware (keeps everything on one port).

## Multi-route application and counting across all endpoints

Let's expand the app with additional endpoints and show two ways to ensure `http_requests_total` counts requests across your entire application.

1. Manual increments in each handler:

```python theme={null}
from flask import Flask
from prometheus_client import Counter, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware

REQUESTS = Counter("http_requests_total", "Total number of requests")

app = Flask(__name__)

# Expose metrics at /metrics on the same Flask port
app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {
    "/metrics": make_wsgi_app()
})

@app.get("/cars")
def get_cars():
    REQUESTS.inc()
    return ["toyota", "honda", "mazda", "lexus"]

@app.get("/cars/<int:id>")
def get_car(id):
    REQUESTS.inc()
    return f"Single car {id}"

@app.post("/cars")
def create_car():
    REQUESTS.inc()
    return "Create Car"

@app.patch("/cars/<int:id>")
def update_car(id):
    REQUESTS.inc()
    return f"Updating Car {id}"

@app.delete("/cars/<int:id>")
def delete_car(id):
    REQUESTS.inc()
    return f"Deleting Car {id}"

if __name__ == "__main__":
    app.run(port=5001)
```

2. Increment once for every incoming request using a global hook (recommended to avoid repeating increments). If you expose metrics at `"/metrics"` via the same Flask app (for example, if `/metrics` is handled by Flask), make sure you do not count requests to `/metrics` itself. If you mount the Prometheus WSGI app via `DispatcherMiddleware`, requests to `/metrics` are handled by the metrics WSGI app and won't trigger Flask `before_request` hooks:

```python theme={null}
from flask import Flask, request
from prometheus_client import Counter, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware

REQUESTS = Counter("http_requests_total", "Total number of requests")

app = Flask(__name__)
app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {
    "/metrics": make_wsgi_app()
})

@app.before_request
def increment_request_counter():
    # Avoid counting the /metrics endpoint itself
    if request.path != "/metrics":
        REQUESTS.inc()

@app.get("/cars")
def get_cars():
    return ["toyota", "honda", "mazda", "lexus"]

@app.get("/cars/<int:id>")
def get_car(id):
    return f"Single car {id}"

@app.post("/cars")
def create_car():
    return "Create Car"

@app.patch("/cars/<int:id>")
def update_car(id):
    return f"Updating Car {id}"

@app.delete("/cars/<int:id>")
def delete_car(id):
    return f"Deleting Car {id}"

if __name__ == "__main__":
    app.run(port=5001)
```

## Callouts

<Callout icon="lightbulb" color="#1CB2FE">
  If you use `start_http_server`, the metrics endpoint is a separate HTTP server (different port) and will not be handled by Flask middleware or Flask hooks like `before_request`. If you want metrics to be part of the same Flask process and port, use `make_wsgi_app` with `DispatcherMiddleware`.
</Callout>

<Callout icon="warning" color="#FF6B6B">
  If you expose metrics at `"/metrics"` on the same Flask app, ensure you do not inadvertently count `/metrics` requests in your application metrics unless that is intentional.
</Callout>

## Summary

* Create and register Prometheus metrics (e.g., `Counter`, `Gauge`, `Histogram`) using `prometheus_client`.
* Increment metrics where appropriate (inside handlers, or globally via `before_request`).
* Expose metrics either with `start_http_server` (separate port) or with `make_wsgi_app` + `DispatcherMiddleware` (same port at `"/metrics"`).
* Avoid double-counting metrics (for example, counting scrapes of the `/metrics` endpoint itself).

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/prometheus-certified-associate-pca/module/0c0155c7-00c8-4ca2-a061-e66baa1a3216/lesson/f79526d0-2745-40a9-ab84-5b8a9acaab25" />
</CardGroup>
