diff --git a/README.md b/README.md index 9ccb46f..49f883b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ or paste it into requirements.txt: prometheus-flask-exporter # or with specific version number -prometheus-flask-exporter==0.18.2 +prometheus-flask-exporter==0.18.3 ``` and then install dependencies from requirements.txt file as usual: ``` @@ -150,10 +150,11 @@ You can avoid recording metrics on individual endpoints by decorating them with `@metrics.do_not_track()`, or use the `excluded_paths` argument when creating the `PrometheusMetrics` instance that takes a regular expression (either a single string, or a list) and -matching paths will be excluded. If you have functions that are inherited -or otherwise get metrics collected that you don't want, you can use -`@metrics.exclude_all_metrics()` to exclude both default and non-default -metrics being collected from it. +matching paths will be excluded. These apply to both built-in and user-defined +default metrics, unless you disable it by setting the `exclude_user_defaults` +argument to `False`. If you have functions that are inherited or otherwise get +metrics collected that you don't want, you can use `@metrics.exclude_all_metrics()` +to exclude both default and non-default metrics being collected from it. ## Configuration diff --git a/prometheus_flask_exporter/__init__.py b/prometheus_flask_exporter/__init__.py index 98cdd06..87e0ba6 100644 --- a/prometheus_flask_exporter/__init__.py +++ b/prometheus_flask_exporter/__init__.py @@ -1,18 +1,18 @@ +import functools +import inspect import os import re import sys -import inspect -import warnings -import functools import threading +import warnings from timeit import default_timer -from flask import request, make_response, current_app from flask import Flask, Response +from flask import request, make_response, current_app from flask.views import MethodViewType -from werkzeug.serving import is_running_from_reloader from prometheus_client import Counter, Histogram, Gauge, Summary from prometheus_client.exposition import choose_encoder +from werkzeug.serving import is_running_from_reloader if sys.version_info[0:2] >= (3, 4): # Python v3.4+ has a built-in has __wrapped__ attribute @@ -102,12 +102,18 @@ def echo_status(status): - Without an argument, possibly to use with the Flask `request` object """ - def __init__(self, app, path='/metrics', - export_defaults=True, defaults_prefix='flask', - group_by='path', buckets=None, + def __init__(self, app, + path='/metrics', + export_defaults=True, + defaults_prefix='flask', + group_by='path', + buckets=None, default_latency_as_histogram=True, - default_labels=None, response_converter=None, - excluded_paths=None, metrics_decorator=None, + default_labels=None, + response_converter=None, + excluded_paths=None, + exclude_user_defaults=True, + metrics_decorator=None, registry=None, **kwargs): """ Create a new Prometheus metrics export configuration. @@ -135,6 +141,8 @@ def __init__(self, app, path='/metrics', metrics endpoint, takes a function and needs to return a function :param excluded_paths: regular expression(s) as a string or a list of strings for paths to exclude from tracking + :param exclude_user_defaults: also apply the `excluded_paths` + exclusions to user-defined defaults (not only built-in ones) :param registry: the Prometheus Registry to use """ @@ -194,6 +202,8 @@ def __init__(self, app, path='/metrics', else: self.excluded_paths = None + self.exclude_user_defaults = exclude_user_defaults + if app is not None: self.init_app(app) @@ -623,6 +633,11 @@ def get_metric(response): def decorator(f): @wraps(f) def func(*args, **kwargs): + if self.exclude_user_defaults and self.excluded_paths: + # exclude based on default excludes + if any(pattern.match(request.path) for pattern in self.excluded_paths): + return f(*args, **kwargs) + if before: metric = get_metric(None) before(metric) @@ -915,4 +930,4 @@ def _make_response(response): return _make_response -__version__ = '0.18.2' +__version__ = '0.18.3' diff --git a/setup.py b/setup.py index 547ce72..37b63fd 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='prometheus_flask_exporter', packages=['prometheus_flask_exporter'], - version='0.18.2', + version='0.18.3', description='Prometheus metrics exporter for Flask', long_description=long_description, long_description_content_type='text/markdown', @@ -14,7 +14,7 @@ author='Viktor Adam', author_email='rycus86@gmail.com', url='https://github.com/rycus86/prometheus_flask_exporter', - download_url='https://github.com/rycus86/prometheus_flask_exporter/archive/0.18.2.tar.gz', + download_url='https://github.com/rycus86/prometheus_flask_exporter/archive/0.18.3.tar.gz', keywords=['prometheus', 'flask', 'monitoring', 'exporter'], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/test_endpoint.py b/tests/test_endpoint.py index d95cefa..1008427 100644 --- a/tests/test_endpoint.py +++ b/tests/test_endpoint.py @@ -406,3 +406,40 @@ def excluded_two(): 'flask_http_request_duration_seconds_count', ('method', 'GET'), ('status', 200), ('path', '/exc/two') ) + + def test_exclude_paths_from_user_metrics(self): + metrics = self.metrics(excluded_paths='/excluded', exclude_user_defaults=True) + + @self.app.route('/included') + def included(): + return 'OK' + + @self.app.route('/excluded') + def excluded(): + return 'OK' + + metrics.register_default( + metrics.counter( + name='by_path_counter', + description='Request count by path', + labels={'path': lambda: request.path} + ) + ) + + for _ in range(5): + self.client.get('/included') + self.client.get('/excluded') + + self.assertMetric( + 'flask_http_request_total', 5.0, + ('method', 'GET'), ('status', 200) + ) + + self.assertMetric( + 'by_path_counter_total', 5.0, + ('path', '/included') + ) + self.assertAbsent( + 'by_path_counter_total', + ('path', '/excluded') + )