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
|
master.xprv.enc
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
*.egg-info/
|
*.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 asyncio
|
||||||
|
import logging
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
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.bets.confirmation # noqa: F401 (registers the "bet" confirmation handler)
|
||||||
import app.rounds.confirmation # noqa: F401 (registers the "payout" confirmation handler)
|
import app.rounds.confirmation # noqa: F401 (registers the "payout" confirmation handler)
|
||||||
import app.withdrawals.confirmation # noqa: F401 (registers the "withdrawal" 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.confirmation import ConfirmationPoller
|
||||||
from app.tx.locks import UserLocks
|
from app.tx.locks import UserLocks
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _make_electrum_client() -> ElectrumClient:
|
def _make_electrum_client() -> ElectrumClient:
|
||||||
return ElectrumClient(settings.electrum_host, settings.electrum_port, settings.electrum_use_ssl)
|
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.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")
|
@app.get("/health")
|
||||||
async def health() -> dict[str, str]:
|
async def health() -> dict[str, str]:
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|||||||
Reference in New Issue
Block a user