Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1e9cfc4

Browse files
committed
feat: add opentelemetry service
It's only useful for metrics, but it adds the core of tracing and logging.
1 parent ed104b4 commit 1e9cfc4

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

‎pyms/flask/app/create_app.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def example():
8080
request: Optional[DriverService] = None
8181
tracer: Optional[DriverService] = None
8282
metrics: Optional[DriverService] = None
83+
opentelemetry: Optional[DriverService] = None
8384
_singleton = True
8485

8586
def __init__(self, *args, **kwargs):
@@ -204,6 +205,28 @@ def init_metrics(self) -> None:
204205
)
205206
self.metrics.monitor(self.application.config["APP_NAME"], self.application)
206207

208+
def init_opentelemetry(self) -> None:
209+
if self.opentelemetry:
210+
if self.opentelemetry.config.metrics.enabled:
211+
# Set metrics backend
212+
self.opentelemetry.set_metrics_backend()
213+
# Set the metrics blueprint
214+
# DISCLAIMER this endpoint may be only necessary with prometheus client
215+
self.application.register_blueprint(self.opentelemetry.blueprint)
216+
# Set instrumentations
217+
if self.opentelemetry.config.metrics.instrumentations.flask:
218+
self.opentelemetry.monitor(
219+
self.application.config["APP_NAME"], self.application
220+
)
221+
if self.opentelemetry.config.metrics.instrumentations.logger:
222+
self.opentelemetry.add_logger_handler(
223+
self.application.logger, self.application.config["APP_NAME"]
224+
)
225+
if self.opentelemetry.config.tracing.enabled:
226+
self.opentelemetry.set_tracing_backend()
227+
if self.opentelemetry.config.logging.enabled:
228+
self.opentelemetry.set_logging_backend()
229+
207230
def reload_conf(self):
208231
self.delete_services()
209232
self.config.reload()
@@ -237,6 +260,8 @@ def create_app(self) -> Flask:
237260

238261
self.init_metrics()
239262

263+
self.init_opentelemetry()
264+
240265
logger.debug(
241266
"Started app with PyMS and this services: {}".format(self.services)
242267
)

‎pyms/flask/services/opentelemetry.py‎

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

AltStyle によって変換されたページ (->オリジナル) /