Monitoring Flask microservices with Prometheus

A couple of lines of code and your application generates metrics, wow!

In order to understand how prometheus works_flask_exporter a minimal example is enough:

from flask import Flask
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)
metrics = PrometheusMetrics(app)

@app.route('/')
def main():
    return 'OK'

That's all you need to get started! By adding an import and a line for initialization Prometheus Metrics, you will get metrics request duration и request countersdisplayed at the endpoint /metrics the Flask app it's registered with, plus any default metrics you get from the base Prometheus client library.

you can find easy to use example in the GitHub repository that launches the instance Prometheus и grafana along with a demo application for generating metrics that would look something like this:

Monitoring Flask microservices with Prometheus

You will also find a list of indicators in README examples that appear in the dashboard, along with Prometheus queries that populate the dashboards.

Setting

The library has a lot of configuration options, have a look at README project their examples with a brief explanation.

The basic configuration is shown above. Just instantiate Prometheus Metrics, let's call it metrics, and then use it to define additional metrics you want to collect by decorating functions:

  • @metrics.counter(..)

  • @metrics.gauge(..)

  • @metrics.summary(..)

  • @metrics.histogram(..)

The counters count calls, and the rest collect metrics based on the duration of those calls. You can define labels for each of these, potentially using request or response properties. For example:

from flask import Flask, request
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)

# group by endpoint rather than path
metrics = PrometheusMetrics(app, group_by='endpoint')

@app.route('/collection/:collection_id/item/:item_id')
@metrics.counter(
    'cnt_collection', 'Number of invocations per collection', labels={
        'collection': lambda: request.view_args['collection_id'],
        'status': lambda resp: resp.status_code
    })
def get_item_from_collection(collection_id, item_id):
    pass

In the example above, clicking on the endpoint /collection/10002/item/76 will cause the counter to increment, e.g. cnt_collection{collection = "10002", status = "200"}, plus you'll get the default metrics (for each endpoint in this example) from the default library:

  • flask_http_request_duration_seconds - HTTP request duration in seconds for all Flask requests by method, path and status

  • flask_http_request_total — Total number of HTTP requests by methods and statuses

There are options to skip tracking certain endpoints, register additional default metrics or skip the ones above, or apply the same custom metric to multiple endpoints. Check out README project to see what's available.

app = Flask(__name__)
metrics = PrometheusMetrics(app)

@app.route('/')
def main():
    pass  # requests tracked by default

@app.route('/skip')
@metrics.do_not_track()
def skip():
    pass  # default metrics are not collected

# custom metric to be applied to multiple endpoints
common_counter = metrics.counter(
    'by_endpoint_counter', 'Request count by endpoints',
    labels={'endpoint': lambda: request.endpoint}
)

@app.route('/common/one')
@common_counter
def endpoint_one():
    pass  # tracked by the custom and the default metrics

@app.route('/common/two')
@common_counter
def endpoint_two():
    pass  # also tracked by the custom and the default metrics

# register additional default metrics
metrics.register_default(
    metrics.counter(
        'by_path_counter', 'Request count by request paths',
        labels={'path': lambda: request.path}
    )
)

The library has handy extensions for popular multiprocessor libraries such as uWSGI and Gunicorn. You can also find small examples of target use cases, including multiprocessing ones.

Collection of metrics

As mentioned above, the default library provides an endpoint /metrics in a Flask application, which can serve as a target for assembler Prometheus.

In the dashboard example above, you can target your Prometheus to a Flask app with default settings like this:

scrape_configs:
  - job_name: 'example'

    dns_sd_configs:
      - names: ['app']
        port: 5000
        type: A
        refresh_interval: 5s

See full example at GitHub repos. This assumes that Prometheus can find your Flask application instances at http://app:5000/metrics, where the application domain name could potentially resolve to multiple IP addresses, such as when running in Kubernetes or Docker swarm.

If exposing the metrics endpoint like this doesn't suit you, perhaps because you don't want to allow external access to it, you can easily disable it by passing path=None when instantiating Prometheus Metrics.

from flask import Flask, request
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)
metrics = PrometheusMetrics(app, path=None)

...

metrics.start_http_server(5099)

Then you can use start_http_server(port)to open this endpoint on a different HTTP port, 5099 in the above example. Alternatively, if you're happy with the endpoint being in the same Flask app, but need to change its path from /metrics, you can either pass a different URI as the path parameter or use register_endpoint(..)to install it later.

Links

If you decide to give it a try, feel free to open an issue on GitHub or leave your comments, feedback, and suggestions!

Thank you!

Source: habr.com