Package: prometheusrock
PrometheusRockPrometheus middleware for Starlette and FastAPIThis middleware collects couple of basic metrics and allow you to add your own ones.Basic metrics:Counter: requests_totalHistogram: request_processing_timeBasic labels for them:methodpathstatus_codeUser-AgentandHostheadersapplication nameExample: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.00036406517028808594Metrics include labels for the HTTP method, the path, and the response status code.Set for path/metricshandlermetrics_routeand your metrics will be exposed on that url for Prometheus further use.Usage1. I don't want anything custom, just give me the basics!If you don't want nothing extra, this is for you. Grab the code and run to paste it!ForstarletteandFastAPIinit part pretty similar.First:pip install prometheusrockSecond:Choose your fighter!
If you're using starlette:fromstarlette.applicationsimportStarletteAnd if you're using FastAPI:fromfastapiimportFastAPIMoving further:fromprometheusrockimportPrometheusMiddleware,metrics_routeapp=# Starlette() or FastAPI()app.add_middleware(PrometheusMiddleware)app.add_route("/metrics",metrics_route)...And that's it! Now go on/metricsand see your logs!2. Custom you say? Let me see...but just a little!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-agentandhost)
you can passlist(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.
If you don't wanna some of them - passlistwith their names here. And their gone!skip_paths- sometimes you don't wanna log some of the endpoint.
(Fore example you don't wanna log accesses to/metricsin your metrics).
If you want to exclude this paths from metric - pass herelistwith their urls.
By default this middleware ignores/metricsroute,
so if you initially moved your metric route to some other url - pass it here.
If you want to log all routes (even the default/metrics- pass an empty list!)disable_default_counter- if you want to disable default Counter metric - passTruevalue to this optional param.disable_default_histogram- if you want to disable default Histogram metric - passTruevalue to this optional param.custom_base_labels- if you want change default labels to yours - pass them here.REWRITES DEFAULT LABLES. Argsremove_labelsWILL BE IGNORED.example -['path','method']- and you have metric, that contains onlypathandmethodlabels.custom_base_headers- if you want change default headers to yours - pass them here.REWRITES DEFAULT HEADERS. Argsadditional_headersWILL BE IGNORED.
If you usecustom_base_labels, don't forget to passheadersin it,
otherwisecustom_base_headerswill have no effect.example -['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,
your logs will quickly overflow, showing you huge amount of numbers, when, in fact,
endpoint is one. So pass here list of endpoints path to aggregate by.
example -['/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 namethis_is_my_app, we want to track headeraccept-encoding, we don't wanna labelpath(if you have one endpoint for example),
and we don't want url/_healthcheckto 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.0Let's go deeper! Add your own custom metric!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 functionasyncdefquery(middleware_proxy):res=awaitdb.execute_query("SELECT COUNT(*) as count from MyTable")middleware_proxy.metric.labels(**res)FunctionMUSTaccept 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:fromprometheusrockimportMetric...asyncdefquery(middleware_proxy:Metric):Metric have 3 attributes:metric - instance ofprometheus_clientmetric object.metric_type - string with type.spent_time - time, that was spent on request. You may need it if you, for example, implementing Histogram metric.request - request object from app.And nowIMPORTANTremark - youmustcorrectly invoke metric!
So if you, for example, choseCountermetric, in your custom function you must domiddleware_proxy.metric.labels(**res).inc(),
or if you chose Histogram -middleware_proxy.metric.labels(**res).observe(middleware_proxy.spent_time)and so on,
according tothis 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 labelscountandid,resmust be
a dictionary{"count": count, "id": id}or list with right positioning -[count, id].And finally we tell our middleware about our custom metric:fromprometheusrockimportAddMetric,PrometheusMiddleware...app.add_middleware(PrometheusMiddleware)...# async here isn't necessary, you can use ordinary functionasyncdefquery(middleware_proxy):res=awaitdb.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:function - function that will work as your metric logicmetric_name - unique metric name, must be ONE-WORDED (e.g. unique_metric_name). Default - "user_metric".metric_description- description of your metric. Default- "description of user metric".labels - list of lables that you want your metric to contain. Default - ["info"].metric_type - one ofprometheus_clientmetric types - described in paragraph 1.Links and dependenciesDependencies:Starlette,client_pythonAdditional links:FastAPI