client_python icon indicating copy to clipboard operation
client_python copied to clipboard

ASGI mounted to FastAPI mounts metrics to `/metrics/` instead of `/metrics`

Open hmellor opened this issue 1 year ago • 8 comments

The documentation gives an example application which mounts the ASGI app to /metrics in a FastAPI app

from fastapi import FastAPI
from prometheus_client import make_asgi_app

# Create app
app = FastAPI(debug=False)

# Add prometheus asgi middleware to route /metrics requests
metrics_app = make_asgi_app()
app.mount("/metrics", metrics_app)

and says that you should be able to see the metrics at http://localhost:8000/metrics, however if you try to access this endpoint

$ uvicorn app:app
INFO:     Started server process [1620866]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:34428 - "GET / HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:34428 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:34444 - "GET /metrics HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:34444 - "GET /metrics/ HTTP/1.1" 200 OK

you get redirected to /metrics/.

It would be better if this redirect could be avoided.

hmellor avatar Mar 15 '24 09:03 hmellor

@kakkoyun this might be something to look at to learn more about the project as well.

csmarchbanks avatar Mar 21 '24 17:03 csmarchbanks

Thanks for pointing this out @hmellor. I was am doing something similar but got an "Method Not Allowed" when doing a GET request for http://localhost:8000/metrics. I am using hypercorn but I do not get the redirect. However, this does seem to work

curl http://localhost:8000/metrics/ 

Sakoes avatar Mar 28 '24 16:03 Sakoes

I think I found the root cause. It has to do with how FastApi and Starlette (which FastApi is based on) are mounting sub-applications. A metrics sub-application is created as follows:

metrics_app = make_asgi_app()
app.mount("/metrics", metrics_app)

And from FastApi and Starlette docs you can read that sub-applications are mounted under a prefix, in this case "/metrics". So when you want to actually access this sub-application I guess a new root endpoint is automatically added thus creating "/metrics/". Not sure if or how you could fix this, but it looks like this is intended behavior when running the prometheus client as a FastApi sub-application.

Here you can find the docs:

Sakoes avatar Mar 28 '24 16:03 Sakoes

In aioprometheus (a third party Prometheus client) they use add_route to expose the metrics and they don't have this problem

from aioprometheus.asgi.starlette import metrics
app.add_route("/metrics", metrics)

hmellor avatar Mar 28 '24 17:03 hmellor

Yes, that is exactly the cause. In this client the make_asgi_app() creates a sub-application that is mounted with app.mount() thus creating a "/metrics" prefix vs an actual route. While the aioprometheus.asgi.starlette implementation is adding a route directly with app.add_route(). So "/metrics" is the actual route and not a prefix.

Sakoes avatar Mar 28 '24 19:03 Sakoes

This workaround appears to work (changes route.path_regex from ^/metrics/(?P<path>.*)$ to ^/metrics(?P<path>.*)$):

import re
from starlette.routing import Mount

...

# Add prometheus asgi middleware to route /metrics requests
route = Mount("/metrics", make_asgi_app())
route.path_regex = re.compile('^/metrics(?P<path>.*)$')
app.routes.append(route)

hmellor avatar May 01 '24 10:05 hmellor

@csmarchbanks @kakkoyun has this been fixed yet?

hmellor avatar Feb 04 '25 19:02 hmellor

@csmarchbanks This issue still seems to persist in the latest version. Is there an acceptable path forward for what a fix would look like? The workflow @hmellor describes here and the solution described above seem to be the most straightforward way for a fix. Is there any reason in particular this hasn't been prioritized yet?

vrdn-23 avatar May 23 '25 08:05 vrdn-23