Series: Lập trình Raspberry Pi & Ứng dụng thực tế Phần 3 — Python “build app thật” Bài 23: Observability “nhẹ” — structured logging + request id + healthcheck nâng cao + alert đơn giản
1) Mục tiêu bài học
Sau bài này bạn sẽ:
- Log “đọc được & lọc được” (structured JSON hoặc format có field rõ ràng).
- Gắn request_id cho mỗi request (dễ trace lỗi).
- Healthcheck nâng cao: phân biệt live vs ready.
- Có alert đơn giản: nếu service down → gửi Telegram (tuỳ chọn) hoặc log cảnh báo.
2) Nâng logging thành “structured”
Hiện tại (Bài 12) log là text. Ta nâng thành log có field cố định để grep dễ.
Mở src/utils/logging.py, thêm option json_mode.
Ví dụ nhanh (giữ đơn giản, không cần thư viện):
import json
...
def log_json(logger, level: str, event: str, **fields):
payload = {"event": event, **fields}
msg = json.dumps(payload, ensure_ascii=False)
getattr(logger, level.lower())(msg)
Bạn dùng như:
log_json(logger, "info", "telemetry_published", device_id="pi-gw-01", t=26.5, h=70.2)
Tip: khi chạy production, log JSON cực tiện để đưa vào Loki/ELK sau này.
3) Thêm Request ID cho FastAPI
Mở src/api.py, thêm middleware:
import uuid
from fastapi import Request
@app.middleware("http")
async def add_request_id(request: Request, call_next):
rid = request.headers.get("x-request-id") or str(uuid.uuid4())
request.state.request_id = rid
response = await call_next(request)
response.headers["x-request-id"] = rid
return response
Giờ mọi response đều có header x-request-id.
4) Healthcheck “đúng bài”: /live và /ready
4.1 /live (process còn sống)
@app.get("/live")
def live():
return {"status": "alive"}
4.2 /ready (phụ thuộc đã sẵn sàng)
Ready thường check:
- DB SQLite mở được
- MQTT connected (nếu app có MQTT)
Ví dụ:
from src.storage_sqlite import ensure_db
READY = {"db": False}
@app.on_event("startup")
def startup():
try:
ensure_db()
READY["db"] = True
except Exception:
READY["db"] = False
@app.get("/ready")
def ready():
ok = READY["db"]
return {"status": "ready" if ok else "not_ready", "db": READY["db"]}
5) Nâng /metrics: thêm build info + service name
Trong /metrics thêm:
- version, git_sha (từ Bài 22)
- service: iotlabs-api
Ví dụ:
"service": "iotlabs-api",
"version": os.getenv("APP_VERSION", "unknown"),
"git_sha": os.getenv("APP_GIT_SHA", "unknown"),
6) Alert đơn giản kiểu “chạy là hiểu”
Option A (nhẹ nhất): cron curl check + log
Tạo script scripts/healthcheck.sh:
#!/usr/bin/env bash
set -e
curl -fsS http://127.0.0.1:8000/ready >/dev/null
Cron mỗi 1 phút:
crontab -e
Thêm:
* * * * * /home/developer/apps/iotlabs-py-agent/scripts/healthcheck.sh || echo "$(date) READY FAIL" >> /home/developer/apps/iotlabs-py-agent/logs/healthcheck.log
Option B (tuỳ chọn): Telegram alert
Nếu bạn muốn, mình sẽ viết bản gửi Telegram bot token + chat_id (mức nhẹ, không lộ secrets trong repo).
7) Debug nhanh
- Muốn xem request_id:
curl -i http://127.0.0.1:8000/health
- Grep log theo event:
grep "telemetry_published" -n logs/iotlabs-py-agent.log | tail
8) Bài tập nâng cấp
- Log JSON cho mọi publish MQTT (telemetry/status/event).
- /ready check thêm “mqtt_connected”.
- Viết endpoint /debug/log để test logging event (chỉ bật trong LAN).


