Skip to content

Commit

Permalink
Add Request exceptions to default metrics
Browse files Browse the repository at this point in the history
Counts exceptions arriving at ``teardown_request``.

It currently does not allow tracking further http
codes in range of 405-5xx.
  • Loading branch information
nlsdfnbch authored and rycus86 committed Aug 26, 2020
1 parent e9849b6 commit ab43abd
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 21 deletions.
59 changes: 41 additions & 18 deletions prometheus_flask_exporter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ def export_defaults(self, buckets=None, group_by='path',
"""
Export the default metrics:
- HTTP request latencies
- HTTP request exceptions
- Number of HTTP requests
:param buckets: the time buckets for request latencies
Expand Down Expand Up @@ -353,18 +354,26 @@ def export_defaults(self, buckets=None, group_by='path',

labels = self._get_combined_labels(None)

histogram = Histogram(
request_duration_metric = Histogram(
'%shttp_request_duration_seconds' % prefix,
'Flask HTTP request duration in seconds',
('method', duration_group_name, 'status') + labels.keys(),
registry=self.registry,
**buckets_as_kwargs
)

counter = Counter(
counter_labels = ('method', 'status') + labels.keys()
request_total_metric = Counter(
'%shttp_request_total' % prefix,
'Total number of HTTP requests',
('method', 'status') + labels.keys(),
counter_labels,
registry=self.registry
)

request_exceptions_metric = Counter(
'%shttp_request_exceptions_total' % prefix,
'Total number of HTTP requests which resulted in an exception',
counter_labels,
registry=self.registry
)

Expand All @@ -387,16 +396,16 @@ def after_request(response):
else:
group = getattr(request, duration_group)

histogram_labels = {
request_duration_labels = {
'method': request.method,
'status': _to_status_code(response.status_code),
duration_group_name: group
}
histogram_labels.update(labels.values_for(response))
request_duration_labels.update(labels.values_for(response))

histogram.labels(**histogram_labels).observe(total_time)
request_duration_metric.labels(**request_duration_labels).observe(total_time)

counter.labels(
request_total_metric.labels(
method=request.method, status=_to_status_code(response.status_code),
**labels.values_for(response)
).inc()
Expand All @@ -411,26 +420,40 @@ def teardown_request(exception=None):
if any(pattern.match(request.path) for pattern in self.excluded_paths):
return

response = make_response('Exception: %s' % exception, 500)
# Check if the exception was raised using a response object and use
# its status_code if present. This will only work for ``werkzeug.exceptions.HTTPException``
# and subclasses thereof. Otherwise we assume it's a 500.
try:
response = exception.get_response()
status_code = _to_status_code(response.status_code)
except:
status_code = 500
finally:
response = make_response('Exception: %s' % exception, status_code)

if callable(duration_group):
group = duration_group(request)
else:
group = getattr(request, duration_group)

request_exceptions_metric.labels(
method=request.method, status=status_code,
**labels.values_for(response)
).inc()

if hasattr(request, 'prom_start_time'):
total_time = max(default_timer() - request.prom_start_time, 0)

if callable(duration_group):
group = duration_group(request)
else:
group = getattr(request, duration_group)

histogram_labels = {
request_duration_labels = {
'method': request.method,
'status': 500,
'status': status_code,
duration_group_name: group
}
histogram_labels.update(labels.values_for(response))
request_duration_labels.update(labels.values_for(response))

histogram.labels(**histogram_labels).observe(total_time)
request_duration_metric.labels(**request_duration_labels).observe(total_time)

counter.labels(
request_total_metric.labels(
method=request.method, status=500,
**labels.values_for(response)
).inc()
Expand Down
57 changes: 54 additions & 3 deletions tests/test_defaults.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from unittest_helper import BaseTestCase

from prometheus_flask_exporter import NO_PREFIX
from flask import request, make_response
from flask import request, make_response, abort


class DefaultsTest(BaseTestCase):
Expand Down Expand Up @@ -52,7 +52,10 @@ def test():
'flask_http_request_duration_seconds_bucket', '2.0',
('le', '+Inf'), ('method', 'GET'), ('path', '/test'), ('status', 200)
)

self.assertAbsent(
'flask_http_request_exceptions_total',
('method', 'GET'), ('path', '/skip/defaults'), ('status', 200)
)
self.client.get('/test')

self.assertMetric(
Expand Down Expand Up @@ -109,6 +112,33 @@ def test():
('method', 'GET'), ('path', '/skip'), ('status', 200)
)

def test_exception_counter_metric(self):
metrics = self.metrics()

@self.app.route('/error')
def test():
raise AttributeError

try:
self.client.get('/error')
except AttributeError:
pass

self.assertMetric(
'flask_http_request_exceptions_total', '1.0',
('method', 'GET'), ('path', '/error'), ('status', 500)
)

try:
self.client.get('/error')
except AttributeError:
pass

self.assertMetric(
'flask_http_request_exceptions_total', '2.0',
('method', 'GET'), ('path', '/error'), ('status', 500)
)

def test_do_not_track_only_excludes_defaults(self):
metrics = self.metrics()

Expand All @@ -130,6 +160,10 @@ def test():
'flask_http_request_duration_seconds_count',
('method', 'GET'), ('path', '/skip/defaults'), ('status', 200)
)
self.assertAbsent(
'flask_http_request_exceptions_total',
('method', 'GET'), ('path', '/skip/defaults'),
)

self.assertMetric('cnt_before_total', 2.0)
self.assertMetric('cnt_after_total', 2.0)
Expand Down Expand Up @@ -178,6 +212,11 @@ def test():
('method', 'GET'), ('path', '/test'), ('status', 200),
endpoint='/my-metrics'
)
self.assertAbsent(
'flask_http_request_exceptions_total',
('method', 'GET'), ('path', '/test'),
endpoint='/my-metrics'
)

def test_no_default_export(self):
self.metrics(export_defaults=False)
Expand All @@ -198,6 +237,11 @@ def test():
('method', 'GET'), ('path', '/test'), ('status', 200),
endpoint='/metrics'
)
self.assertAbsent(
'flask_http_request_exceptions_total',
('method', 'GET'), ('path', '/test'), ('status', 200),
endpoint='/metrics'
)

def test_custom_defaults_prefix(self):
metrics = self.metrics(defaults_prefix='www')
Expand Down Expand Up @@ -291,7 +335,10 @@ def test():
'late_http_request_total',
('method', 'GET'), ('status', 200)
)

self.assertAbsent(
'flask_http_request_exceptions_total',
('method', 'GET'), ('path', '/test'),
)
metrics.export_defaults(prefix='late')

self.assertMetric(
Expand All @@ -311,6 +358,10 @@ def test():
'late_http_request_duration_seconds_count', '3.0',
('method', 'GET'), ('path', '/test'), ('status', 200)
)
self.assertAbsent(
'flask_http_request_exceptions_total',
('method', 'GET'), ('path', '/test'),
)

def test_non_automatic_endpoint_registration(self):
metrics = self.metrics(path=None)
Expand Down

0 comments on commit ab43abd

Please sign in to comment.