Backend logging: stderr + configurable rotating file
Add configure_logging() (main.py): always logs to stderr, plus a rotating file when PIRATS_LOG_FILE is set; level from PIRATS_LOG_LEVEL (default INFO). Called from both the CLI entrypoint and the app lifespan (idempotent), which also logs startup/migration/shutdown. Targeted server-side events for ops/debugging (not every game event): game created, player joined, player removed (kick/leave), game ended. NixOS service: new logFile (/var/log/pirats/pirats.log) and logLevel options, wired to the env vars; LogsDirectory=pirats makes the path writable under the sandboxed DynamicUser. README documents the env vars/options. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,9 @@ from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlmodel import Session
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import re
|
||||
import sys
|
||||
import uvicorn
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -21,10 +23,58 @@ EVENT_PAGE_SIZE = 50
|
||||
|
||||
BASE_DIR = Path(__file__).parent
|
||||
|
||||
_logging_configured = False
|
||||
|
||||
def configure_logging() -> None:
|
||||
"""Set up backend logging: always to stderr, plus a rotating file when
|
||||
PIRATS_LOG_FILE is set (the NixOS service points it at /var/log/pirats/).
|
||||
Level comes from PIRATS_LOG_LEVEL (default INFO). Idempotent so it's safe to
|
||||
call from both the CLI entrypoint and the app's startup lifespan."""
|
||||
global _logging_configured
|
||||
if _logging_configured:
|
||||
return
|
||||
_logging_configured = True
|
||||
|
||||
level_name = os.environ.get("PIRATS_LOG_LEVEL", "INFO").upper()
|
||||
level = getattr(logging, level_name, logging.INFO)
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(level)
|
||||
fmt = logging.Formatter(
|
||||
"%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
stderr_handler = logging.StreamHandler(sys.stderr)
|
||||
stderr_handler.setFormatter(fmt)
|
||||
root.addHandler(stderr_handler)
|
||||
|
||||
log_file = os.environ.get("PIRATS_LOG_FILE")
|
||||
if log_file:
|
||||
try:
|
||||
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
||||
)
|
||||
file_handler.setFormatter(fmt)
|
||||
root.addHandler(file_handler)
|
||||
logger.info("Logging to %s", log_file)
|
||||
except OSError:
|
||||
# A bad/unwritable path mustn't take the server down — keep stderr.
|
||||
logger.exception("Could not open log file %s; logging to stderr only", log_file)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
configure_logging()
|
||||
logger.info(
|
||||
"Starting Rats with Gats backend (db=%s, dev_mode_default=%s)",
|
||||
engine.url.render_as_string(hide_password=True),
|
||||
crud.dev_mode_default(),
|
||||
)
|
||||
run_migrations()
|
||||
logger.info("Database migrations applied")
|
||||
yield
|
||||
logger.info("Shutting down")
|
||||
|
||||
app = FastAPI(title="Rats with Gats Remote Play API", lifespan=lifespan)
|
||||
api = APIRouter()
|
||||
@@ -189,6 +239,7 @@ if os.path.exists(static_dir):
|
||||
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
|
||||
|
||||
def main():
|
||||
configure_logging()
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Run the Rats with Gats web app")
|
||||
parser.add_argument("--host", default="0.0.0.0", help="Host address to bind to")
|
||||
|
||||
Reference in New Issue
Block a user