improved logging
This commit is contained in:
@@ -10,6 +10,7 @@ static single-page frontend is mounted at ``/`` and the JSON/WS API under
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -19,6 +20,8 @@ from python_app.webui.controller import WebController
|
||||
from python_app.webui.routes import router
|
||||
from python_app.webui.streaming import RingBroadcaster
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_STATIC_DIR = Path(__file__).resolve().parent / "static"
|
||||
|
||||
|
||||
@@ -31,10 +34,12 @@ def create_app(controller: WebController) -> FastAPI:
|
||||
app.state.controller = controller
|
||||
app.state.broadcaster = broadcaster
|
||||
broadcaster.start()
|
||||
logger.info("Web UI application started")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await broadcaster.stop()
|
||||
logger.info("Web UI application stopped")
|
||||
|
||||
app = FastAPI(title="Radar Web UI", lifespan=lifespan)
|
||||
app.include_router(router)
|
||||
|
||||
@@ -10,12 +10,15 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, Request, WebSocket, WebSocketDisconnect
|
||||
|
||||
from python_app.webui.controller import WebController
|
||||
from python_app.webui.streaming import RingBroadcaster
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -30,6 +33,7 @@ async def get_status(request: Request) -> dict:
|
||||
|
||||
@router.post("/api/start")
|
||||
async def post_start(request: Request) -> dict:
|
||||
logger.info("Web UI request: start")
|
||||
controller = _controller(request)
|
||||
controller.start()
|
||||
return controller.status()
|
||||
@@ -37,6 +41,7 @@ async def post_start(request: Request) -> dict:
|
||||
|
||||
@router.post("/api/single_capture")
|
||||
async def post_single_capture(request: Request) -> dict:
|
||||
logger.info("Web UI request: single capture")
|
||||
controller = _controller(request)
|
||||
controller.single_capture()
|
||||
return controller.status()
|
||||
@@ -44,6 +49,7 @@ async def post_single_capture(request: Request) -> dict:
|
||||
|
||||
@router.post("/api/stop")
|
||||
async def post_stop(request: Request) -> dict:
|
||||
logger.info("Web UI request: stop")
|
||||
controller = _controller(request)
|
||||
controller.stop()
|
||||
return controller.status()
|
||||
@@ -51,6 +57,7 @@ async def post_stop(request: Request) -> dict:
|
||||
|
||||
@router.post("/api/tmp_reference")
|
||||
async def post_tmp_reference(request: Request) -> dict:
|
||||
logger.info("Web UI request: capture temporary reference")
|
||||
controller = _controller(request)
|
||||
controller.capture_tmp_reference()
|
||||
return controller.status()
|
||||
@@ -66,6 +73,7 @@ async def post_live_settings(request: Request, fields: dict = Body(default={}))
|
||||
try:
|
||||
return _controller(request).apply_live_settings(fields)
|
||||
except ValueError as exc:
|
||||
logger.warning("Web UI rejected live settings update: %s", exc)
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@@ -76,11 +84,12 @@ async def ws(websocket: WebSocket) -> None:
|
||||
broadcaster: RingBroadcaster = websocket.app.state.broadcaster
|
||||
queue: asyncio.Queue[dict] = asyncio.Queue(maxsize=1)
|
||||
broadcaster.register(queue)
|
||||
logger.info("Web UI client connected")
|
||||
try:
|
||||
while True:
|
||||
await websocket.send_json(await queue.get())
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
logger.info("Web UI client disconnected")
|
||||
finally:
|
||||
broadcaster.unregister(queue)
|
||||
with contextlib.suppress(Exception):
|
||||
|
||||
@@ -30,6 +30,8 @@ class WebUiServer:
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start serving on the background thread."""
|
||||
config = self._server.config
|
||||
logger.info("Starting web UI server on %s:%s", config.host, config.port)
|
||||
self._thread.start()
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
@@ -49,5 +51,8 @@ class WebUiServer:
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Ask uvicorn to exit and wait briefly for the thread to unwind."""
|
||||
logger.info("Stopping web UI server")
|
||||
self._server.should_exit = True
|
||||
self._thread.join(timeout=5.0)
|
||||
if self._thread.is_alive():
|
||||
logger.warning("Web UI server thread did not stop within timeout")
|
||||
|
||||
@@ -37,16 +37,19 @@ class RingBroadcaster:
|
||||
def register(self, queue: asyncio.Queue[dict]) -> None:
|
||||
"""Add a client queue to receive subsequent frames and status."""
|
||||
self._clients.add(queue)
|
||||
logger.debug("Registered web client queue (clients=%d)", len(self._clients))
|
||||
|
||||
def unregister(self, queue: asyncio.Queue[dict]) -> None:
|
||||
"""Remove a client queue; safe to call more than once."""
|
||||
self._clients.discard(queue)
|
||||
logger.debug("Unregistered web client queue (clients=%d)", len(self._clients))
|
||||
|
||||
def start(self) -> None:
|
||||
"""Launch the single polling task (idempotent)."""
|
||||
if self._task is None or self._task.done():
|
||||
self._task = asyncio.create_task(self._run(), name="ring-broadcaster")
|
||||
self._task.add_done_callback(self._on_task_done)
|
||||
logger.info("Ring broadcaster started")
|
||||
|
||||
@staticmethod
|
||||
def _on_task_done(task: "asyncio.Task[None]") -> None:
|
||||
@@ -62,6 +65,7 @@ class RingBroadcaster:
|
||||
with contextlib.suppress(asyncio.CancelledError):
|
||||
await self._task
|
||||
self._task = None
|
||||
logger.info("Ring broadcaster stopped")
|
||||
|
||||
def _publish(self, message: dict) -> None:
|
||||
"""Push a message to every client, dropping the oldest on a full queue."""
|
||||
|
||||
Reference in New Issue
Block a user