Add public setup_logging() for standalone pretty logging
Optionally usable early in CLI mains, devservers and scripts that log before (or without) server.run(). Shares patch_log_config with the server path (same log_config overrides), but installs no server-side patches: Formatter gains an install flag gating the lifespan/error middleware monkeypatches, and the NullHandler backdoor is skipped. dev=None follows env.dev for the root level, True/False override it. Pre-existing loggers survive the dictConfig (disable_existing_loggers is set False).
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"""FastAPI Vue integration - serve Vue frontend from FastAPI."""
|
"""FastAPI Vue integration - serve Vue frontend from FastAPI."""
|
||||||
|
|
||||||
from .environ import env, teleport
|
from .environ import env, teleport
|
||||||
|
from .logging import setup_logging
|
||||||
from .staticfiles import Frontend
|
from .staticfiles import Frontend
|
||||||
|
|
||||||
__all__ = ["Frontend", "env", "teleport"]
|
__all__ = ["Frontend", "env", "setup_logging", "teleport"]
|
||||||
|
|||||||
@@ -98,14 +98,16 @@ class Formatter(logging.Formatter):
|
|||||||
use_colors: bool | None = None, # noqa: FBT001 # mirrors logging.Formatter
|
use_colors: bool | None = None, # noqa: FBT001 # mirrors logging.Formatter
|
||||||
*,
|
*,
|
||||||
access: bool = False,
|
access: bool = False,
|
||||||
|
install: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Load tracerite, optionally install the access log, detect color support."""
|
"""Load tracerite, optionally install server patches and access log."""
|
||||||
tracerite.load()
|
tracerite.load()
|
||||||
tracerite.load_suppressions(
|
tracerite.load_suppressions(
|
||||||
extra={"starlette.routing": "until", "fastapi.routing": "until"}
|
extra={"starlette.routing": "until", "fastapi.routing": "until"}
|
||||||
)
|
)
|
||||||
patch_lifespan_logging()
|
if install:
|
||||||
patch_server_error_middleware()
|
patch_lifespan_logging()
|
||||||
|
patch_server_error_middleware()
|
||||||
if access:
|
if access:
|
||||||
install_access_log()
|
install_access_log()
|
||||||
if use_colors in (True, False):
|
if use_colors in (True, False):
|
||||||
@@ -309,7 +311,35 @@ def _merge_log_config(base: dict, overlay: dict) -> dict:
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
def patch_log_config(log_config, *, access_log: bool = True): # noqa: ANN001, ANN201
|
def setup_logging(*, log_config: dict | None = None, dev: bool | None = None) -> None:
|
||||||
|
"""Set up pretty logging standalone, outside of ``server.run()``.
|
||||||
|
|
||||||
|
Optional helper for CLI mains, devservers and scripts that log before
|
||||||
|
(or without) starting the server. Loads tracerite directly and applies
|
||||||
|
the same patching as the server path (see ``patch_log_config``, a
|
||||||
|
private helper) with ``logging.config.dictConfig``: partial dicts merge
|
||||||
|
over uvicorn's default config, so only customizations are needed. The
|
||||||
|
server-side patches (error middleware, access log) are not installed —
|
||||||
|
there is no server here. The root logger level is INFO with *dev*
|
||||||
|
true, WARNING otherwise; *dev* of None follows ``env.dev``. An
|
||||||
|
explicit level in *log_config* always wins::
|
||||||
|
|
||||||
|
import fastapi_vue
|
||||||
|
fastapi_vue.setup_logging(log_config={"loggers": {"myapp": {"level": "DEBUG"}}})
|
||||||
|
"""
|
||||||
|
if log_config is not None and not isinstance(log_config, dict):
|
||||||
|
msg = f"setup_logging requires a dict log_config, got {type(log_config).__name__}"
|
||||||
|
raise TypeError(msg)
|
||||||
|
import logging.config
|
||||||
|
|
||||||
|
tracerite.load()
|
||||||
|
config = patch_log_config(log_config or {}, access_log=False, install=False, dev=dev)
|
||||||
|
# Standalone setup must not disable loggers created before this call.
|
||||||
|
config.setdefault("disable_existing_loggers", False)
|
||||||
|
logging.config.dictConfig(config)
|
||||||
|
|
||||||
|
|
||||||
|
def patch_log_config(log_config, *, access_log: bool = True, install: bool = True, dev: bool | None = None): # noqa: ANN001, ANN201
|
||||||
"""Patch a uvicorn log_config dict for our logging, best-effort.
|
"""Patch a uvicorn log_config dict for our logging, best-effort.
|
||||||
|
|
||||||
A dict without a ``version`` key is treated as a partial config: it is
|
A dict without a ``version`` key is treated as a partial config: it is
|
||||||
@@ -330,9 +360,11 @@ def patch_log_config(log_config, *, access_log: bool = True): # noqa: ANN001, A
|
|||||||
``watchfiles.main`` logger is lifted to WARNING so its INFO "N changes
|
``watchfiles.main`` logger is lifted to WARNING so its INFO "N changes
|
||||||
detected" line is dropped while the WARNING "Reloading..." line (logged
|
detected" line is dropped while the WARNING "Reloading..." line (logged
|
||||||
to ``uvicorn.error``) still shows; a user-supplied level wins.
|
to ``uvicorn.error``) still shows; a user-supplied level wins.
|
||||||
With ``access_log``, additionally rewires the ``access`` formatter to
|
With ``install=False`` (standalone use via ``setup_logging``) the
|
||||||
our Formatter and attaches its handler to our ``fastapi_vue.access``
|
NullHandler backdoor and the server-side patches in Formatter are
|
||||||
logger. We must not
|
skipped. With ``access_log``, additionally rewires the ``access``
|
||||||
|
formatter to our Formatter and attaches its handler to our
|
||||||
|
``fastapi_vue.access`` logger. We must not
|
||||||
attach handlers to ``uvicorn.access``: uvicorn gates its own
|
attach handlers to ``uvicorn.access``: uvicorn gates its own
|
||||||
protocol-level access logging on ``uvicorn.access.hasHandlers()``.
|
protocol-level access logging on ``uvicorn.access.hasHandlers()``.
|
||||||
"""
|
"""
|
||||||
@@ -342,12 +374,13 @@ def patch_log_config(log_config, *, access_log: bool = True): # noqa: ANN001, A
|
|||||||
if "version" not in config:
|
if "version" not in config:
|
||||||
config = _merge_log_config(deepcopy(LOGGING_CONFIG), config)
|
config = _merge_log_config(deepcopy(LOGGING_CONFIG), config)
|
||||||
|
|
||||||
with suppress(Exception):
|
if install:
|
||||||
config["formatters"]["fastapi_vue"] = {"()": "fastapi_vue.logging.Formatter"}
|
with suppress(Exception):
|
||||||
config["handlers"]["fastapi_vue"] = {
|
config["formatters"]["fastapi_vue"] = {"()": "fastapi_vue.logging.Formatter"}
|
||||||
"class": "logging.NullHandler",
|
config["handlers"]["fastapi_vue"] = {
|
||||||
"formatter": "fastapi_vue",
|
"class": "logging.NullHandler",
|
||||||
}
|
"formatter": "fastapi_vue",
|
||||||
|
}
|
||||||
|
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
filters = config.setdefault("filters", {})
|
filters = config.setdefault("filters", {})
|
||||||
@@ -367,6 +400,7 @@ def patch_log_config(log_config, *, access_log: bool = True): # noqa: ANN001, A
|
|||||||
"()": "fastapi_vue.logging.Formatter",
|
"()": "fastapi_vue.logging.Formatter",
|
||||||
"fmt": "%(message)s",
|
"fmt": "%(message)s",
|
||||||
"use_colors": None,
|
"use_colors": None,
|
||||||
|
"install": install,
|
||||||
}
|
}
|
||||||
|
|
||||||
# uvicorn's default config leaves the root logger handlerless, eating
|
# uvicorn's default config leaves the root logger handlerless, eating
|
||||||
@@ -375,7 +409,7 @@ def patch_log_config(log_config, *, access_log: bool = True): # noqa: ANN001, A
|
|||||||
# with Python's default; dev keeps INFO. Subloggers can override.
|
# with Python's default; dev keeps INFO. Subloggers can override.
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
root = config.setdefault("root", {})
|
root = config.setdefault("root", {})
|
||||||
root.setdefault("level", "INFO" if env.dev else "WARNING")
|
root.setdefault("level", "INFO" if (env.dev if dev is None else dev) else "WARNING")
|
||||||
if "default" in config.get("handlers", {}):
|
if "default" in config.get("handlers", {}):
|
||||||
root_handlers = root.setdefault("handlers", [])
|
root_handlers = root.setdefault("handlers", [])
|
||||||
if "default" not in root_handlers:
|
if "default" not in root_handlers:
|
||||||
|
|||||||
Reference in New Issue
Block a user