Add colored HTTP/WebSocket access logging to fastapi_vue.server.

run() gains access_log (default on) and log_config parameters.  When
enabled, uvicorn's own access log is disabled (access_log=False) and
replaced by an ASGI middleware (adapted from the uvicorn access-log
branch) that logs colored request lines, WebSocket open/close pairs,
and pre-accept rejections in open format.

log_config dicts (uvicorn's default or user-supplied) are patched
best-effort via a registered patch pipeline in fastapi_vue.logging:
swapping in our AccessFormatter, routing our output to a private
fastapi_vue.access logger (uvicorn gates its protocol-level access
logging on uvicorn.access.hasHandlers(), so that logger must stay
handlerless), and filtering stock WebSocket handshake chatter from
uvicorn.error.  Non-dict or structurally unexpected configs pass
through untouched.

The middleware is installed by patching Config.load/load_app, hooked
into AccessFormatter instantiation so reload/worker subprocesses
(which re-apply log_config without re-calling run()) are covered.
This commit is contained in:
2026-08-31 04:16:43 +00:00
parent 52ca3f4f49
commit 77a34753d9
3 changed files with 602 additions and 1 deletions
+15 -1
View File
@@ -11,17 +11,20 @@ import uvicorn
from uvicorn import Config, Server
from .hostutil import parse_endpoints
from .logging import install_access_log, patch_log_config
logger = logging.getLogger(__name__)
def run(
def run( # noqa: PLR0913
app: str,
*,
listen: str | list[str] | None = None,
default_port: int = 8000,
reload: bool | Path = False,
workers: int | None = None,
access_log: bool = True,
log_config: Any = uvicorn.config.LOGGING_CONFIG, # noqa: ANN401
**uvicorn_config: Any, # noqa: ANN401
) -> None:
"""Run uvicorn server(s) for the given app.
@@ -34,6 +37,11 @@ def run(
directory. True enables reload without setting a reload directory.
False disables reload and clears any reload_dirs.
workers: Number of worker processes (requires uvicorn.run, single endpoint only).
access_log: Enable our colored HTTP/WebSocket access logging (uvicorn's
own access log is disabled either way).
log_config: Logging config passed to uvicorn. When access_log is
enabled, dict configs are patched best-effort for our access log
formatting (see fastapi_vue.logging.patch_log_config).
**uvicorn_config: Additional uvicorn config options (overrides all other settings).
"""
@@ -47,6 +55,12 @@ def run(
elif not reload:
uvicorn_config.pop("reload_dirs", None)
if access_log:
install_access_log()
log_config = patch_log_config(log_config)
uvicorn_config["access_log"] = False
uvicorn_config["log_config"] = log_config
conf: dict[str, object] = {"app": app, "reload": bool(reload), "workers": workers}
proxy = os.getenv("FORWARDED_ALLOW_IPS", "127.0.0.1,::1")
if proxy: