feat: configure full logging in webmain.py — all API logs to file + console
- RotatingFileHandler: logs/server_YYYY-MM-DD.log (50MB, keep 7 files) - Console handler: colored timestamp + level + logger name + message - webapp.* and rag_eval.* loggers captured at configured level - uvicorn access/error logs also routed to same handlers - File always captures DEBUG; console level controlled by --log-level arg - Added --log-level and --log-file CLI arguments to webmain.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
99
webmain.py
99
webmain.py
@@ -5,13 +5,73 @@ and the same runs/ artifacts. Example:
|
|||||||
|
|
||||||
python webmain.py
|
python webmain.py
|
||||||
python webmain.py --host 0.0.0.0 --port 8800
|
python webmain.py --host 0.0.0.0 --port 8800
|
||||||
|
python webmain.py --host 0.0.0.0 --port 8800 --log-level debug
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import uvicorn
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
|
||||||
|
def _build_log_config(log_file: Path, level: str) -> dict:
|
||||||
|
"""Build a uvicorn-compatible logging config dict.
|
||||||
|
|
||||||
|
Writes to both stderr (console) and a rotating daily log file.
|
||||||
|
All webapp.* and rag_eval.* loggers inherit from root so every
|
||||||
|
logger.info() call in the API routes is captured.
|
||||||
|
"""
|
||||||
|
level_upper = level.upper()
|
||||||
|
return {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"detailed": {
|
||||||
|
"format": "%(asctime)s %(levelname)-8s %(name)s %(message)s",
|
||||||
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||||
|
},
|
||||||
|
"console": {
|
||||||
|
"format": "%(asctime)s %(levelname)-8s %(name)-30s %(message)s",
|
||||||
|
"datefmt": "%H:%M:%S",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"stream": "ext://sys.stderr",
|
||||||
|
"formatter": "console",
|
||||||
|
"level": level_upper,
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"class": "logging.handlers.RotatingFileHandler",
|
||||||
|
"filename": str(log_file),
|
||||||
|
"maxBytes": 50 * 1024 * 1024, # 50 MB per file
|
||||||
|
"backupCount": 7, # keep 7 rotated files
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"formatter": "detailed",
|
||||||
|
"level": "DEBUG", # file always captures everything
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
# Our application loggers — detailed level
|
||||||
|
"webapp": {"handlers": ["console", "file"], "level": level_upper, "propagate": False},
|
||||||
|
"rag_eval": {"handlers": ["console", "file"], "level": level_upper, "propagate": False},
|
||||||
|
# uvicorn access log — captured to file, shown on console
|
||||||
|
"uvicorn.access": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
|
||||||
|
"uvicorn.error": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
|
||||||
|
"uvicorn": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"handlers": ["console", "file"],
|
||||||
|
"level": "WARNING", # suppress noisy third-party libs at WARNING
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
@@ -24,17 +84,52 @@ def parse_args() -> argparse.Namespace:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Enable auto-reload for local development.",
|
help="Enable auto-reload for local development.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--log-level",
|
||||||
|
default="info",
|
||||||
|
choices=["debug", "info", "warning", "error"],
|
||||||
|
help="Console log level (default: info). File always captures DEBUG.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--log-file",
|
||||||
|
default=None,
|
||||||
|
help="Log file path (default: logs/server_YYYY-MM-DD.log).",
|
||||||
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Start uvicorn with the configured application."""
|
"""Start uvicorn with the configured application and logging."""
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
|
# Resolve log file path
|
||||||
|
logs_dir = REPO_ROOT / "logs"
|
||||||
|
logs_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
if args.log_file:
|
||||||
|
log_file = Path(args.log_file)
|
||||||
|
else:
|
||||||
|
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
log_file = logs_dir / f"server_{date_str}.log"
|
||||||
|
|
||||||
|
log_config = _build_log_config(log_file, args.log_level)
|
||||||
|
|
||||||
|
# Apply config before uvicorn starts so our loggers are ready immediately
|
||||||
|
logging.config.dictConfig(log_config)
|
||||||
|
|
||||||
|
logger = logging.getLogger("webapp.server")
|
||||||
|
logger.info(
|
||||||
|
"Starting RAGAS Console host=%s port=%d log_level=%s log_file=%s",
|
||||||
|
args.host, args.port, args.log_level, log_file,
|
||||||
|
)
|
||||||
|
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"webapp.server:app",
|
"webapp.server:app",
|
||||||
host=args.host,
|
host=args.host,
|
||||||
port=args.port,
|
port=args.port,
|
||||||
reload=args.reload,
|
reload=args.reload,
|
||||||
|
log_config=log_config, # hand our config to uvicorn so it uses same handlers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user