برنامه نویسی

مشاهدات آسان ساخته شده است: اضافه کردن سیاهههای مربوط ، آثار و معیارها به 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)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اکنون تمام درخواست های شما به هر دو سرور به صورت خودکار ابزار می شوند.

fastapi-with-logfire

اکنون تمام سیاهههای مربوط به سطح خدمات تحت درخواست های مربوطه به طور مرتب و مرتب شده اند. اما این را بدست آورید – می تواند حتی بهتر شود.

وارد کنید – ردیابی.

ردیابی توزیع شده

بنابراین ما برنامه خود را به گونه ای تنظیم کردیم 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 با استفاده از آتش سوزی، پوشش ورود به سیستم ، ردیابی توزیع شده و معیارهای زمان واقعی با حداقل تنظیم در حالی که ما روی سازهای اتوماتیک متمرکز شده ایم ، حتی بیشتر می توانید کشف کنید – مانند آثار دستی، که به شما کنترل ریز و درشتی بر دهانه ها و سیاهههای مربوط برای بینش های عمیق تر می دهد. اگر از این پست لذت بردید ، برای غواصی های عمیق بیشتر متمرکز بیشتر در خبرنامه من مشترک شوید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا