Skip to content

Commit 1a3bb9b

Browse files
author
SerggAidd
committed
Complete Lab 8
1 parent 7cd73f7 commit 1a3bb9b

14 files changed

Lines changed: 1214 additions & 14 deletions

app_python/app.py

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
import platform
1313
import socket
1414
import sys
15+
import time
1516
from datetime import datetime, timezone
1617

17-
from flask import Flask, jsonify, request
18+
from flask import Flask, Response, g, jsonify, request
19+
from prometheus_client import (CONTENT_TYPE_LATEST, Counter, Gauge,
20+
Histogram, generate_latest)
1821

1922
# Flask application instance
2023
app = Flask(__name__)
@@ -35,6 +38,27 @@
3538
"framework": "Flask",
3639
}
3740

41+
# Prometheus metrics
42+
# Counter: total HTTP requests by method / endpoint / status code
43+
http_requests_total = Counter(
44+
"http_requests_total",
45+
"Total HTTP requests",
46+
["method", "endpoint", "status_code"],
47+
)
48+
49+
# Histogram: request duration distribution
50+
http_request_duration_seconds = Histogram(
51+
"http_request_duration_seconds",
52+
"HTTP request duration in seconds",
53+
["method", "endpoint", "status_code"],
54+
)
55+
56+
# Gauge: current number of requests being processed
57+
http_requests_in_progress = Gauge(
58+
"http_requests_in_progress",
59+
"HTTP requests currently being processed",
60+
)
61+
3862

3963
# JSON log formatter
4064
# Produces structured logs suitable for Loki / Promtail / Grafana
@@ -147,6 +171,17 @@ def request_info():
147171
}
148172

149173

174+
def normalized_endpoint():
175+
"""
176+
Return a normalized endpoint for metrics labels.
177+
178+
Uses Flask route rule when available, fallback to raw request path.
179+
"""
180+
if request.url_rule is not None:
181+
return request.url_rule.rule
182+
return request.path
183+
184+
150185
def endpoints_info():
151186
"""
152187
Build an API endpoints list dynamically from Flask URL map.
@@ -201,6 +236,12 @@ def health():
201236
return jsonify(payload)
202237

203238

239+
@app.get("/metrics")
240+
def metrics():
241+
"""Prometheus metrics endpoint."""
242+
return Response(generate_latest(), content_type=CONTENT_TYPE_LATEST)
243+
244+
204245
# Test-only endpoint to trigger HTTP 500 (uncomment to verify error handler)
205246
@app.get("/crash")
206247
def crash():
@@ -256,6 +297,14 @@ def internal_error(error):
256297
def log_requests():
257298
"""Log basic request metadata before handling."""
258299

300+
# Skip Prometheus self-scrape from app HTTP metrics
301+
g.track_metrics = request.path != "/metrics"
302+
303+
# Save request start time and increment in-progress gauge
304+
if g.track_metrics:
305+
g.request_start_time = time.perf_counter()
306+
http_requests_in_progress.inc()
307+
259308
info = request_info()
260309
app.logger.info(
261310
"request_started",
@@ -273,16 +322,44 @@ def log_response(response):
273322
"""Log response status code after handling."""
274323

275324
info = request_info()
276-
app.logger.info(
277-
"request_finished",
278-
extra={
279-
"method": info["method"],
280-
"path": info["path"],
281-
"status_code": response.status_code,
282-
"client_ip": info["client_ip"],
283-
"user_agent": info["user_agent"],
284-
},
285-
)
325+
326+
# Record Prometheus metrics after request is processed
327+
if getattr(g, "track_metrics", False):
328+
duration = time.perf_counter() - g.request_start_time
329+
endpoint = normalized_endpoint()
330+
status_code = str(response.status_code)
331+
332+
http_requests_total.labels(
333+
method=request.method,
334+
endpoint=endpoint,
335+
status_code=status_code,
336+
).inc()
337+
338+
http_request_duration_seconds.labels(
339+
method=request.method,
340+
endpoint=endpoint,
341+
status_code=status_code,
342+
).observe(duration)
343+
344+
http_requests_in_progress.dec()
345+
346+
latency_ms = round(duration * 1000, 2)
347+
else:
348+
latency_ms = None
349+
350+
extra_payload = {
351+
"method": info["method"],
352+
"path": info["path"],
353+
"status_code": response.status_code,
354+
"client_ip": info["client_ip"],
355+
"user_agent": info["user_agent"],
356+
}
357+
358+
# Add measured latency for normal app requests
359+
if latency_ms is not None:
360+
extra_payload["latency_ms"] = latency_ms
361+
362+
app.logger.info("request_finished", extra=extra_payload)
286363

287364
return response
288365

app_python/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ requests==2.32.5
1313
urllib3==2.6.3
1414
Werkzeug==3.1.5
1515
zope.interface==8.2
16+
prometheus-client==0.23.1

monitoring/docker-compose.yml

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ services:
4343
depends_on:
4444
loki:
4545
condition: service_started
46+
healthcheck:
47+
test:
48+
[
49+
"CMD-SHELL",
50+
"bash -c 'exec 3<>/dev/tcp/127.0.0.1/9080 && printf \"GET /ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3 && grep -q ready <&3' || exit 1"
51+
]
52+
interval: 10s
53+
timeout: 5s
54+
retries: 5
55+
start_period: 15s
4656
deploy:
4757
resources:
4858
limits:
@@ -76,6 +86,44 @@ services:
7686
timeout: 5s
7787
retries: 5
7888
start_period: 20s
89+
deploy:
90+
resources:
91+
limits:
92+
cpus: "0.50"
93+
memory: 512M
94+
reservations:
95+
cpus: "0.25"
96+
memory: 256M
97+
98+
prometheus:
99+
image: prom/prometheus:v3.9.0
100+
container_name: prometheus
101+
ports:
102+
- "9090:9090"
103+
volumes:
104+
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
105+
- prometheus-data:/prometheus
106+
command:
107+
- "--config.file=/etc/prometheus/prometheus.yml"
108+
- "--storage.tsdb.path=/prometheus"
109+
- "--storage.tsdb.retention.time=15d"
110+
- "--storage.tsdb.retention.size=10GB"
111+
networks:
112+
- logging
113+
restart: unless-stopped
114+
depends_on:
115+
loki:
116+
condition: service_started
117+
grafana:
118+
condition: service_started
119+
app-python:
120+
condition: service_started
121+
healthcheck:
122+
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:9090/-/healthy || exit 1"]
123+
interval: 10s
124+
timeout: 5s
125+
retries: 5
126+
start_period: 20s
79127
deploy:
80128
resources:
81129
limits:
@@ -112,7 +160,7 @@ services:
112160
resources:
113161
limits:
114162
cpus: "0.50"
115-
memory: 512M
163+
memory: 256M
116164
reservations:
117165
cpus: "0.10"
118166
memory: 128M
@@ -138,7 +186,7 @@ services:
138186
resources:
139187
limits:
140188
cpus: "0.50"
141-
memory: 512M
189+
memory: 256M
142190
reservations:
143191
cpus: "0.10"
144192
memory: 128M
@@ -150,4 +198,5 @@ networks:
150198
volumes:
151199
loki-data:
152200
grafana-data:
153-
promtail-positions:
201+
promtail-positions:
202+
prometheus-data:

0 commit comments

Comments
 (0)