مشاهدات آسان ساخته شده است: اضافه کردن سیاهههای مربوط ، آثار و معیارها به FastApi با Logfire

این را تصور کنید: شما برنامه جدید براق خود را مستقر می کنید. همه چیز در Dev بسیار عالی به نظر می رسد ، سیاهههای مربوط به آن تمیز هستند ، درخواست ها خوشحال هستند و زندگی خوب است. سپس … حملات فاجعه. یک کاربر یک اشکال را گزارش می کند. دیگری از زمان پاسخ آهسته شکایت می کند. شما سیاهههای مربوطه را بررسی می کنید – منتظر ، آنها کجا هستند؟ شما وارد سرور می شوید ، برخی از سیاههها را دم می کنید ، حدس می زنید که چه اشتباهی رخ داده است و به بهترین ها امیدوار هستید. صدا آشنا است؟
رعایت – دانستن آنچه در برنامه شما در زمان واقعی اتفاق می افتد – نمی تواند این کار سخت باشد. اما تنظیم یک پشته مشاهده اغلب مانند مونتاژ مبلمان IKEA با دستورالعمل های گمشده احساس می شود. این جایی است که آتش سوزی وارد می شود
Pydantic Logfire سکویی است که باعث می شود مشاهده آن به برنامه شما ، بدون توجه به اندازه ، به طرز مسخره ای آسان باشد. در این پست ، من به شما نشان می دهم که چگونه Logfire را در یک ادغام کنید فریپی برنامه برای به دست آوردن بینش فوری در مورد سیاهههای مربوط ، آثار و معیارها – بدون سردردهای تنظیم معمول. در پایان ، شما در زمان واقعی در مورد آنچه در زیر کاپوت اتفاق می افتد ، خواهید داشت ، بنابراین می توانید شبانه اشکال زدایی ، بهینه سازی و خواب بهتر کنید.
بیایید شروع کنیم!
راه اندازی
ما دو سرویس ایجاد خواهیم کرد که سفارشات و حمل و نقل برای Bighuge Corp Inc. را برای نگه داشتن امور ساده نگه می داریم ، ما هر دو سرویس را در یک repo قرار می دهیم و یک FastAPI را برای هر یک از آنها اجرا می کنیم تا دو سرویس را که با یکدیگر صحبت می کنند تقلید کنند.
mkdir fastapi-logfire
cd fastapi-logfire
ما یک محیط مجازی ایجاد می کنیم و آن را فعال خواهیم کرد:
python -m venv venv
نحوه فعال کردن ENV مجازی در سیستم عامل خود را در اینجا پیدا کنید: https://www.geeksforgeeks.org/create-virtual-environment-using-venv-python/
بیایید بسته های لازم را نصب کنیم:
pip install 'fastapi[standard]' 'logfire[fastapi]'
/orders
وت /shipping
خدمات
سرویس سفارش فقط شامل دو نقطه پایانی خواهد بود – یکی برای سفارش و دیگری برای دریافت جزئیات یک سفارش خاص. به همین ترتیب سرویس حمل و نقل دارای دو نقطه پایانی خواهد بود – یکی برای شروع فرآیند حمل و نقل و دیگری برای به دست آوردن وضعیت حمل و نقل.
سرویس سفارش خدمات حمل و نقل را در هر دو نقطه پایانی خود فراخوانی می کند.
در اینجا کد برای هر دو سرویس وجود دارد:
# app/routers/shipping.py
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
import uuid
from typing import Optional
router = APIRouter(prefix="/shipping", tags=["Shipping"])
class ShippingOrder(BaseModel):
order_id: str
items: list[str]
customer_id: int
id: Optional[str] = None
# Highly scalable, available, durable and all the other cool words
# Presenting....dictionary DB (/j). This will store our shipping data
shipping_db = {}
@router.post("/initiate")
async def initiate_shipping(shipping_order: ShippingOrder):
shipment_id = str(uuid.uuid4())
shipping_order_data = shipping_order.model_dump()
shipping_order_data["id"] = shipment_id
shipping_db[shipment_id] = shipping_order_data
return {"message": "Shipping initiated", "order": shipping_order_data}
@router.get("/status/{shipment_id}")
async def get_shipping_status(shipment_id: str):
shipping_order_data = shipping_db.get(shipment_id)
if not shipping_order_data:
raise HTTPException(status_code=404, detail="Shipping order not found")
return shipping_order_data
# app/routers/order.py
import uuid
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
import requests
router = APIRouter(prefix="/orders", tags=["Orders"])
# We'll just use a dictionary to store the orders for now
orders_db = {}
class Order(BaseModel):
customer_id: int
item: str
quantity: int
id: Optional[str] = None
shipment_id: Optional[str] = None
@router.post("/")
async def place_order(order: Order):
order_id = str(uuid.uuid4())
order_data = order.model_dump()
order_data["id"] = order_id
shipping_data = requests.post(
"http://127.0.0.1:8001/shipping/initiate",
json={
"order_id": order_id,
"items": [order.item],
"customer_id": order.customer_id,
},
)
if shipping_data.status_code != 200:
raise HTTPException(status_code=500, detail="Error initiating shipping")
shipping_data = shipping_data.json()
order_data["shipment_id"] = shipping_data["order"]["id"]
orders_db[order_id] = order_data
return {"message": "Order placed", "order": order_data}
@router.get("/{order_id}")
async def get_order(order_id: str) -> Order:
order_data = orders_db.get(order_id)
if not order_data:
raise HTTPException(status_code=404, detail="Order not found")
shipping_data = requests.get(
f"http://127.0.0.1:8001/shipping/status/{order_data['shipment_id']}"
)
if shipping_data.status_code != 200:
raise HTTPException(status_code=500, detail="Error fetching shipping status")
shipping_data = shipping_data.json()
order_data["shipping_status"] = shipping_data
return order_data
اکنون ما ایجاد خواهیم کرد main.py
وت main2.py
که برای شروع هر دو سرویس استفاده می شود
# app/main.py
from fastapi import FastAPI
from app.routers import order
app = FastAPI()
app.include_router(order.router)
# app/main2.py
from fastapi import FastAPI
from app.routers import shipping
app = FastAPI()
app.include_router(shipping.router)
شما می توانید هر دو سرویس را با استفاده از پایانه های جداگانه اجرا کنید fastapi dev
فرمان
fastapi dev app/main.py
fastapi dev --port=8001 app/main2.py
شما می توانید به localhost:8000/docs
برای امتحان کردن سرویس سفارش و دیدن اینکه آیا همه چیز همانطور که انتظار می رود کار می کند یا خیر.
اضافه کردن ورود به سیستم
قبل از ادغام Logfire ، باید یک نشانه از Logfire ایجاد کنید تا بتوانید داده ها را به داشبورد Logfire بنویسید. می توانید در مورد نحوه تولید نشانه در اینجا بخوانید. پس از داشتن نشانه ، می توانید آن را به عنوان یک متغیر محیط در یک ذخیره کنید .env
پرونده (ممکن است شما نیاز به نصب داشته باشید python-dotenv
برای استفاده از آن)
LOGFIRE_TOKEN=YOUR_TOKEN
ما کوچک شروع خواهیم کرد ما Logfire را در برنامه خود ادغام خواهیم کرد به گونه ای که تمام سیاهههای مربوط به برنامه به داشبورد Logfire ارسال شود. ما Logfire را در logging
کتابخانه استاندارد (نکته: همچنین می توانید با استفاده از روشهای Logfire مانند گزارش ها را مستقیماً منتشر کنید logfire.info()
)
# app/core/logger.py
from logging import basicConfig, getLogger
import logfire
# Adding the logfire handler
basicConfig(handlers=[logfire.LogfireLoggingHandler()])
def setup_logger(name):
logger = getLogger(name)
# sending all logs starting from the DEBUG level
logger.setLevel("DEBUG")
return logger
ما تعریف کرده ایم setup_logger
عملکردی که می توانیم برای ارسال سیاههها در هر نقطه از پروژه خود تماس بگیریم.
# app/routers/order.py
from ..core.logger import setup_logger
# [...] Other imports [...]
@router.post("/")
async def place_order(order: Order):
# log to indicate the starting of handler
logger.info("Placing order")
order_id = str(uuid.uuid4())
order_data = order.model_dump()
order_data["id"] = order_id
# [...] Retrieving shipping logic [...]
orders_db[order_id] = order_data
# log to indicate order was placed successfully
logger.info("Order placed")
return {"message": "Order placed", "order": order_data}
می توانید بیانیه های مشابهی را در آن اضافه کنید shipping
کنترل کننده نیز
در main.py
وت main2.py
، ما منطق را برای پیکربندی logfire اضافه خواهیم کرد
# app/main.py
# [...]
import logfire
app = FastAPI()
logfire.configure(token=os.getenv("LOGFIRE_TOKEN"), service_name="orders")
# [...]
همین کار را برای main2.py
، اما با service_name
به عنوان حمل و نقل
اگر هر دو برنامه را دوباره اجرا کنید و برخی از درخواست ها را امتحان کنید ، برخی از سیاهههای مربوط به نمایش در برگه زنده Dashboad Logfire را مشاهده خواهید کرد:
عالی! اکنون اظهارات ورود به سیستم خود را در داشبورد مشاهده می کنیم. اما Logfire به ما اجازه می دهد تا به طور مستقیم ابزار FastAPI را به ما بدهیم تا داده های بیشتری را برای هر درخواست بدست آوریم. بیایید آن را اجرا کنیم.
در زیر logfire.configure()
در هر دو main.py
وت main2.py
، یک خط جدید از کد اضافه کنید:
logfire.instrument_fastapi(app, capture_headers=True)
اکنون تمام درخواست های شما به هر دو سرور به صورت خودکار ابزار می شوند.
اکنون تمام سیاهههای مربوط به سطح خدمات تحت درخواست های مربوطه به طور مرتب و مرتب شده اند. اما این را بدست آورید – می تواند حتی بهتر شود.
وارد کنید – ردیابی.
ردیابی توزیع شده
بنابراین ما برنامه خود را به گونه ای تنظیم کردیم POST /orders
نقطه پایانی تماس خواهد گرفت POST /shipping/initiate
نقطه پایانی در داخل آن. این واقعاً خوب خواهد بود اگر داشبورد ما بتواند به جای نشان دادن هر دو تماس به صورت جداگانه ، این جریان متوالی را نشان دهد (مانند گذشته که دیدیم). ما می توانیم این کار را با استفاده از ردیابی انجام دهیم.
برای ردیابی کار ، زمینه باید در سراسر خدمات تبلیغ شود. این “زمینه” به پیگیری ردیابی والدین/دهانه یک دهانه/ورود به سیستم جدید کمک می کند تا در پشت سر هم مشاهده شود. خوشبختانه Logfire راهی آسان برای انجام این کار به ما می دهد. از آنجا که ما استفاده می کنیم requests
برای استفاده از /shipping
سرویس ، ما کتابخانه مرتبط را از Logfire نصب خواهیم کرد.
pip install 'logfire[requests]'
# or pip install 'logfire[httpx]' if you're using httpx
ما کد را به روز خواهیم کرد main.py
اضافه کردن logfire.instrument_requests()
# app/main.py
#[...]
logfire.configure(token=os.getenv("LOGFIRE_TOKEN"), service_name="orders")
logfire.instrument_requests() # NEW CODE
logfire.instrument_fastapi(app, capture_headers=True)
#[...]
نیازی به به روزرسانی نداریم main2.py
(حمل و نقل) زیرا ما در حال حاضر هیچ درخواستی در آن سرور ارسال نمی کنیم.
instrument_requests()
در هنگام درخواست ، اطمینان حاصل می کند که هدر واسطه به طور خودکار تنظیم شده است. instrument_fastapi()
اطمینان می دهد که traceparent
هدر به درستی از درخواست های دریافتی استخراج می شود. این است متن منتشر شد!
دوباره سرورها را اجرا کنید و سعی کنید برخی از درخواست ها را ارسال کنید. در داشبورد Logfire خود چیزی شبیه به این خواهید دید:
و آسمانها ممنوع است ، اگر چیزی در یکی از خدمات اشتباه پیش برود ، اکنون می دانید که کجا اشتباه پیش آمده است. بیایید این را آزمایش کنیم:
# app/routers/shipping.py
#[...]
@router.get("/this-will-fail-just-because")
async def throw_error():
raise Exception("Exception of my own making")
# app/routers/orders.py
#[...]
@router.post("/")
async def place_order(order: Order):
# [...]
try:
requests.post(
"http://127.0.0.1:8001/shipping/this-will-fail-just-because"
)
except Exception as e:
pass
# [...]
اگر سعی کنیم POST /orders
اکنون ، ما چیزی شبیه به این خواهیم دید:
معیارها
معیارها نشان می دهد که چه مقدار از چیزی وجود دارد. هنگامی که با یک سری زمانی جفت می شویم ، می توانیم مقدار متریک را در یک مقطع زمانی بدانیم. این برای مشاهده داده هایی مانند تعداد درخواست ها در طی یک دوره زمانی مفید است ، یا مواردی مانند استفاده از CPU در طول زمان و غیره.
تنظیم ردیابی متریک سیستم با Logfire بسیار ساده است ، بنابراین ما ابتدا این کار را انجام خواهیم داد.
pip install 'logfire[system-metrics]'
و اکنون در main.py
# app/main.py
# [...]
# [...] logfire config [...]
logfire.instrument_system_metrics()
# [...]
اکنون به پلت فرم Logfire در مرورگر خود بروید ، برگه “داشبورد” را انتخاب کنید. روی دکمه “داشبورد جدید” کلیک کنید. “معیارهای اصلی سیستم (Logfire)” را از پایین کشویی انتخاب کنید.
دوباره سرور را اجرا کنید و باید با داده های سیستم خود نمودار را جمع کنید.
اگر “معیارهای سرور وب” را از پایین آمدن داشبورد ایجاد کنید ، داشبورد آماده دیگری را که حاوی معیارهای مفید از خدمات ما است ، دریافت خواهید کرد.
حال بیایید یک متریک سفارشی اضافه کنیم تا سفارشات موجود در طول زمان را ببینیم. ما از یک پیشخوان برای این یکی استفاده خواهیم کرد.
# app/routers/orders.py
# [...]
import logfire
orders_placed = logfire.metric_counter("orders_placed")
# [...]
@router.post("/")
async def place_order(order: Order):
# [...]
logger.info(
"Order placed",
extra={"order": order_data, "shipping_details": shipping_data},
)
# Increment the metric counter
orders_placed.add(1) # NEW CODE
return {"message": "Order placed", "order": order_data}
اکنون در پلت فرم Logfire ، یک داشبورد جدید ایجاد کرده و “شروع از ابتدا” را از کشویی انتخاب کنید. روی “افزودن نمودار” کلیک کنید. ما باید داده های مورد نیاز خود را از Logfire با استفاده از SQL بازیابی کنیم. برای به دست آوردن ایده ای در مورد چگونگی ساختار نمایش داده های SQL خود ، از برگه “Explore” در پلت فرم Logfire استفاده کنید.
برای به دست آوردن کل سفارشات در یک دوره زمانی ، از پرس و جو زیر استفاده خواهیم کرد:
SELECT
SUM(scalar_value) AS total_orders
FROM metrics
WHERE metric_name = 'orders_placed'
نوع تجسم را به عنوان “مقادیر” تنظیم کرده و نمودار را ذخیره کنید. اکنون نمودار بر اساس دوره زمانی که در بالا انتخاب می کنید به روز می شود.
برای ترسیم این در یک سری زمانی ، از پرس و جو زیر استفاده کنید:
SELECT
time_bucket('%time_bucket_duration%', start_timestamp) AS x,
scalar_value
FROM metrics
WHERE metric_name = 'orders_placed';
تجسم را به عنوان “سری زمانی” انتخاب کنید و معیارها را روی “scalar_value” تنظیم کنید.
پیچیدن
در این پست ، ما رویکرد دستی برای تنظیم مشاهده در یک برنامه FastAPI با استفاده از آتش سوزی، پوشش ورود به سیستم ، ردیابی توزیع شده و معیارهای زمان واقعی با حداقل تنظیم در حالی که ما روی سازهای اتوماتیک متمرکز شده ایم ، حتی بیشتر می توانید کشف کنید – مانند آثار دستی، که به شما کنترل ریز و درشتی بر دهانه ها و سیاهههای مربوط برای بینش های عمیق تر می دهد. اگر از این پست لذت بردید ، برای غواصی های عمیق بیشتر متمرکز بیشتر در خبرنامه من مشترک شوید.