- Dockerfile: python:3.14-slim, the Python CI tests on (was 3.13). tini from apt is PID 1 (ENTRYPOINT tini -- entrypoint.sh): it reaps the zombies ssh and ansible leave and forwards SIGTERM. - cli: Hypercorn's serve() with one worker (run, stream and scheduler state is in-process), a 10 s graceful timeout, and SIGTERM/SIGINT as the shutdown trigger. Access and error lines go through Steward's logging. --debug keeps Quart's reloading dev server. - Logging: steward/core/logging_setup.py installs one root handler before anything logs (until now a library's basicConfig on import was the only configuration). Its level is the `log.level` setting (DEBUG/INFO/WARNING/ ERROR, default INFO), applied at boot and on save in Settings → General. - CI publish: build, then "The image boots with no internet" (rule 164, snippet #3552), then push, so a broken image never reaches a tag (rule 177). On an --internal network with its own Postgres, the image must migrate and answer /health, run tini as PID 1, serve first-run setup with its assets from /static and no third-party host, and exit 0 on SIGTERM. Logs and clean-up run always. Tests: tests/core/test_logging_setup.py; the smoke step itself. Not in this commit: the dependency lockfile (it conflicts with rule 154 and is waiting on the operator). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.0 KiB
Python
58 lines
2.0 KiB
Python
"""Logging and the server (#5159): one handler, the level a setting, and
|
|
Hypercorn's lines going through the same logging."""
|
|
import logging
|
|
|
|
import pytest
|
|
|
|
from steward.core import logging_setup
|
|
from steward.core.settings import DEFAULTS
|
|
|
|
|
|
@pytest.fixture
|
|
def root():
|
|
root = logging.getLogger()
|
|
handlers, level = list(root.handlers), root.level
|
|
yield root
|
|
root.handlers[:] = handlers
|
|
root.setLevel(level)
|
|
|
|
|
|
def test_configuring_twice_installs_one_handler(root):
|
|
logging_setup.configure_logging("INFO")
|
|
logging_setup.configure_logging("DEBUG")
|
|
ours = [h for h in root.handlers if getattr(h, "_steward", False)]
|
|
assert len(ours) == 1
|
|
assert ours[0].formatter._fmt == logging_setup.FORMAT
|
|
assert root.level == logging.DEBUG
|
|
|
|
|
|
@pytest.mark.parametrize("given, applied", [
|
|
("warning", "WARNING"), (" Error ", "ERROR"), ("DEBUG", "DEBUG"),
|
|
("loud", "INFO"), ("", "INFO"), (None, "INFO"),
|
|
])
|
|
def test_the_level_setting_is_applied_and_an_unknown_one_is_info(root, given, applied):
|
|
assert logging_setup.set_log_level(given) == applied
|
|
assert root.level == getattr(logging, applied)
|
|
|
|
|
|
def test_a_library_basic_config_after_ours_changes_nothing(root):
|
|
logging_setup.configure_logging("WARNING")
|
|
before = list(root.handlers)
|
|
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
|
|
assert root.handlers == before and root.level == logging.WARNING
|
|
|
|
|
|
def test_the_level_is_a_setting_defaulting_to_info():
|
|
assert DEFAULTS["log.level"] == "INFO"
|
|
assert DEFAULTS["log.level"] in logging_setup.LEVELS
|
|
|
|
|
|
def test_the_server_is_one_hypercorn_worker_logging_through_steward():
|
|
from steward.cli import GRACEFUL_TIMEOUT_SECONDS, hypercorn_config
|
|
config = hypercorn_config("0.0.0.0", 5000)
|
|
assert config.bind == ["0.0.0.0:5000"]
|
|
assert config.workers == 1 # run, stream and scheduler state is in-process
|
|
assert config.graceful_timeout == GRACEFUL_TIMEOUT_SECONDS
|
|
assert config.accesslog is logging.getLogger("hypercorn.access")
|
|
assert config.errorlog is logging.getLogger("hypercorn.error")
|