Skip to content

Commit d514672

Browse files
committed
feat(opentelemetry): add opentelemetry service for metrics
1 parent 093faf7 commit d514672

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed

pyms/flask/services/opentelemetry.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import time
2+
import logging
3+
from typing import Text
4+
5+
from flask import Blueprint, Response, request
6+
from pyms.flask.services.driver import DriverService
7+
8+
from prometheus_client import generate_latest
9+
from opentelemetry import metrics
10+
from opentelemetry.exporter.prometheus import PrometheusMetricsExporter
11+
from opentelemetry.sdk.metrics import Counter, ValueRecorder, MeterProvider
12+
from opentelemetry.sdk.metrics.export.controller import PushController
13+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
14+
15+
# Based on https://github.com/sbarratt/flask-prometheus
16+
# and https://github.com/korfuri/python-logging-prometheus/
17+
18+
# Set meter provider
19+
metrics.set_meter_provider(MeterProvider())
20+
meter = metrics.get_meter(__name__)
21+
# Set main exporter
22+
exporter = PrometheusMetricsExporter()
23+
# Ser interval to generate metrics
24+
INTERVAL = 1
25+
# Instrument requests
26+
instrumentor = RequestsInstrumentor()
27+
instrumentor.instrument()
28+
PushController(instrumentor.meter, exporter, INTERVAL)
29+
# Generate the custom metrics
30+
PushController(meter, exporter, INTERVAL)
31+
32+
33+
FLASK_REQUEST_LATENCY = meter.create_metric(
34+
"http_server_requests_seconds",
35+
"Flask Request Latency",
36+
"http_server_requests_seconds",
37+
float,
38+
ValueRecorder,
39+
("service", "method", "uri", "status"),
40+
)
41+
FLASK_REQUEST_COUNT = meter.create_metric(
42+
"http_server_requests_count",
43+
"Flask Request Count",
44+
"http_server_requests_count",
45+
int,
46+
Counter,
47+
["service", "method", "uri", "status"],
48+
)
49+
LOGGER_TOTAL_MESSAGES = meter.create_metric(
50+
"logger_messages_total",
51+
"Count of log entries by service and level.",
52+
"logger_messages_total",
53+
int,
54+
Counter,
55+
["service", "level"],
56+
)
57+
58+
59+
class FlaskMetricsWrapper():
60+
def __init__(self, app_name):
61+
self.app_name = app_name
62+
63+
def before_request(self): # pylint: disable=R0201
64+
request.start_time = time.time()
65+
66+
def after_request(self, response):
67+
if hasattr(request.url_rule, "rule"):
68+
path = request.url_rule.rule
69+
else:
70+
path = request.path
71+
request_latency = time.time() - request.start_time
72+
labels = {
73+
"service": self.app_name,
74+
"method": str(request.method),
75+
"uri": path,
76+
"status": str(response.status_code),
77+
}
78+
79+
FLASK_REQUEST_LATENCY.record(request_latency, labels)
80+
FLASK_REQUEST_COUNT.add(1, labels)
81+
82+
return response
83+
84+
85+
class Service(DriverService):
86+
"""
87+
Adds [Prometheus](https://prometheus.io/) metrics using the [Opentelemetry Client Library](https://opentelemetry-python.readthedocs.io/en/latest/exporter/prometheus/prometheus.html).
88+
"""
89+
config_resource: Text = "metrics"
90+
91+
def __init__(self, *args, **kwargs):
92+
super().__init__(*args, **kwargs)
93+
94+
self.metrics_blueprint = Blueprint("metrics", __name__)
95+
self.serve_metrics()
96+
97+
@staticmethod
98+
def monitor(app_name, app):
99+
metric = FlaskMetricsWrapper(app_name)
100+
app.before_request(metric.before_request)
101+
app.after_request(metric.after_request)
102+
103+
def serve_metrics(self):
104+
@self.metrics_blueprint.route("/metrics", methods=["GET"])
105+
def metrics(): # pylint: disable=unused-variable
106+
return Response(
107+
generate_latest(),
108+
mimetype="text/print()lain",
109+
content_type="text/plain; charset=utf-8",
110+
)
111+
112+
@staticmethod
113+
def add_logger_handler(logger, service_name):
114+
logger.addHandler(MetricsLogHandler(service_name))
115+
return logger
116+
117+
118+
class MetricsLogHandler(logging.Handler):
119+
"""A LogHandler that exports logging metrics for Prometheus.io."""
120+
121+
def __init__(self, app_name):
122+
super().__init__()
123+
self.app_name = str(app_name)
124+
125+
def emit(self, record):
126+
labels = {"service": self.app_name, "level": record.levelname}
127+
LOGGER_TOTAL_MESSAGES.add(1, labels)

setup.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
'prometheus_client>=0.8.0',
5353
]
5454

55+
install_opentelemetry_requires = [
56+
'opentelemetry-exporter-prometheus>=0.14b0',
57+
'opentelemetry-sdk>=0.14b0',
58+
]
59+
5560
install_tests_requires = [
5661
'requests-mock>=1.8.0',
5762
'coverage>=5.3',

0 commit comments

Comments
 (0)