feat: add privacy-safe structured logging
This commit is contained in:
@@ -4,8 +4,11 @@ from collections import Counter
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
import time as time_module
|
||||
from typing import Annotated, Literal
|
||||
from uuid import UUID
|
||||
import uuid
|
||||
|
||||
import httpx
|
||||
from fastapi import Depends, FastAPI, File, Header, HTTPException, Query, Request, Response, UploadFile
|
||||
@@ -19,12 +22,15 @@ from .database import get_session
|
||||
from .config import settings
|
||||
from .community_review import ExternalReviewError, map_observation, publish_observation, reject_observation
|
||||
from .importer import ImportSourceError, import_records, normalize
|
||||
from .logging_config import configure_logging
|
||||
from .models import Bait, BaitKind, CatchReport, ExternalObservation, Fish, ModerationEvent, ModerationStatus, OfficialRecordImport, SourceType, Spot, SubmissionAttempt, Waterbody
|
||||
from .readiness import readiness_report
|
||||
from .schemas import ActivityOut, AdminCatchReportOut, BaitOut, CatchOut, CatchReportCreate, CatchReportCreated, ExternalObservationDecision, ExternalObservationMapping, ExternalObservationOut, ExternalObservationPublished, FishOut, ImportRunOut, ModerationUpdate, OfficialRecordOut, SpotOut, WaterbodyOut
|
||||
from .storage import ScreenshotError, client as storage_client, delete_screenshot, signed_screenshot_url, upload_screenshot
|
||||
|
||||
|
||||
configure_logging(settings.log_level)
|
||||
logger = logging.getLogger("rf4.api")
|
||||
app = FastAPI(title="RF4 Spotter API", version="0.1.0")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -35,6 +41,31 @@ app.add_middleware(
|
||||
Db = Annotated[Session, Depends(get_session)]
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def structured_request_log(request: Request, call_next):
|
||||
request_id = uuid.uuid4().hex
|
||||
started = time_module.perf_counter()
|
||||
status_code = 500
|
||||
try:
|
||||
response = await call_next(request)
|
||||
status_code = response.status_code
|
||||
response.headers["X-Request-ID"] = request_id
|
||||
return response
|
||||
except Exception as exc:
|
||||
logger.error("request failed", extra={"request_id": request_id, "error_type": type(exc).__name__})
|
||||
raise
|
||||
finally:
|
||||
logger.log(
|
||||
logging.DEBUG if request.url.path in {"/health", "/ready"} else logging.INFO,
|
||||
"request completed",
|
||||
extra={
|
||||
"request_id": request_id, "method": request.method,
|
||||
"path": request.url.path, "status_code": status_code,
|
||||
"duration_ms": round((time_module.perf_counter() - started) * 1000, 2),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user