Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using prometheus_flask_exporter with ASGI Flask #183

Open
chorus-over-flanger opened this issue Dec 3, 2024 · 3 comments
Open

Using prometheus_flask_exporter with ASGI Flask #183

chorus-over-flanger opened this issue Dec 3, 2024 · 3 comments

Comments

@chorus-over-flanger
Copy link

Hello @rycus86 !
I'm trying to connect prometheus_flask_exporter to the following ASGI Flask app,

# app.py
from asgiref.wsgi import WsgiToAsgi
from flask import Flask, Blueprint

from prometheus_client import CollectorRegistry
from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics

app = Flask(__name__)

metrics = GunicornInternalPrometheusMetrics.for_app_factory(
    registry=CollectorRegistry(
        target_info={},
    )
)
time_summary = metrics.summary("time_summary", "Time spent processing requests.")

# health_check Blueprint
health_check_bp = Blueprint('health_check', __name__)
@health_check_bp.route('/health')
@time_summary
async def health_check():
    return 'OK'


app.register_blueprint(health_check_bp)
metrics.init_app(app)

# Gunicorn configuration
asgi_app = WsgiToAsgi(app)

it started normally with,

mkdir -p prometheus
PROMETHEUS_MULTIPROC_DIR=prometheus gunicorn app:asgi_app -b 0.0.0.0:8000 --workers 2 -k 'uvicorn.workers.UvicornWorker'

but when requested with,

curl 0.0.0.0:8000/health -v

it gives the error,

TypeError: The view function did not return a valid response. The `return` type must be a string, dict, list, tuple with headers or status, Response instance, or WSGI callable, but it was a coroutine.

Internally decoration logic is implemented in _track, and the following simple patching resolves the error with coroutine handling,

def _track(self, metric_type, metric_call, metric_kwargs, name, description, labels,
               initial_value_when_only_static_labels, registry, before=None, revert_when_not_tracked=None):
        ...
        def decorator(f):
            @wraps(f)
            async def func(*args, **kwargs):
                ...
                try:
                        # execute the handler function
                        response = await f(*args, **kwargs)
                    except Exception as ex:
                        # let Flask decide to wrap or reraise the Exception
                        response = current_app.handle_user_exception(ex)
                ....

after patching for the above example app both /health and /metrics respond correctly, metrics aggregated

So Its working, but I wonder if this can lead to unexpected increase of latency or any other drawbacks? Maybe there is another way to connect prometheus_flask_exporter to ASGI server? Any other advices are also appreciated!

Thanks!

@rycus86
Copy link
Owner

rycus86 commented Dec 3, 2024

Hi,

I'm not too familiar with ASGI and Uvicorn, I wonder if we're just missing support for the coroutine responses that we could add?
Is that what you patch does basically?

@chorus-over-flanger
Copy link
Author

I wonder if we're just missing support for the coroutine responses that we could add? ... Is that what you patch does basically?

Yes, patching the code this way makes the coroutine response handling correct, though it seems like a surprisingly simple patch, so I'm not fully confident it addresses all edge cases.

@rycus86
Copy link
Owner

rycus86 commented Dec 8, 2024

I'm thinking perhaps we should subclass GunicornInternalPrometheusMetrics (or another appropriate class) that adds Uvicorn support, and then that can have this complication. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants