Skip to content

FastAPI 應用範例

pretty-loguru 與 FastAPI 完美整合,提供美觀且實用的 API 日誌記錄功能。本頁面展示如何在 FastAPI 應用中充分利用 pretty-loguru 的視覺化功能。

🚀 基本整合

簡單的 FastAPI 應用

python
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pretty_loguru import create_logger
import time
import uvicorn

# 初始化日誌系統
logger = create_logger(
    name="fastapi_demo",
    log_path="fastapi_logs", preset="development",
    level="INFO"
)

app = FastAPI(title="Pretty Loguru API Demo", version="1.0.1")

@app.on_event("startup")
async def startup_event():
    """應用啟動事件"""
    logger.ascii_header("API START", font="slant", border_style="blue")
    
    logger.block(
        "FastAPI 應用啟動",
        [
            "🚀 應用名稱: Pretty Loguru API Demo",
            "📦 版本: 1.0.1",
            "🌐 環境: Development",
            "📝 日誌系統: pretty-loguru",
            "⚡ 狀態: 準備就緒"
        ],
        border_style="green",
        log_level="SUCCESS"
    )

@app.on_event("shutdown")
async def shutdown_event():
    """應用關閉事件"""
    logger.ascii_header("SHUTDOWN", font="standard", border_style="yellow")
    logger.warning("FastAPI 應用正在關閉...")

@app.get("/")
async def root():
    logger.info("收到根路徑請求")
    return {"message": "Hello World", "status": "success"}

@app.get("/health")
async def health_check():
    """健康檢查端點"""
    logger.info("執行健康檢查")
    
    health_status = {
        "status": "healthy",
        "timestamp": time.time(),
        "version": "1.0.1"
    }
    
    logger.block(
        "健康檢查結果",
        [
            "✅ 應用狀態: 正常",
            "✅ 資料庫: 連接正常",
            "✅ 快取: 運行正常",
            f"⏰ 檢查時間: {time.strftime('%Y-%m-%d %H:%M:%S')}"
        ],
        border_style="green"
    )
    
    return health_status

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

🔧 中間件整合

請求日誌中間件

python
from fastapi import FastAPI, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
import time
import json

class PrettyLoguruMiddleware(BaseHTTPMiddleware):
    """Pretty Loguru 日誌中間件"""
    
    async def dispatch(self, request: Request, call_next):
        # 記錄請求開始
        start_time = time.time()
        
        # 獲取請求資訊
        method = request.method
        url = str(request.url)
        client_ip = request.client.host if request.client else "unknown"
        user_agent = request.headers.get("user-agent", "unknown")
        
        logger.ascii_header("REQUEST", font="small", border_style="cyan")
        
        logger.block(
            "新的 API 請求",
            [
                f"🌐 方法: {method}",
                f"📍 URL: {url}",
                f"💻 客戶端 IP: {client_ip}",
                f"🖥️  User-Agent: {user_agent[:50]}...",
                f"⏰ 時間: {time.strftime('%Y-%m-%d %H:%M:%S')}"
            ],
            border_style="blue"
        )
        
        # 處理請求
        try:
            response = await call_next(request)
            process_time = time.time() - start_time
            
            # 根據響應狀態選擇顏色
            if response.status_code < 300:
                color = "green"
                level = "SUCCESS"
            elif response.status_code < 400:
                color = "yellow" 
                level = "WARNING"
            else:
                color = "red"
                level = "ERROR"
            
            logger.block(
                "請求處理完成",
                [
                    f"📊 狀態碼: {response.status_code}",
                    f"⏱️  處理時間: {process_time:.3f}s",
                    f"📦 響應大小: {len(response.body) if hasattr(response, 'body') else 'N/A'} bytes",
                    f"✅ 狀態: {'成功' if response.status_code < 400 else '失敗'}"
                ],
                border_style=color,
                log_level=level
            )
            
            return response
            
        except Exception as e:
            process_time = time.time() - start_time
            
            logger.ascii_block(
                "請求處理失敗",
                [
                    f"❌ 錯誤: {str(e)}",
                    f"⏱️  處理時間: {process_time:.3f}s",
                    f"🔍 請求: {method} {url}",
                    f"💻 客戶端: {client_ip}"
                ],
                ascii_header="ERROR",
                ascii_font="doom",
                border_style="red",
                log_level="ERROR"
            )
            
            raise

# 應用中間件
app = FastAPI()
app.add_middleware(PrettyLoguruMiddleware)

錯誤處理中間件

python
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    """HTTP 例外處理器"""
    
    logger.ascii_block(
        "HTTP 例外處理",
        [
            f"❌ 狀態碼: {exc.status_code}",
            f"📝 詳細訊息: {exc.detail}",
            f"🌐 請求路徑: {request.url.path}",
            f"📊 請求方法: {request.method}",
            f"💻 客戶端 IP: {request.client.host if request.client else 'unknown'}"
        ],
        ascii_header="HTTP ERROR",
        ascii_font="standard",
        border_style="red",
        log_level="ERROR"
    )
    
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail, "status_code": exc.status_code}
    )

@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    """一般例外處理器"""
    
    logger.ascii_block(
        "系統例外處理",
        [
            f"💥 例外類型: {type(exc).__name__}",
            f"📝 錯誤訊息: {str(exc)}",
            f"🌐 請求路徑: {request.url.path}",
            f"📊 請求方法: {request.method}",
            f"🔍 需要檢查: 程式碼邏輯"
        ],
        ascii_header="EXCEPTION", 
        ascii_font="doom",
        border_style="red",
        log_level="CRITICAL"
    )
    
    return JSONResponse(
        status_code=500,
        content={"detail": "Internal server error", "status_code": 500}
    )

📊 業務邏輯日誌

用戶認證系統

python
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from typing import Optional
import jwt
import hashlib

security = HTTPBearer()

class AuthService:
    """認證服務"""
    
    def __init__(self):
        self.secret_key = "your-secret-key"
        self.algorithm = "HS256"
    
    async def login(self, username: str, password: str):
        """用戶登入"""
        logger.ascii_header("LOGIN", font="standard", border_style="blue")
        
        logger.block(
            "用戶登入嘗試",
            [
                f"👤 用戶名: {username}",
                f"🕐 時間: {time.strftime('%Y-%m-%d %H:%M:%S')}",
                f"🔐 密碼雜湊: {hashlib.md5(password.encode()).hexdigest()[:8]}...",
                f"🌐 請求來源: 登入端點"
            ],
            border_style="cyan"
        )
        
        # 模擬認證邏輯
        if username == "admin" and password == "password":
            # 成功登入
            token = jwt.encode(
                {"username": username, "exp": time.time() + 3600}, 
                self.secret_key, 
                algorithm=self.algorithm
            )
            
            logger.ascii_block(
                "登入成功",
                [
                    f"✅ 用戶: {username}",
                    f"🎫 Token 已生成",
                    f"⏰ 有效期: 1 小時",
                    f"🔒 算法: {self.algorithm}",
                    f"🎯 狀態: 認證成功"
                ],
                ascii_header="SUCCESS",
                ascii_font="slant",
                border_style="green",
                log_level="SUCCESS"
            )
            
            return {"access_token": token, "token_type": "bearer"}
        else:
            # 登入失敗
            logger.ascii_block(
                "登入失敗",
                [
                    f"❌ 用戶: {username}",
                    f"🚫 原因: 認證資訊無效",
                    f"⚠️  風險等級: 中",
                    f"🔍 建議: 檢查用戶名和密碼",
                    f"📊 失敗次數: 需要追蹤"
                ],
                ascii_header="FAILED",
                ascii_font="doom",
                border_style="red",
                log_level="WARNING"
            )
            
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="認證失敗"
            )
    
    async def verify_token(self, credentials: HTTPAuthorizationCredentials = Depends(security)):
        """驗證 Token"""
        try:
            payload = jwt.decode(
                credentials.credentials,
                self.secret_key,
                algorithms=[self.algorithm]
            )
            username = payload.get("username")
            
            logger.debug(f"Token 驗證成功: {username}")
            return username
            
        except jwt.ExpiredSignatureError:
            logger.warning("Token 已過期")
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token 已過期"
            )
        except jwt.InvalidTokenError:
            logger.error("無效的 Token")
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, 
                detail="無效的 Token"
            )

auth_service = AuthService()

@app.post("/login")
async def login_endpoint(username: str, password: str):
    return await auth_service.login(username, password)

@app.get("/protected")
async def protected_route(current_user: str = Depends(auth_service.verify_token)):
    logger.info(f"受保護路由訪問: {current_user}")
    return {"message": f"Hello {current_user}", "status": "authorized"}

資料庫操作日誌

python
from typing import List, Optional
import asyncio

class UserService:
    """用戶服務"""
    
    async def create_user(self, user_data: dict):
        """創建用戶"""
        logger.ascii_header("CREATE USER", font="standard", border_style="green")
        
        logger.block(
            "創建用戶請求",
            [
                f"👤 用戶名: {user_data.get('username')}",
                f"📧 郵箱: {user_data.get('email')}",
                f"🏷️  角色: {user_data.get('role', 'user')}",
                f"🕐 創建時間: {time.strftime('%Y-%m-%d %H:%M:%S')}"
            ],
            border_style="blue"
        )
        
        try:
            # 模擬資料庫操作
            await asyncio.sleep(0.1)  # 模擬資料庫延遲
            
            # 模擬驗證
            if not user_data.get('username'):
                raise ValueError("用戶名不能為空")
            
            if not user_data.get('email'):
                raise ValueError("郵箱不能為空")
            
            # 模擬成功創建
            user_id = hash(user_data['username']) % 10000
            
            logger.ascii_block(
                "用戶創建成功",
                [
                    f"✅ 用戶 ID: {user_id}",
                    f"👤 用戶名: {user_data['username']}",
                    f"📧 郵箱: {user_data['email']}",
                    f"🎯 狀態: 已激活",
                    f"📊 資料庫: 已同步"
                ],
                ascii_header="CREATED",
                ascii_font="slant",
                border_style="green",
                log_level="SUCCESS"
            )
            
            return {"user_id": user_id, "status": "created"}
            
        except Exception as e:
            logger.ascii_block(
                "用戶創建失敗",
                [
                    f"❌ 錯誤: {str(e)}",
                    f"📝 用戶資料: {user_data}",
                    f"🔍 需要檢查: 輸入驗證",
                    f"💾 資料庫狀態: 未變更"
                ],
                ascii_header="FAILED",
                ascii_font="doom", 
                border_style="red",
                log_level="ERROR"
            )
            
            raise HTTPException(
                status_code=400,
                detail=f"創建用戶失敗: {str(e)}"
            )
    
    async def get_users(self, limit: int = 10, offset: int = 0):
        """獲取用戶列表"""
        logger.info(f"查詢用戶列表: limit={limit}, offset={offset}")
        
        # 模擬資料庫查詢
        await asyncio.sleep(0.05)
        
        users = [
            {"id": i, "username": f"user{i}", "email": f"user{i}@example.com"}
            for i in range(offset, offset + limit)
        ]
        
        logger.block(
            "用戶查詢結果",
            [
                f"📊 查詢條件: limit={limit}, offset={offset}",
                f"📈 返回數量: {len(users)}",
                f"⏱️  查詢時間: 0.05s",
                f"💾 快取狀態: 未使用"
            ],
            border_style="blue"
        )
        
        return users

user_service = UserService()

@app.post("/users")
async def create_user_endpoint(user_data: dict):
    return await user_service.create_user(user_data)

@app.get("/users")
async def get_users_endpoint(limit: int = 10, offset: int = 0):
    return await user_service.get_users(limit, offset)

📈 性能監控

API 性能追蹤

python
import asyncio
from functools import wraps
from time import perf_counter

def performance_monitor(func):
    """性能監控裝飾器"""
    @wraps(func)
    async def wrapper(*args, **kwargs):
        start_time = perf_counter()
        
        try:
            result = await func(*args, **kwargs)
            end_time = perf_counter()
            execution_time = end_time - start_time
            
            # 根據執行時間決定警告級別
            if execution_time > 1.0:
                color = "red"
                level = "WARNING"
                status = "⚠️  緩慢"
            elif execution_time > 0.5:
                color = "yellow"
                level = "WARNING" 
                status = "⚠️  偏慢"
            else:
                color = "green"
                level = "INFO"
                status = "✅ 正常"
            
            logger.block(
                f"函數執行監控: {func.__name__}",
                [
                    f"⏱️  執行時間: {execution_time:.3f}s",
                    f"📊 狀態: {status}",
                    f"🎯 函數: {func.__name__}",
                    f"📈 效能等級: {'優秀' if execution_time < 0.1 else '良好' if execution_time < 0.5 else '需優化'}"
                ],
                border_style=color,
                log_level=level
            )
            
            return result
            
        except Exception as e:
            end_time = perf_counter()
            execution_time = end_time - start_time
            
            logger.ascii_block(
                "函數執行異常",
                [
                    f"❌ 函數: {func.__name__}",
                    f"💥 異常: {str(e)}",
                    f"⏱️  執行時間: {execution_time:.3f}s",
                    f"🔍 需要調查: 異常原因"
                ],
                ascii_header="EXCEPTION",
                ascii_font="doom",
                border_style="red",
                log_level="ERROR"
            )
            
            raise
    
    return wrapper

@app.get("/slow-endpoint")
@performance_monitor
async def slow_endpoint():
    """模擬緩慢的端點"""
    logger.info("開始執行緩慢操作...")
    await asyncio.sleep(2)  # 模擬緩慢操作
    return {"message": "操作完成", "duration": "2 seconds"}

@app.get("/fast-endpoint")
@performance_monitor
async def fast_endpoint():
    """模擬快速的端點"""
    logger.info("執行快速操作")
    return {"message": "快速響應", "duration": "immediate"}

🔧 配置管理

環境配置

python
from pydantic import BaseSettings
import os

class Settings(BaseSettings):
    """應用設定"""
    
    app_name: str = "Pretty Loguru FastAPI"
    app_version: str = "1.0.1"
    debug: bool = False
    log_level: str = "INFO"
    log_folder: str = "api_logs"
    
    # 資料庫設定
    database_url: str = "postgresql://localhost/mydb"
    
    # Redis 設定
    redis_url: str = "redis://localhost:6379"
    
    class Config:
        env_file = ".env"

settings = Settings()

@app.on_event("startup")
async def startup_with_config():
    """使用配置啟動"""
    
    # 根據環境配置日誌
    if settings.debug:
        create_logger(preset="debug", folder=settings.log_folder)
    else:
        create_logger(preset="production", folder=settings.log_folder)
    
    logger.ascii_header("CONFIG LOADED", font="standard", border_style="cyan")
    
    logger.block(
        "應用配置",
        [
            f"📱 應用名稱: {settings.app_name}",
            f"📦 版本: {settings.app_version}",
            f"🔧 除錯模式: {'啟用' if settings.debug else '停用'}",
            f"📊 日誌級別: {settings.log_level}",
            f"📁 日誌目錄: {settings.log_folder}",
            f"🗄️  資料庫: {settings.database_url.split('@')[0]}@***",
            f"🔴 Redis: {settings.redis_url}"
        ],
        border_style="green"
    )

@app.get("/config")
async def get_config():
    """獲取應用配置"""
    logger.info("請求應用配置資訊")
    
    config_info = {
        "app_name": settings.app_name,
        "version": settings.app_version,
        "debug": settings.debug,
        "log_level": settings.log_level
    }
    
    logger.block(
        "配置資訊請求",
        [f"{key}: {value}" for key, value in config_info.items()],
        border_style="blue"
    )
    
    return config_info

🚀 完整應用範例

結合所有功能的完整 FastAPI 應用:

python
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.middleware.cors import CORSMiddleware
from pretty_loguru import create_logger
import uvicorn
import time
import asyncio

# 初始化應用
app = FastAPI(
    title="Pretty Loguru FastAPI Demo",
    description="展示 pretty-loguru 與 FastAPI 整合的完整範例",
    version="1.0.1"
)

# 添加 CORS 中間件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 添加日誌中間件
app.add_middleware(PrettyLoguruMiddleware)

@app.on_event("startup")
async def startup():
    """應用啟動"""
    create_logger(preset="development", folder="fastapi_complete_logs")
    
    logger.ascii_block(
        "FastAPI 應用啟動完成",
        [
            "🚀 服務名稱: Pretty Loguru FastAPI Demo",
            "📦 版本: 1.0.1",
            "🌐 CORS: 已啟用",
            "📝 日誌中間件: 已載入",
            "🔧 認證系統: 已初始化",
            "💾 服務連接: 準備就緒",
            "⚡ 狀態: 完全啟動"
        ],
        ascii_header="ONLINE",
        ascii_font="block",
        border_style="green",
        log_level="SUCCESS"
    )

@app.on_event("shutdown")
async def shutdown():
    """應用關閉"""
    logger.ascii_block(
        "FastAPI 應用關閉",
        [
            "🛑 正在關閉服務...",
            "💾 清理資源連接",
            "📝 保存日誌狀態",
            "🔒 關閉認證系統",
            "✅ 優雅關閉完成"
        ],
        ascii_header="SHUTDOWN",
        ascii_font="standard",
        border_style="yellow",
        log_level="WARNING"
    )

# 包含所有路由
app.include_router(auth_router, prefix="/auth", tags=["認證"])
app.include_router(user_router, prefix="/users", tags=["用戶"])
app.include_router(monitor_router, prefix="/monitor", tags=["監控"])

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        reload=True,
        log_config=None  # 使用 pretty-loguru 而不是 uvicorn 的日誌
    )

這個完整的 FastAPI 範例展示了 pretty-loguru 在實際 Web 應用中的強大功能,提供專業級的日誌記錄和監控能力!

基於 MIT 許可證發布