Skip to content

Commit a5684d7

Browse files
authored
Merge pull request #357 from ansforge/converter/fix-prometheus-metrics-export
Converter/fix prometheus metrics export
2 parents 3570309 + 157cf84 commit a5684d7

File tree

7 files changed

+68
-19
lines changed

7 files changed

+68
-19
lines changed

converter/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,7 @@ cython_debug/
171171
.pypirc
172172

173173
# Request cache
174-
*_cache.sqlite
174+
*_cache.sqlite
175+
176+
# prometheus metrics
177+
tmp/prometheus_metrics

converter/Dockerfile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,11 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
5151

5252
ENV CONVERTER_VERSION=${CONVERTER_VERSION}
5353

54+
# Flask Prometheus Exporter setup
55+
RUN mkdir /tmp/prometheus_metrics
56+
ENV PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus_metrics
57+
58+
ENV FLASK_ENV=production
59+
5460
# Use Gunicorn for production deployment
55-
CMD ["uv", "run", "--no-dev", "gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "converter.converter:app"]
61+
CMD ["uv", "run", "--no-dev", "gunicorn", "-c", "gunicorn.conf.py", "-w", "4", "-b", "0.0.0.0:8080", "converter.converter:app"]

converter/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,22 @@ Note : the tests download files (json samples & schemas) using the Github API. T
5555
Development mode:
5656

5757
```bash
58-
# In converter/, run the command:
58+
# In converter/, run the commands:
5959
FLASK_APP=converter.converter \
6060
FLASK_ENV=development \
6161
FLASK_DEBUG=1 \
6262
uv run python -m flask run --port 8080
6363
```
6464

65+
*Note :* enable prometheus metrics, add `DEBUG_METRICS=1` in the above command.
66+
6567
Production mode (using Gunicorn):
6668

6769
```bash
68-
gunicorn -w 4 -b 0.0.0.0:8080 converter.converter:app
70+
# In converter/, run the commands:
71+
mkdir -p ./tmp/prometheus_metrics
72+
73+
PROMETHEUS_MULTIPROC_DIR=./tmp/prometheus_metrics gunicorn -c gunicorn.conf.py -w 4 -b 0.0.0.0:8080 converter.converter:app
6974
```
7075

7176
### Controlling Logging Level

converter/converter/converter.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Flask, request, jsonify, g
22
import logging
3-
from werkzeug.middleware.dispatcher import DispatcherMiddleware
4-
from prometheus_client import make_wsgi_app, Histogram
3+
import os
4+
from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics
5+
from prometheus_flask_exporter import PrometheusMetrics
56

67
from converter.conversion_strategy.conversion_strategy import conversion_strategy
78
from converter.utils import (
@@ -15,26 +16,42 @@
1516
configure_logging()
1617

1718
app = Flask(__name__)
18-
logger = logging.getLogger(__name__)
1919

20-
# Add prometheus wsgi middleware to route /metrics requests
21-
# ignore typing issue with reassigning method
22-
app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {"/metrics": make_wsgi_app()}) # type: ignore[assignment]
20+
is_prod = os.getenv("FLASK_ENV") == "production"
2321

22+
if is_prod:
23+
metrics = GunicornInternalPrometheusMetrics(app)
24+
else:
25+
metrics = PrometheusMetrics(app)
2426

25-
convertion_timer = Histogram(
26-
"conversion_duration_seconds",
27-
"The number of seconds it took to the /convert endpoint to answer",
28-
)
27+
logger = logging.getLogger(__name__)
2928

3029

3130
def raise_error(message, code: int = 400):
3231
logger.error(message)
3332
return jsonify({"error": message}), code
3433

3534

35+
def extract_message_type_from_payload():
36+
try:
37+
data = request.get_json(silent=True) or {}
38+
edxl_json = data.get("edxl")
39+
message_content = extract_message_content(edxl_json)
40+
return extract_message_type_from_message_content(message_content)
41+
except Exception:
42+
return "unknownMessageType"
43+
44+
3645
@app.route("/convert", methods=["POST"])
37-
@convertion_timer.time()
46+
@metrics.do_not_track()
47+
@metrics.histogram(
48+
"conversion_duration_seconds",
49+
"The number of seconds it took to the /convert endpoint to answer",
50+
labels={
51+
"status": lambda r: r.status_code,
52+
"message_type": extract_message_type_from_payload,
53+
},
54+
)
3855
def convert():
3956
if not request.is_json:
4057
return raise_error("Content-Type must be application/json")

converter/gunicorn.conf.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics
2+
3+
4+
def child_exit(_, worker):
5+
GunicornInternalPrometheusMetrics.mark_process_dead_on_child_exit(worker.pid)

converter/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ dependencies = [
88
"flask>=3.1.2",
99
"gunicorn>=23.0.0",
1010
"jsonpath-ng>=1.7.0",
11-
"prometheus-client>=0.23.1",
11+
"prometheus-flask-exporter>=0.23.2",
1212
"pydantic>=2.11.9",
1313
"python-json-logger>=4.0.0",
1414
"pyyaml>=6.0.2",
@@ -59,5 +59,5 @@ dev = [
5959
]
6060

6161
[[tool.mypy.overrides]]
62-
module = ["snapshottest.*", "jsonpath_ng.*"]
62+
module = ["snapshottest.*", "jsonpath_ng.*", "prometheus_flask_exporter.*"]
6363
ignore_missing_imports = true

converter/uv.lock

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

0 commit comments

Comments
 (0)