fix: logging overhaul — Alembic fileConfig disable_existing_loggers=False, propagate=False on loggers, ADR docs, catch-up migration for missing columns, sqlparse dep, build archive naming
This commit is contained in:
@@ -6,9 +6,20 @@
|
||||
# @RELATION DEPENDS_ON -> [WebSocketLogHandler]
|
||||
# @PRE Python 3.7+ with cot_logger ContextVars available.
|
||||
# @POST All log output is single-line JSON with ts, level, trace_id, src, marker, intent, payload, error, span_id.
|
||||
# @SIDE_EFFECT Configures global logger handlers and formatters; seeds global _enable_belief_state and _task_log_level; writes JSON to console/files.
|
||||
# @RATIONALE Replaced BeliefFormatter text-based [Entry]/[Exit]/[Action] logging with CotJsonFormatter JSON output. Old format was not machine-parseable and mixed presentation with intent. CotJsonFormatter produces structured JSON consistent with cot_logger.py MarkerLogger protocol.
|
||||
# @REJECTED Keeping both formatters side-by-side was rejected (two inconsistent output formats would confuse log consumers).
|
||||
# Both 'superset_tools_app' and 'cot' loggers have propagate=False — no text duplicates via root logger.
|
||||
# @INVARIANT Every logger.info() call MUST produce exactly one JSON line (see [ADR LOG-002] for why).
|
||||
# @SIDE_EFFECT Configures global logger handlers and formatters; seeds global _enable_belief_state
|
||||
# and _task_log_level; sets propagate=False on both loggers; writes JSON to console/files.
|
||||
# @RELATION DISABLED_BY -> [LOG-001:alembic/env.py:fileConfig]
|
||||
# If alembic/env.py calls fileConfig() without disable_existing_loggers=False, Python 3.13's
|
||||
# logging framework sets logger.disabled = True on 'superset_tools_app', silencing ALL output.
|
||||
# @RATIONALE Replaced BeliefFormatter text-based [Entry]/[Exit]/[Action] logging with CotJsonFormatter
|
||||
# JSON output. Old format was not machine-parseable and mixed presentation with intent. CotJsonFormatter
|
||||
# produces structured JSON consistent with cot_logger.py MarkerLogger protocol.
|
||||
# @REJECTED Keeping both formatters side-by-side was rejected (two inconsistent output formats would
|
||||
# confuse log consumers).
|
||||
# @REJECTED Keeping propagate=True was rejected after uvicorn's dictConfig added a root logger handler
|
||||
# that duplicates every log as plain text (see [ADR LOG-002]).
|
||||
# @DATA_CONTRACT All log records conform to molecular CoT JSON schema: {ts, level, trace_id, src, marker, intent, span_id?, payload?, error?}
|
||||
# @INVARIANT CotJsonFormatter.format() always returns valid single-line JSON string.
|
||||
from contextlib import contextmanager
|
||||
@@ -162,8 +173,12 @@ def belief_scope(anchor_id: str, message: str = ""):
|
||||
# @RELATION CALLS -> [CotJsonFormatter]
|
||||
# @RELATION CALLS -> [CotLoggerModule]
|
||||
# @PRE config is a valid LoggingConfig instance.
|
||||
# @POST Logger levels, handlers, formatters, belief state flag, and task log level are updated.
|
||||
# @SIDE_EFFECT Mutates global _enable_belief_state, _task_log_level; adds/removes handlers on both logger and cot_logger; creates file directories.
|
||||
# @POST Logger levels, handlers, formatters, belief state flag, task log level, and
|
||||
# propagate flags (False) are updated. Both loggers emit JSON only — no text duplicates.
|
||||
# @SIDE_EFFECT Mutates global _enable_belief_state, _task_log_level; adds/removes handlers
|
||||
# on both logger and cot_logger; sets propagate=False on both; creates file directories.
|
||||
# @DATA_CONTRACT configure_logger() -> ENABLES both loggers. Every call to logger.info()
|
||||
# MUST produce exactly one JSON line (not zero, not two). See [ADR LOG-001] and [ADR LOG-002].
|
||||
def configure_logger(config):
|
||||
global _enable_belief_state, _task_log_level
|
||||
_enable_belief_state = config.enable_belief_state
|
||||
@@ -206,9 +221,21 @@ def configure_logger(config):
|
||||
cot_handler.setFormatter(CotJsonFormatter())
|
||||
cot_logger.addHandler(cot_handler)
|
||||
|
||||
# Disable propagation to prevent duplicate output via uvicorn's root logger handlers.
|
||||
# Both loggers have their own dedicated handlers; propagation would cause each message
|
||||
# to appear twice: once via CotJsonFormatter (JSON) and once via uvicorn's DefaultFormatter (text).
|
||||
# @ADR [LOG-002] Disable logger propagation to prevent duplicate output
|
||||
# @RATIONALE Uvicorn's configure_logging() registers a StreamHandler on the root
|
||||
# logger via logging.config.dictConfig(). Both 'superset_tools_app' and 'cot'
|
||||
# loggers have their own dedicated handlers with CotJsonFormatter. With
|
||||
# propagate=True (default), each log message is handled twice:
|
||||
# 1. Our CotJsonFormatter → structured JSON (desired)
|
||||
# 2. Propagated to root → uvicorn's DefaultFormatter → plain text (duplicate)
|
||||
# Setting propagate=False prevents the second emission.
|
||||
# @REJECTED Removing uvicorn's root logger handler via logging.getLogger().handlers = []
|
||||
# was rejected because it couples SS-TOOLS to uvicorn's internal logging setup,
|
||||
# which could change between uvicorn versions. Setting propagate on our own
|
||||
# loggers is self-contained and version-safe.
|
||||
# @REJECTED Overriding log level on root logger was rejected (root.setLevel(WARNING))
|
||||
# because it would suppress legitimate warnings from third-party libraries
|
||||
# (SQLAlchemy, Alembic, httpx, etc.).
|
||||
logger.propagate = False
|
||||
cot_logger.propagate = False
|
||||
# #endregion configure_logger
|
||||
|
||||
Reference in New Issue
Block a user