fastapi-service
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFastAPI Service
FastAPI 服务
Build production-grade FastAPI services with JWT authentication, structured logging via structlog, and Prometheus metrics.
构建具备JWT认证、基于structlog的结构化日志以及Prometheus指标监控的生产级FastAPI服务。
Quick Start
快速开始
python
import os
import sys
import logging
from contextlib import asynccontextmanager
from typing import Dict
import structlog
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
from prometheus_fastapi_instrumentator import Instrumentatorpython
import os
import sys
import logging
from contextlib import asynccontextmanager
from typing import Dict
import structlog
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
from prometheus_fastapi_instrumentator import Instrumentator-------------------------
-------------------------
Logging configuration
Logging configuration
-------------------------
-------------------------
def configure_logging() -> None:
log_level_name = os.getenv("LOG_LEVEL", "INFO").upper()
log_level = getattr(logging, log_level_name, logging.INFO)
json_logs = os.getenv("LOG_JSON", "false").lower() == "true"
service_name = os.getenv("SERVICE_NAME", "fastapi-app")
logging.basicConfig(level=log_level, stream=sys.stdout)
processors = [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
]
if json_logs:
processors.append(structlog.processors.JSONRenderer())
else:
processors.append(structlog.dev.ConsoleRenderer())
structlog.configure(
processors=processors,
wrapper_class=structlog.make_filtering_bound_logger(log_level),
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=True,
)
structlog.contextvars.bind_contextvars(service=service_name)configure_logging()
log = structlog.get_logger()
def configure_logging() -> None:
log_level_name = os.getenv("LOG_LEVEL", "INFO").upper()
log_level = getattr(logging, log_level_name, logging.INFO)
json_logs = os.getenv("LOG_JSON", "false").lower() == "true"
service_name = os.getenv("SERVICE_NAME", "fastapi-app")
logging.basicConfig(level=log_level, stream=sys.stdout)
processors = [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
]
if json_logs:
processors.append(structlog.processors.JSONRenderer())
else:
processors.append(structlog.dev.ConsoleRenderer())
structlog.configure(
processors=processors,
wrapper_class=structlog.make_filtering_bound_logger(log_level),
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=True,
)
structlog.contextvars.bind_contextvars(service=service_name)configure_logging()
log = structlog.get_logger()
-------------------------
-------------------------
Auth configuration
Auth configuration
-------------------------
-------------------------
JWT_SECRET = os.getenv("AUTH_SECRET", "change-me")
JWT_ALG = os.getenv("JWT_ALG", "HS256")
security = HTTPBearer(auto_error=True)
def require_claims(
credentials: HTTPAuthorizationCredentials = Depends(security),
) -> Dict:
token = credentials.credentials
try:
claims = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
return claims
JWT_SECRET = os.getenv("AUTH_SECRET", "change-me")
JWT_ALG = os.getenv("JWT_ALG", "HS256")
security = HTTPBearer(auto_error=True)
def require_claims(
credentials: HTTPAuthorizationCredentials = Depends(security),
) -> Dict:
token = credentials.credentials
try:
claims = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
return claims
-------------------------
-------------------------
App & instrumentation
App & instrumentation
-------------------------
-------------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
log.info("service.startup")
try:
yield
finally:
log.info("service.shutdown")
app = FastAPI(title=os.getenv("SERVICE_NAME", "fastapi-app"), lifespan=lifespan)
@asynccontextmanager
async def lifespan(app: FastAPI):
log.info("service.startup")
try:
yield
finally:
log.info("service.shutdown")
app = FastAPI(title=os.getenv("SERVICE_NAME", "fastapi-app"), lifespan=lifespan)
Prometheus: exposes /metrics by default
Prometheus: exposes /metrics by default
Instrumentator().instrument(app).expose(app)
@app.get("/healthz", tags=["health"])
def healthz():
return {"status": "ok"}
@app.get("/whoami", tags=["auth"])
def whoami(claims: Dict = Depends(require_claims)):
return {"claims": claims}
if name == "main":
import uvicorn
host = os.getenv("HOST", "0.0.0.0")
port = int(os.getenv("PORT", "8000"))
uvicorn.run("main:app", host=host, port=port, reload=False)undefinedInstrumentator().instrument(app).expose(app)
@app.get("/healthz", tags=["health"])
def healthz():
return {"status": "ok"}
@app.get("/whoami", tags=["auth"])
def whoami(claims: Dict = Depends(require_claims)):
return {"claims": claims}
if name == "main":
import uvicorn
host = os.getenv("HOST", "0.0.0.0")
port = int(os.getenv("PORT", "8000"))
uvicorn.run("main:app", host=host, port=port, reload=False)undefinedDependencies
依赖项
bash
uv add fastapi "uvicorn[standard]" "python-jose[cryptography]" prometheus-fastapi-instrumentator structlogbash
uv add fastapi "uvicorn[standard]" "python-jose[cryptography]" prometheus-fastapi-instrumentator structlogEnvironment
环境配置
Create :
.env.serverexport AUTH_SECRET="your-strong-shared-secret"
export LOG_LEVEL=INFO
export LOG_JSON=false创建 :
.env.serverexport AUTH_SECRET="your-strong-shared-secret"
export LOG_LEVEL=INFO
export LOG_JSON=falseMakefile
Makefile
Makefile
SHELL := /bin/bash
define setup_env
$(eval ENV_FILE := $(1))
$(eval include $(1))
$(eval export)
endef
run-server:
$(call setup_env, .env.server)
uv run uvicorn main:app --host 0.0.0.0 --port 8000Makefile
SHELL := /bin/bash
define setup_env
$(eval ENV_FILE := $(1))
$(eval include $(1))
$(eval export)
endef
run-server:
$(call setup_env, .env.server)
uv run uvicorn main:app --host 0.0.0.0 --port 8000Testing Auth
认证测试
Generate a test token:
bash
uv run python - <<'PY'
from jose import jwt
print(jwt.encode({"sub":"alice","role":"admin"}, "your-strong-shared-secret", algorithm="HS256"))
PYCall the authenticated endpoint:
bash
curl -H "Authorization: Bearer <token>" http://localhost:8000/whoami生成测试令牌:
bash
uv run python - <<'PY'
from jose import jwt
print(jwt.encode({"sub":"alice","role":"admin"}, "your-strong-shared-secret", algorithm="HS256"))
PY调用需认证的接口:
bash
curl -H "Authorization: Bearer <token>" http://localhost:8000/whoamiKey Patterns
核心模式
- Lifespan: Use for startup/shutdown, not deprecated
@asynccontextmanageron_event - Dependency injection: Use for auth, DB connections, and shared resources
Depends() - Structured logging: Configure structlog once at module level; bind context vars per-request
- Metrics: auto-instruments all routes
prometheus-fastapi-instrumentator - Health check: Always expose for k8s readiness/liveness probes
/healthz
- Lifespan: 使用处理启动/关闭逻辑,而非已弃用的
@asynccontextmanageron_event - Dependency injection: 使用实现认证、数据库连接及共享资源的依赖注入
Depends() - Structured logging: 在模块级别统一配置structlog;为每个请求绑定上下文变量
- Metrics: 会自动为所有路由添加监控埋点
prometheus-fastapi-instrumentator - Health check: 始终暴露接口,用于K8s的就绪/存活探针
/healthz
Bookkeeping
后续维护
After modifying the server, update the adjacent README.md to reflect the current API surface.
修改服务器代码后,请更新相邻的README.md文件,以反映当前的API接口情况。