From a69189c85b31393b6f82ddf9fad86849151799bc Mon Sep 17 00:00:00 2001 From: Mikan <72257910+Mikan-DS@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:48:07 +0300 Subject: [PATCH] fix --- backend/Dockerfile | 8 ++++-- backend/app/logging_setup.py | 47 +++++++++++++++++++++++++++++++- backend/app/main.py | 10 +++++-- backend/app/prompts/templates.py | 6 ++-- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 9a8e8dd..719eac6 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,5 +19,9 @@ COPY . . EXPOSE 8000 -# Default: run uvicorn with hot reload for dev -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] +# Default: run uvicorn with hot reload for dev. +# We pass --log-level explicitly from $LOG_LEVEL so uvicorn's own loggers +# (uvicorn, uvicorn.access) start at the right level from the very first +# request — without this, they stay at INFO until our lifespan runs. +# Shell form (not exec form) so ${LOG_LEVEL} is interpolated by the shell. +CMD sh -c 'exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --log-level "${LOG_LEVEL:-info}"' diff --git a/backend/app/logging_setup.py b/backend/app/logging_setup.py index 78ad0e1..a5168b7 100644 --- a/backend/app/logging_setup.py +++ b/backend/app/logging_setup.py @@ -1,4 +1,18 @@ -"""Structured logging setup.""" +"""Structured logging setup. + +`LOG_LEVEL` (env) controls the verbosity of: + - The root Python logger + - structlog-bound loggers (app.*) + - Uvicorn's own loggers (`uvicorn`, `uvicorn.access`, `uvicorn.error`, + `uvicorn.asgi`) — these otherwise stay at INFO regardless of LOG_LEVEL + because uvicorn configures them itself at startup, before our lifespan + calls setup_logging(). We forcibly re-level them here. + - The `sqlalchemy.engine` logger (kept at WARNING unless LOG_LEVEL=DEBUG). + +Note: `logging.basicConfig()` is a no-op once the root logger has been +configured (which uvicorn does at import time), so it alone is NOT enough +to honor LOG_LEVEL — we must also call `setLevel()` on each named logger. +""" from __future__ import annotations import logging @@ -9,15 +23,46 @@ import structlog from app.config import settings +# Loggers whose level must be forced to LOG_LEVEL (uvicorn pre-configures them +# at INFO before our lifespan runs, so basicConfig cannot change them). +_FORCED_LOGGERS = ( + "uvicorn", + "uvicorn.access", + "uvicorn.error", + "uvicorn.asgi", + "fastapi", +) + + def setup_logging() -> None: level = getattr(logging, settings.log_level.upper(), logging.INFO) + # Force the root logger level (affects any logger that doesn't override). + logging.getLogger().setLevel(level) + + # Also call basicConfig for the formatter (idempotent if already set up). logging.basicConfig( format="%(message)s", stream=sys.stdout, level=level, + force=True, # python 3.8+: re-init even if already configured ) + # Force level on loggers that uvicorn pre-configured. + for name in _FORCED_LOGGERS: + lg = logging.getLogger(name) + lg.setLevel(level) + # Ensure uvicorn access logs propagate to the root handler. + lg.propagate = True + for h in lg.handlers: + h.setLevel(level) + + # SQLAlchemy is chatty at INFO; keep it at WARNING unless explicitly DEBUG. + sa_level = logging.DEBUG if level <= logging.DEBUG else logging.WARNING + logging.getLogger("sqlalchemy.engine").setLevel(sa_level) + logging.getLogger("sqlalchemy.pool").setLevel(sa_level) + logging.getLogger("asyncpg").setLevel(sa_level) + structlog.configure( processors=[ structlog.contextvars.merge_contextvars, diff --git a/backend/app/main.py b/backend/app/main.py index d424591..5556fb1 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -11,12 +11,18 @@ from app.api import admin, auth, misc, presets, sessions, worlds from app.config import settings from app.logging_setup import get_logger, setup_logging +# Configure logging as early as possible — at import time, before uvicorn +# finishes its own logger setup. This ensures LOG_LEVEL is honored for the +# very first request and for startup messages from submodules. +setup_logging() +log = get_logger("app") + @asynccontextmanager async def lifespan(app: FastAPI): + # Re-apply logging config in case any submodule reset it during import. setup_logging() - log = get_logger("app") - log.info("app_starting", worker_mode=settings.is_worker) + log.info("app_starting", worker_mode=settings.is_worker, log_level=settings.log_level) # Initialize DB tables and seed defaults from app.migrations.init_db import init_db diff --git a/backend/app/prompts/templates.py b/backend/app/prompts/templates.py index 3b49904..3e94e48 100644 --- a/backend/app/prompts/templates.py +++ b/backend/app/prompts/templates.py @@ -28,10 +28,10 @@ When calling `submit_world_definition`: - `setting_description`: 1-2 paragraph expanded setting. - `rules`: object with keys like `stats`, `combat`, `magic`, `time`, `inventory`, `death` (whichever apply). - `world_schema`: a JSON Schema describing the shape of the world's state (player, npcs, locations, world_time, flags, etc.). -- `plot_rails`: `{main_goal, subgoals, hooks}`. +- `plot_rails`: an object with keys `main_goal` (string), `subgoals` (array of strings), and `hooks` (array of strings). - `initial_state`: the initial world state matching `world_schema`. - `initial_time`: world-time string in the form `day_N_hour_H` (e.g. `day_1_hour_8`). -- `calendar`: optional. `{hours_per_day: 24, minutes_per_hour: 60, days_per_week: 7}`. Include only if the world uses a non-standard calendar (e.g. 28-hour days). +- `calendar`: optional. An object with `hours_per_day` (default 24), `minutes_per_hour` (default 60), `days_per_week` (default 7). Include only if the world uses a non-standard calendar (e.g. 28-hour days). - `is_final`: set to `true` ONLY when the player has explicitly accepted the world. CRITICAL: @@ -100,7 +100,7 @@ SUMMARIZER_SYSTEM = """You compress the history of a role-playing session. Given Call the `submit_summary` tool with: - `summary`: 3-6 sentences of key events and state changes (max 150 words). -- `facts`: array of important persistent facts `[{kind, name, description}]` where kind is one of npc, location, item, lore, event. +- `facts`: array of important persistent facts. Each fact is an object with keys `kind`, `name`, `description`, where `kind` is one of `npc`, `location`, `item`, `lore`, `event`. CRITICAL: Preserve names, numbers, and important state changes. Your text response is ignored — only the `submit_summary` tool call is used. """