fix(logging): ensure docker logs show bridge output

logging.basicConfig() ran after bridge_endpoints (via bridge_logging)
had already attached a handler to the root logger at import time,
making basicConfig() a no-op per Python's docs (it only configures
the root logger when it has none yet). Every log.info/warning call
after that point silently stopped reaching stdout/stderr - "docker
logs" only ever showed whatever fired before this point.

Moved basicConfig() to run before any extracted-module import, and
added PYTHONUNBUFFERED=1 so output isn't buffered away under a
non-TTY container stdout.
This commit is contained in:
2026-08-12 14:18:01 +02:00
parent 6ab5c31035
commit 714a81206b
2 changed files with 18 additions and 9 deletions

View File

@ -37,6 +37,9 @@ RUN mkdir -p /app/config && mkdir -p /app/data
# und Container-Erkennung für den Bridge-Restart (Supervisor startet neu statt subprocess).
ENV KX_DATA_DIR=/app/data
ENV KX_IN_DOCKER=1
# Ohne das puffert Python stdout/stderr komplett wenn kein TTY angehängt ist -
# "docker logs" bleibt dann leer bis der Prozess beendet wird.
ENV PYTHONUNBUFFERED=1
EXPOSE 7125

View File

@ -40,6 +40,21 @@ import sys
import time
import threading
# Must run before any extracted-module import below: bridge_endpoints pulls in
# bridge_logging, which attaches its browser-log handler to the root logger at
# import time. If that happens first, basicConfig() becomes a no-op (it only
# configures the root logger when it has no handlers yet) and every log.info/
# warning call silently stops reaching stdout/stderr - "docker logs" then only
# ever shows whatever fired before this point (e.g. an early config warning).
logging.basicConfig(level=logging.INFO,
format="[%(asctime)s] %(levelname)-5s %(name)s: %(message)s",
datefmt="%H:%M:%S")
log = logging.getLogger("bridge")
# aiohttp logs one INFO line per HTTP request (access log) — with 2s frontend
# polling that drowns out the bridge's own logs by default. Toggleable at
# runtime via the verbose_http_log setting (see handle_api_settings_post).
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
# For PyInstaller binaries everything sits next to sys.executable, otherwise next to __file__
_BASE = os.path.dirname(sys.executable) if getattr(sys, "frozen", False) else os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _BASE)
@ -74,15 +89,6 @@ except ImportError:
print("Error: aiohttp is not installed. Run: pip install aiohttp")
sys.exit(1)
logging.basicConfig(level=logging.INFO,
format="[%(asctime)s] %(levelname)-5s %(name)s: %(message)s",
datefmt="%H:%M:%S")
log = logging.getLogger("bridge")
# aiohttp logs one INFO line per HTTP request (access log) — with 2s frontend
# polling that drowns out the bridge's own logs by default. Toggleable at
# runtime via the verbose_http_log setting (see handle_api_settings_post).
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
# UI theme-name validation (used in __init__); the /kx/ui asset-serving
# constants live in bridge_endpoints alongside their only consumers.