From 60674c86392ec82b09ebe8769b1a7ad3d0db4bd5 Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 30 Jun 2026 17:41:13 +0300 Subject: [PATCH] fix(logging): defensive trace_id seed + falsy error/payload in CotJsonFormatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit log_requests middleware (BaseHTTPMiddleware) showed no-trace because anyio.create_task_group() in Starlette 0.50.0 does not always propagate ContextVar from raw ASGI TraceContextMiddleware. Fix 1: defensive get_trace_id() check at log_requests entry — if empty, seed_trace_id() to ensure every request has one. Fix 2: CotJsonFormatter used 'if error:' and 'if payload:' which silently drop empty strings (str(e)='') and empty dicts. Changed to 'is not None' checks — preserves all data. Root cause: 12 EXPLORE-without-error entries from belief_scope's exception handler where Exception() has empty message. --- backend/src/app.py | 8 +++++++- backend/src/core/logger.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/src/app.py b/backend/src/app.py index 7ca86e74..14332aa6 100755 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -58,7 +58,7 @@ from .api.routes import ( ) from .api.routes.validation_tasks import router as validation_tasks from .core.auth.security import get_password_hash -from .core.cot_logger import seed_trace_id +from .core.cot_logger import get_trace_id, seed_trace_id from .core.database import AuthSessionLocal, init_db from .core.encryption_key import ensure_encryption_key from .core.logger import belief_scope, logger @@ -338,6 +338,12 @@ async def network_error_handler(request: Request, exc: NetworkError): # @POST Logs request and response details. @app.middleware("http") async def log_requests(request: Request, call_next): + # Defensive trace_id seeding: TraceContextMiddleware (raw ASGI) seeds + # trace_id in the root context, but BaseHTTPMiddleware may run in a + # child task that doesn't inherit the ContextVar (Starlette 0.50.0 + + # anyio.create_task_group). If empty, seed now. + if not get_trace_id(): + seed_trace_id() with belief_scope("log_requests"): # Avoid spamming logs for polling endpoints is_polling = request.url.path.endswith("/api/tasks") and request.method == "GET" diff --git a/backend/src/core/logger.py b/backend/src/core/logger.py index 71013ced..8984a4cd 100755 --- a/backend/src/core/logger.py +++ b/backend/src/core/logger.py @@ -103,9 +103,9 @@ class CotJsonFormatter(logging.Formatter): span_id = _span_id.get() if span_id: log_obj["span_id"] = span_id - if payload: + if payload is not None: log_obj["payload"] = payload - if error: + if error is not None: log_obj["error"] = error return json.dumps(log_obj, ensure_ascii=False, default=str)