mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-05 02:45:28 +00:00
fix(logging): preserve level across uvicorn startup
This commit is contained in:
parent
71ea62f934
commit
1a5df9edc0
5 changed files with 106 additions and 6 deletions
9
app.py
9
app.py
|
|
@ -88,6 +88,7 @@ from core.log_safety import (
|
|||
ScopedDiagnosticsFilter,
|
||||
application_log_settings,
|
||||
configure_uvicorn_log_levels,
|
||||
uvicorn_log_config,
|
||||
)
|
||||
|
||||
_root_logger = logging.getLogger()
|
||||
|
|
@ -1298,4 +1299,10 @@ if __name__ == "__main__":
|
|||
bind_host = os.getenv("APP_BIND", "127.0.0.1")
|
||||
bind_port = int(os.getenv("APP_PORT", "7000"))
|
||||
|
||||
uvicorn.run(app, host=bind_host, port=bind_port, log_level=_application_log_level)
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=bind_host,
|
||||
port=bind_port,
|
||||
log_level=_application_log_level,
|
||||
log_config=uvicorn_log_config(_application_log_level),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,13 +9,18 @@ also doubles as a sanitizer barrier for CodeQL's clear-text-logging query.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
import logging
|
||||
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
|
||||
CAPABILITY_DIAGNOSTICS_LOGGER = "src.model_capability_readers"
|
||||
UVICORN_LOGGER_NAMES = ("uvicorn", "uvicorn.error", "uvicorn.access")
|
||||
UVICORN_LOGGER_NAMES = (
|
||||
"uvicorn",
|
||||
"uvicorn.error",
|
||||
"uvicorn.access",
|
||||
"uvicorn.asgi",
|
||||
)
|
||||
|
||||
_LOG_LEVELS = {
|
||||
"DEBUG": logging.DEBUG,
|
||||
|
|
@ -53,6 +58,18 @@ def configure_uvicorn_log_levels(application_level: int) -> None:
|
|||
logging.getLogger(logger_name).setLevel(application_level)
|
||||
|
||||
|
||||
def uvicorn_log_config(application_level: int) -> dict:
|
||||
"""Return a Uvicorn config that preserves the mapped level on direct runs."""
|
||||
|
||||
from uvicorn.config import LOGGING_CONFIG
|
||||
|
||||
config = deepcopy(LOGGING_CONFIG)
|
||||
loggers = config.setdefault("loggers", {})
|
||||
for logger_name in UVICORN_LOGGER_NAMES:
|
||||
loggers.setdefault(logger_name, {})["level"] = application_level
|
||||
return config
|
||||
|
||||
|
||||
class ScopedDiagnosticsFilter(logging.Filter):
|
||||
"""Allow normal application records plus one explicitly scoped DEBUG log."""
|
||||
|
||||
|
|
|
|||
12
launcher.py
12
launcher.py
|
|
@ -128,9 +128,13 @@ if __name__ == "__main__":
|
|||
import uvicorn
|
||||
# Import the FastAPI app from app.py
|
||||
from app import app
|
||||
from core.log_safety import application_log_settings, uvicorn_log_config
|
||||
|
||||
bind_host = os.getenv("APP_BIND", "127.0.0.1")
|
||||
bind_port = int(os.getenv("APP_PORT", "7000"))
|
||||
application_log_level, _ = application_log_settings(
|
||||
os.getenv("LOG_LEVEL", "INFO")
|
||||
)
|
||||
url = f"http://{bind_host}:{bind_port}"
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
|
|
@ -139,4 +143,10 @@ if __name__ == "__main__":
|
|||
# Start system tray manager thread
|
||||
threading.Thread(target=setup_system_tray, args=(url,), daemon=True).start()
|
||||
|
||||
uvicorn.run(app, host=bind_host, port=bind_port, log_level="info")
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=bind_host,
|
||||
port=bind_port,
|
||||
log_level=application_log_level,
|
||||
log_config=uvicorn_log_config(application_log_level),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from core.log_safety import (
|
||||
CAPABILITY_DIAGNOSTICS_LOGGER,
|
||||
ScopedDiagnosticsFilter,
|
||||
UVICORN_LOGGER_NAMES,
|
||||
application_log_settings,
|
||||
configure_uvicorn_log_levels,
|
||||
redact_url,
|
||||
uvicorn_log_config,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -67,7 +72,7 @@ def test_application_log_settings_scope_debug_and_fail_closed(
|
|||
|
||||
|
||||
def test_configure_uvicorn_log_levels_clamps_non_propagating_loggers():
|
||||
logger_names = ("uvicorn", "uvicorn.error", "uvicorn.access")
|
||||
logger_names = UVICORN_LOGGER_NAMES
|
||||
previous_levels = {
|
||||
name: logging.getLogger(name).level for name in logger_names
|
||||
}
|
||||
|
|
@ -85,6 +90,61 @@ def test_configure_uvicorn_log_levels_clamps_non_propagating_loggers():
|
|||
logging.getLogger(name).setLevel(level)
|
||||
|
||||
|
||||
def test_uvicorn_log_config_sets_all_named_loggers_without_mutating_default():
|
||||
from uvicorn.config import LOGGING_CONFIG
|
||||
|
||||
configured = uvicorn_log_config(logging.ERROR)
|
||||
|
||||
assert all(
|
||||
configured["loggers"][name]["level"] == logging.ERROR
|
||||
for name in UVICORN_LOGGER_NAMES
|
||||
)
|
||||
assert LOGGING_CONFIG["loggers"]["uvicorn"]["level"] == "INFO"
|
||||
|
||||
|
||||
def test_uvicorn_levels_hold_across_external_and_direct_config_order():
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
dedent(
|
||||
"""
|
||||
import logging
|
||||
import uvicorn
|
||||
|
||||
from core.log_safety import (
|
||||
UVICORN_LOGGER_NAMES,
|
||||
configure_uvicorn_log_levels,
|
||||
uvicorn_log_config,
|
||||
)
|
||||
|
||||
uvicorn.Config("app:app", log_level="debug")
|
||||
configure_uvicorn_log_levels(logging.INFO)
|
||||
assert all(
|
||||
logging.getLogger(name).level == logging.INFO
|
||||
for name in UVICORN_LOGGER_NAMES
|
||||
)
|
||||
|
||||
uvicorn.Config(
|
||||
"app:app",
|
||||
log_level=logging.ERROR,
|
||||
log_config=uvicorn_log_config(logging.ERROR),
|
||||
)
|
||||
assert all(
|
||||
logging.getLogger(name).level == logging.ERROR
|
||||
for name in UVICORN_LOGGER_NAMES
|
||||
)
|
||||
"""
|
||||
),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def _record(name: str, level: int) -> logging.LogRecord:
|
||||
return logging.LogRecord(name, level, __file__, 1, "message", (), None)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ def test_fallback_debug_log_is_explicit_and_has_no_capability_claims(caplog):
|
|||
|
||||
|
||||
def test_web_app_logging_uses_existing_log_level_environment_toggle():
|
||||
source = (Path(__file__).resolve().parents[1] / "app.py").read_text(encoding="utf-8")
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
source = (root / "app.py").read_text(encoding="utf-8")
|
||||
launcher_source = (root / "launcher.py").read_text(encoding="utf-8")
|
||||
|
||||
assert 'os.getenv("LOG_LEVEL", "INFO")' in source
|
||||
assert "application_log_settings(_log_level_name)" in source
|
||||
|
|
@ -66,3 +68,7 @@ def test_web_app_logging_uses_existing_log_level_environment_toggle():
|
|||
assert "_console_h.addFilter(_diagnostics_filter)" in source
|
||||
assert "_file_h.addFilter(_diagnostics_filter)" in source
|
||||
assert "log_level=_application_log_level" in source
|
||||
assert "log_config=uvicorn_log_config(_application_log_level)" in source
|
||||
assert "application_log_settings(" in launcher_source
|
||||
assert "log_level=application_log_level" in launcher_source
|
||||
assert "log_config=uvicorn_log_config(application_log_level)" in launcher_source
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue