IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series: Lập trình Raspberry Pi – Bài 23: Observability “nhẹ” — structured logging + request id + healthcheck nâng cao + alert đơn giản

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.
  • 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

  1. Log JSON cho mọi publish MQTT (telemetry/status/event).
  2. /ready check thêm “mqtt_connected”.
  3. Viết endpoint /debug/log để test logging event (chỉ bật trong LAN).