fix: logging verbosity now actually applies + per-service log levels
Unit Tests / test (push) Successful in 12m34s
Unit Tests / test (push) Successful in 12m34s
Root causes fixed:
- Dead LOG_LEVEL globals() lookup pinned root logger at INFO regardless of
PIC_LOG_LEVEL env or config; replaced with _resolve_root_log_level() +
apply_root_log_level() which sets both root logger and all attached handlers
at startup and on runtime re-apply.
- set_service_level() only set the named 'pic.<service>' logger; bare module
loggers (e.g. 'caddy_manager') were never reached, so per-service log files
stayed 0 bytes. Fixed via _SERVICE_MODULE_LOGGERS map covering all managers.
- Log viewer GET /api/logs had no level filter; added ?level= query param.
- Per-service log levels lived in an out-of-band config/api/log_levels.json
side-file with no validation; migrated into ConfigManager under a new
'logging' section ({python:{root,services}, containers:{caddy,coredns,
wireguard,mailserver,api}}) with get/set helpers, invalid-level rejection,
and one-time migration from the old file on first load.
New capabilities:
- Container log levels: Caddy (injects global log { level X } + hot reload),
CoreDNS (DEBUG enables log plugin, else errors-only), WireGuard/mailserver
via pending_restart path.
- PUT /api/logs/verbosity accepts {python, containers} dict; returns per-entry
applied:hot|pending_restart status.
- Webui Logs page gains two-section Verbosity tab (Python services + Container
services) with needs-restart badges.
- managers.py wires per-service loggers before manager instantiation and
re-applies persisted levels from ConfigManager; legacy log_levels.json read
removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+31
-2
@@ -111,6 +111,30 @@ class CaddyManager(BaseServiceManager):
|
||||
|
||||
# ── Caddyfile generation ──────────────────────────────────────────────
|
||||
|
||||
# Python logging level → Caddy log level. Caddy only knows
|
||||
# DEBUG/INFO/WARN/ERROR (no CRITICAL).
|
||||
_CADDY_LEVEL_MAP = {
|
||||
'DEBUG': 'DEBUG', 'INFO': 'INFO', 'WARNING': 'WARN',
|
||||
'ERROR': 'ERROR', 'CRITICAL': 'ERROR',
|
||||
}
|
||||
|
||||
def _resolve_caddy_level(self) -> str:
|
||||
"""Read the configured caddy container log level (Python level name)."""
|
||||
if self.config_manager is not None:
|
||||
try:
|
||||
return self.config_manager.get_logging_config()['containers'].get('caddy', 'INFO')
|
||||
except Exception:
|
||||
pass
|
||||
return 'INFO'
|
||||
|
||||
def _global_log_block(self) -> str:
|
||||
"""Return the global-options `log { level <X> }` line(s), or '' for the
|
||||
Caddy default (INFO). Injected inside the global `{ ... }` block."""
|
||||
level = self._CADDY_LEVEL_MAP.get(self._resolve_caddy_level(), 'INFO')
|
||||
if level == 'INFO':
|
||||
return ''
|
||||
return f" log {{\n level {level}\n }}"
|
||||
|
||||
def generate_caddyfile(self, identity: Dict[str, Any],
|
||||
installed_services: List[Dict[str, Any]]) -> str:
|
||||
"""Generate a complete Caddyfile based on identity and services.
|
||||
@@ -172,13 +196,15 @@ class CaddyManager(BaseServiceManager):
|
||||
|
||||
# ── per-mode generators ───────────────────────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _global_acme_block(email: Optional[str]) -> str:
|
||||
def _global_acme_block(self, email: Optional[str]) -> str:
|
||||
"""Return the ``{ ... }`` global block for an ACME-enabled mode."""
|
||||
lines = ["{"]
|
||||
# Bind admin API on all interfaces so cell-api can reach cell-caddy
|
||||
# across the Docker bridge (default 127.0.0.1 is unreachable cross-container).
|
||||
lines.append(" admin 0.0.0.0:2019")
|
||||
log_block = self._global_log_block()
|
||||
if log_block:
|
||||
lines.append(log_block)
|
||||
if email:
|
||||
lines.append(f" email {email}")
|
||||
# Only write acme_ca when a URL is configured — an empty ACME_CA_URL
|
||||
@@ -290,9 +316,12 @@ class CaddyManager(BaseServiceManager):
|
||||
body.append(self._indent_routes(service_routes))
|
||||
body.append(core_routes)
|
||||
inner = "\n".join(body)
|
||||
log_block = self._global_log_block()
|
||||
log_line = (log_block + "\n") if log_block else ""
|
||||
return (
|
||||
"{\n"
|
||||
" admin 0.0.0.0:2019\n"
|
||||
f"{log_line}"
|
||||
" auto_https off\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
|
||||
Reference in New Issue
Block a user