europapark-exporter/src/europapark_exporter/http_server.py

140 lines
3.9 KiB
Python

import logging
import time
import gunicorn.app.base
from prometheus_client import CONTENT_TYPE_LATEST, Summary, Counter, generate_latest
from werkzeug.routing import Map, Rule
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import InternalServerError
from .collector import collect_europapark
class EuropaParkExporterApplication:
"""
Europa Park prometheus collector HTTP handler.
"""
# pylint: disable=no-self-use
def __init__(self, duration, errors):
self._duration = duration
self._errors = errors
self._log = logging.getLogger(__name__)
def on_europapark(self, module='default'):
"""
Request handler for /europapark route
"""
start = time.time()
output = collect_europapark()
response = Response(output)
response.headers['content-type'] = CONTENT_TYPE_LATEST
self._duration.labels(module).observe(time.time() - start)
return response
def on_metrics(self):
"""
Request handler for /metrics route
"""
response = Response(generate_latest())
response.headers['content-type'] = CONTENT_TYPE_LATEST
return response
def on_index(self):
"""
Request handler for index route (/).
"""
response = Response(
"""<html>
<head><title>Europa Park Exporter</title></head>
<body>
<h1>Europa Park Exporter</h1>
<p>Visit <code>/europapark</code> to use.</p>
</body>
</html>"""
)
response.headers['content-type'] = 'text/html'
return response
def view(self, endpoint, values, args):
"""
Werkzeug views mapping method.
"""
view_registry = {
'index': self.on_index,
'metrics': self.on_metrics,
'europapark': self.on_europapark,
}
params = dict(values)
try:
return view_registry[endpoint](**params)
except Exception as error: # pylint: disable=broad-except
self._log.exception("Exception thrown while rendering view")
self._errors.labels(args.get('module', 'default')).inc()
raise InternalServerError from error
@Request.application
def __call__(self, request):
url_map = Map([
Rule('/', endpoint='index'),
Rule('/metrics', endpoint='metrics'),
Rule('/europapark', endpoint='europapark'),
])
urls = url_map.bind_to_environ(request.environ)
view_func = lambda endpoint, values: self.view(endpoint, values, request.args)
return urls.dispatch(view_func, catch_http_exceptions=True)
class StandaloneGunicornApplication(gunicorn.app.base.BaseApplication):
"""
Copy-paste from https://docs.gunicorn.org/en/stable/custom.html
"""
# 'init' and 'load' methods are implemented by WSGIApplication.
# pylint: disable=abstract-method
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
config = {key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
def start_http_server(gunicorn_options):
"""
Start a HTTP API server for Europa Park prometheus collector.
"""
duration = Summary(
'europapark_collection_duration_seconds',
'Duration of collections by the Europa Park exporter',
['module'],
)
errors = Counter(
'europapark_request_errors_total',
'Errors in requests to Europa Park exporter',
['module'],
)
app = EuropaParkExporterApplication(duration, errors)
StandaloneGunicornApplication(app, gunicorn_options).run()