fix
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user