Prometheus middleware for Starlette and FastAPI
MIT License
Prometheus middleware for Starlette and FastAPI
This middleware collects couple of basic metrics and allow you to add your own ones.
Basic metrics:
Basic labels for them:
User-Agent
and Host
headersExample:
request_processing_time_sum{app_name="test_app",headers="{'host': '127.0.0.1:8020', 'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'}",path="/test",status_code="200"} 0.00036406517028808594
Metrics include labels for the HTTP method, the path, and the response status code.
Set for path /metrics
handler metrics_route
and your metrics will be exposed on that url for Prometheus further use.
If you don't want nothing extra, this is for you. Grab the code and run to paste it!
For starlette and FastAPI init part pretty similar.
First:
pip install prometheusrock
Second:
Choose your fighter! If you're using starlette:
from starlette.applications import Starlette
And if you're using FastAPI:
from fastapi import FastAPI
Moving further:
from prometheusrock import PrometheusMiddleware, metrics_route
app = # Starlette() or FastAPI()
app.add_middleware(PrometheusMiddleware)
app.add_route("/metrics", metrics_route)
...
And that's it! Now go on /metrics
and see your logs!
If you want to configure basic metrics let me show you how!
When you declare middleware, you can pass following args:
app_name
- the name you want to show in metrics as the name of your app. Default - "ASGIApp",additional_headers
- if you want to track additional headers (aside of default ones - user-agent
and host
)list
(that's important!) with names of that headers. They all cast to lowercase, so casing doesn't matters.remove_labels
- by default basic metrics labels are following: method
, path
, status_code
, headers
, app_name
.list
with their names here. And their gone!skip_paths
- sometimes you don't wanna log some of the endpoint./metrics
in your metrics).list
with their urls./metrics
route,/metrics
- pass an empty list!)disable_default_counter
- if you want to disable default Counter metric - pass True
value to this optional param.disable_default_histogram
- if you want to disable default Histogram metric - pass True
value to this optional param.custom_base_labels
- if you want change default labels to yours - pass them here.remove_labels
WILL BE IGNORED.['path','method']
- and you have metric, that contains only path
and method
labels.custom_base_headers
- if you want change default headers to yours - pass them here.additional_headers
WILL BE IGNORED.custom_base_labels
, don't forget to pass headers
in it,custom_base_headers
will have no effect.['content-type','x-api-client']
- and now you write only these two headers.aggregate_paths
- if you have endpoints like /item/{id}
, then, by default,['/item/']
But a picture is worth a thousand words, right? Let's see some code!
For example, we want our middleware to have a following settings:
we want a name this_is_my_app
, we want to track header accept-encoding
, we don't wanna label path
(if you have one endpoint for example),
and we don't want url /_healthcheck
to be tracked.
app.add_middleware(
PrometheusMiddleware,
app_name='this_is_my_app',
additional_headers=['accept-encoding'],
remove_labels=['path'],
skip_paths=['/_healthcheck']
)
And after that, our metric will look something like that:
requests_total{app_name="this_is_my_app",headers="{'host': '127.0.0.1:8000', 'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0', 'accept-encoding': 'gzip, deflate'}",method="GET",status_code="200"} 1.0
And the star of the evening - custom metrics! So, lets suppose you want to check how many are rows in your Database after each request. Let's explore this:
First, we do all the same things - we initiate the app, we add PrometheusMiddleware. And the next steps are:
We must decide what type of metric we want - choose one from here. Basically, you will need pass one of the types - info, gauge, counter, histogram, summary, enum
.
We declare the function that will act like our metric logic:
# async here isn't necessary, you can use ordinary function
async def query(middleware_proxy):
res = await db.execute_query(
"SELECT COUNT(*) as count from MyTable"
)
middleware_proxy.metric.labels(**res)
Function MUST accept this argument. Obviously you can name it however you want,
as long is it still there. If you want to know what's inside -
from prometheusrock import Metric
. I strongly recommend to pass it as typehinting:
from prometheusrock import Metric
...
async def query(middleware_proxy: Metric):
Metric have 3 attributes:
prometheus_client
metric object.And now IMPORTANT remark - you must correctly invoke metric!
So if you, for example, chose Counter
metric, in your custom function you must do middleware_proxy.metric.labels(**res).inc()
,
or if you chose Histogram - middleware_proxy.metric.labels(**res).observe(middleware_proxy.spent_time)
and so on,
according to this docs.
Value that you're passing there - res
(or however you called it) must be a sequence of the parameters,
that you set as lables for your metric. For example, if your metric have labels count
and id
, res
must be
a dictionary {"count": count, "id": id}
or list with right positioning - [count, id]
.
And finally we tell our middleware about our custom metric:
from prometheusrock import AddMetric, PrometheusMiddleware
...
app.add_middleware(PrometheusMiddleware)
...
# async here isn't necessary, you can use ordinary function
async def query(middleware_proxy):
res = await db.execute_query(
"SELECT COUNT(*) as count from MyTable"
)
middleware_proxy.metric.labels(**res)
AddMetric(
function=query,
metric_name='my_precious',
metric_type='info',
labels=['row_count']
)
AddMetric accept following params:
prometheus_client
metric types - described in paragraph 1.Dependencies: Starlette, client_python
Additional links: FastAPI