Skip to content

Commit bde0f00

Browse files
alexppgavara1986
authored andcommitted
Añadir soporte para métricas (#62)
* Add metrics support out of the box * Split to ease modularization * Add logger metrics * Add requirements * Update pipfile lock * Delete unnecesary line * Deacoplate class of logger function * Deacoplate logger metrics from metrics * Delete unused imported functions * Create metrics test * Fix vulnerable dependencies * Test metrics endpoint * Restore deleted line * Migrate metrics implementation to service * Add metrics documentation * Add jaeger metrics * Don't use namespace for metrics See jaegertracing/jaeger-client-python#268 * Add jaeger metrics test
1 parent c001b28 commit bde0f00

File tree

11 files changed

+188
-10
lines changed

11 files changed

+188
-10
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jaeger-client = "==4.1.0"
1414
flask-opentracing = "*"
1515
opentracing = ">=2.1"
1616
opentracing-instrumentation = "==3.2.1"
17+
prometheus_client = ">=0.7.1"
1718
[dev-packages]
1819
requests-mock = "*"
1920
coverage = "==4.4.0"

Pipfile.lock

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/services.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,27 @@ Extends the Microservice with [Connexion](https://github.com/zalando/connexion)
1414
Extend the [requests library](http://docs.python-requests.org/en/master/) with trace headers and parsing JSON objects.
1515
Encapsulate common rest operations between business services propagating trace headers if set up.
1616

17+
## Metrics
18+
Adds ![Prometheus](https://prometheus.io/) metrics using the ![Prometheus Client
19+
Library](https://github.com/prometheus/client_python).
20+
21+
At the moment, the next metrics are available:
22+
- Incoming requests latency as a histogram
23+
- Incoming requests number as a counter, divided by HTTP method, endpoint and
24+
HTTP status
25+
- Total number of log events divided by level
26+
- If `tracer` service activated and it's jaeger, it will show its metrics
27+
28+
To use this service, you may add the next to you configuration file:
29+
30+
```yaml
31+
pyms:
32+
metrics: true
33+
```
34+
35+
This will add the endpoint `/metrics` to your microservice, which will expose
36+
the metrics.
1737

1838
## How to contrib: create your own service:
1939

20-
TODO
40+
TODO

docs/structure.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ With the function `create_app` initialize the Flask app, register [blueprints](h
88
and initialize all libraries such as Swagger, database, trace system, custom logger format, etc.
99

1010
### pyms/flask/services
11-
Integrations and wrappers over common libs like request, swagger, connexion
11+
Integrations and wrappers over common libs like request, swagger, connexion or metrics.
1212

1313
### pyms/flask/healthcheck
1414
This view is usually used by Kubernetes, Eureka and other systems to check if our application is running.
@@ -17,4 +17,4 @@ This view is usually used by Kubernetes, Eureka and other systems to check if ou
1717
Print logger in JSON format to send to server like Elasticsearch. Inject span traces in logger.
1818

1919
### pyms/tracer
20-
Create an injector `flask_opentracing.FlaskTracer` to use in our projects.
20+
Create an injector `flask_opentracing.FlaskTracer` to use in our projects.

pyms/flask/app/create_app.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def init_logger(self):
7070
log_handler.setFormatter(formatter)
7171

7272
self.application.logger.addHandler(log_handler)
73+
7374
self.application.logger.propagate = False
7475

7576
if self.application.config["DEBUG"]:
@@ -99,6 +100,15 @@ def init_app(self) -> Flask:
99100

100101
return application
101102

103+
def init_metrics(self):
104+
if getattr(self, "metrics", False) and self.metrics:
105+
self.application.register_blueprint(self.metrics.metrics_blueprint)
106+
self.metrics.add_logger_handler(
107+
self.application.logger,
108+
self.application.config["APP_NAME"]
109+
)
110+
self.metrics.monitor(self.application)
111+
102112
def create_app(self):
103113
"""Initialize the Flask app, register blueprints and initialize
104114
all libraries like Swagger, database,
@@ -121,6 +131,8 @@ def create_app(self):
121131

122132
self.init_logger()
123133

134+
self.init_metrics()
135+
124136
return self.application
125137

126138
def add_error_handlers(self):

pyms/flask/services/metrics.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import time
2+
import logging
3+
4+
from flask import Blueprint, Response, request
5+
from prometheus_client import Counter, Histogram, generate_latest
6+
from pyms.flask.services.driver import DriverService
7+
8+
# Based on https://github.com/sbarratt/flask-prometheus
9+
# and https://github.com/korfuri/python-logging-prometheus/
10+
11+
FLASK_REQUEST_LATENCY = Histogram(
12+
"flask_request_latency_seconds", "Flask Request Latency", ["method", "endpoint"]
13+
)
14+
FLASK_REQUEST_COUNT = Counter(
15+
"flask_request_count", "Flask Request Count", ["method", "endpoint", "http_status"]
16+
)
17+
18+
LOGGER_TOTAL_MESSAGES = Counter(
19+
"python_logging_messages_total",
20+
"Count of log entries by service and level.",
21+
["service", "level"],
22+
)
23+
24+
25+
def before_request():
26+
request.start_time = time.time()
27+
28+
29+
def after_request(response):
30+
request_latency = time.time() - request.start_time
31+
FLASK_REQUEST_LATENCY.labels(request.method, request.path).observe(request_latency)
32+
FLASK_REQUEST_COUNT.labels(request.method, request.path, response.status_code).inc()
33+
34+
return response
35+
36+
37+
class Service(DriverService):
38+
service = "metrics"
39+
40+
def __init__(self, service, *args, **kwargs):
41+
super().__init__(service, *args, **kwargs)
42+
self.metrics_blueprint = Blueprint("metrics", __name__)
43+
self.serve_metrics()
44+
45+
def monitor(self, app):
46+
app.before_request(before_request)
47+
app.after_request(after_request)
48+
49+
def serve_metrics(self):
50+
@self.metrics_blueprint.route("/metrics", methods=["GET"])
51+
def metrics():
52+
return Response(
53+
generate_latest(),
54+
mimetype="text/print()lain",
55+
content_type="text/plain; charset=utf-8",
56+
)
57+
58+
def add_logger_handler(self, logger, service_name):
59+
logger.addHandler(MetricsLogHandler(service_name))
60+
return logger
61+
62+
63+
class MetricsLogHandler(logging.Handler):
64+
"""A LogHandler that exports logging metrics for Prometheus.io."""
65+
66+
def __init__(self, app_name):
67+
super(MetricsLogHandler, self).__init__()
68+
self.app_name = app_name
69+
70+
def emit(self, record):
71+
LOGGER_TOTAL_MESSAGES.labels(self.app_name, record.levelname).inc()

pyms/flask/services/tracer.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import logging
22

3+
from jaeger_client.metrics.prometheus import PrometheusMetricsFactory
4+
35
from pyms.constants import LOGGER_NAME
46
from pyms.flask.services.driver import DriverService
57
from pyms.utils.utils import check_package_exists, import_package, import_from
8+
from pyms.config.conf import get_conf
69

710
logger = logging.getLogger(LOGGER_NAME)
811

@@ -43,7 +46,11 @@ def init_jaeger_tracer(self):
4346
'reporting_host': self.host
4447
}
4548
}
46-
49+
metrics_config = get_conf(service="pyms.metrics", empty_init=True, memoize=False)
50+
metrics = ""
51+
if metrics_config:
52+
service_name = self.component_name.lower().replace("-", "_").replace(" ", "_")
53+
metrics = PrometheusMetricsFactory()
4754
config = Config(config={
4855
**{'sampler': {
4956
'type': 'const',
@@ -54,6 +61,7 @@ def init_jaeger_tracer(self):
5461
},
5562
**host
5663
}, service_name=self.component_name,
64+
metrics_factory=metrics,
5765
validate=True)
5866
return config.initialize_tracer()
5967

requirements-tests.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ mkdocs==1.0.4
4242
mock==2.0.0
4343
more-itertools==7.2.0
4444
nose==1.3.7
45-
numpy==1.13.3
45+
numpy==1.16.1
4646
openapi-spec-validator==0.2.8
4747
opentracing==2.2.0
4848
opentracing-instrumentation==3.2.1
@@ -52,7 +52,7 @@ pkginfo==1.5.0.1
5252
pluggy==0.13.0
5353
protobuf==3.9.0rc1
5454
py==1.8.0
55-
py-ms==1.0.0
55+
py-ms==1.0.1
5656
Pygments==2.3.1
5757
pylint==2.4.3
5858
pyparsing==2.4.2
@@ -85,3 +85,4 @@ webencodings==0.5.1
8585
Werkzeug==0.16.0
8686
wrapt==1.11.2
8787
zipp==0.6.0
88+
prometheus_client==0.7.1

tests/config-tests-metrics.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
pyms:
3+
metrics: true
4+
tracer:
5+
client: "jaeger"
6+
component_name: "Python Microservice"
7+
my-ms:
8+
DEBUG: true
9+
TESTING: true
10+
APP_NAME: "Python Microservice"
11+
APPLICATION_ROOT: /

tests/config-tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pyms:
2+
metrics: true
23
requests:
34
data: data
45
swagger:
@@ -17,4 +18,4 @@ my-ms:
1718
subservice1:
1819
test: input
1920
subservice2:
20-
test: output
21+
test: output

0 commit comments

Comments
 (0)