Log to a file instead of only stdout
All app and uvicorn logging now goes to logs/app.log (rotating, 10MB x5), and a catch-all exception handler logs full tracebacks there before returning a generic 500 — so an error is traceable to its cause without depending on how the process was launched. logs/ is gitignored, like the other runtime artifacts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,3 +6,4 @@ __pycache__/
|
||||
master.xprv.enc
|
||||
.pytest_cache/
|
||||
*.egg-info/
|
||||
logs/
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
LOG_DIR = Path(__file__).resolve().parent.parent / "logs"
|
||||
LOG_FILE = LOG_DIR / "app.log"
|
||||
|
||||
_FORMAT = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
|
||||
|
||||
|
||||
def setup_logging() -> None:
|
||||
"""Route all app + uvicorn logging to logs/app.log so errors are traceable
|
||||
without depending on however the process happens to be launched."""
|
||||
LOG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
handler = RotatingFileHandler(LOG_FILE, maxBytes=10 * 1024 * 1024, backupCount=5)
|
||||
handler.setFormatter(logging.Formatter(_FORMAT))
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(logging.INFO)
|
||||
root.addHandler(handler)
|
||||
|
||||
# "uvicorn.error"/"uvicorn.access" have propagate=False in uvicorn's default
|
||||
# logging config, so they never reach the root handler above and need their
|
||||
# own. The parent "uvicorn" logger is deliberately skipped: uvicorn.error
|
||||
# already bubbles into it, so attaching there too would double every line.
|
||||
for logger_name in ("uvicorn.error", "uvicorn.access"):
|
||||
logging.getLogger(logger_name).addHandler(handler)
|
||||
+15
-2
@@ -1,10 +1,15 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.logging_config import setup_logging
|
||||
|
||||
setup_logging()
|
||||
|
||||
import app.bets.confirmation # noqa: F401 (registers the "bet" confirmation handler)
|
||||
import app.rounds.confirmation # noqa: F401 (registers the "payout" confirmation handler)
|
||||
import app.withdrawals.confirmation # noqa: F401 (registers the "withdrawal" confirmation handler)
|
||||
@@ -22,6 +27,8 @@ from app.tx.broadcast import RbfBumper
|
||||
from app.tx.confirmation import ConfirmationPoller
|
||||
from app.tx.locks import UserLocks
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _make_electrum_client() -> ElectrumClient:
|
||||
return ElectrumClient(settings.electrum_host, settings.electrum_port, settings.electrum_use_ssl)
|
||||
@@ -60,6 +67,12 @@ app.include_router(withdrawals_router)
|
||||
app.include_router(admin_router)
|
||||
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def log_unhandled_exception(request: Request, exc: Exception) -> JSONResponse:
|
||||
logger.exception("Unhandled error on %s %s", request.method, request.url.path)
|
||||
return JSONResponse(status_code=500, content={"detail": "internal server error"})
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user