60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
|
|
"""
|
||
|
|
Structured logging configuration for CBPOA backend.
|
||
|
|
|
||
|
|
- LOG_LEVEL: DEBUG, INFO, WARNING, ERROR, CRITICAL (default INFO)
|
||
|
|
- LOG_FORMAT: "json" for production, "text" for human-readable dev output (default text)
|
||
|
|
"""
|
||
|
|
import logging
|
||
|
|
import json
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
|
||
|
|
class JSONFormatter(logging.Formatter):
|
||
|
|
"""Emit structured JSON log lines for production."""
|
||
|
|
|
||
|
|
def format(self, record: logging.LogRecord) -> str:
|
||
|
|
log_entry = {
|
||
|
|
"timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
|
||
|
|
"level": record.levelname,
|
||
|
|
"logger": record.name,
|
||
|
|
"message": record.getMessage(),
|
||
|
|
}
|
||
|
|
if record.exc_info and record.exc_info[1]:
|
||
|
|
log_entry["exception"] = self.formatException(record.exc_info)
|
||
|
|
# Include extra fields (request_id, method, path, etc.)
|
||
|
|
for key in ("request_id", "method", "path", "status_code", "duration_ms"):
|
||
|
|
val = getattr(record, key, None)
|
||
|
|
if val is not None:
|
||
|
|
log_entry[key] = val
|
||
|
|
return json.dumps(log_entry, ensure_ascii=False)
|
||
|
|
|
||
|
|
|
||
|
|
def setup_logging() -> None:
|
||
|
|
"""Configure root logger based on environment variables."""
|
||
|
|
level_name = os.getenv("LOG_LEVEL", "INFO").upper()
|
||
|
|
level = getattr(logging, level_name, logging.INFO)
|
||
|
|
|
||
|
|
log_format = os.getenv("LOG_FORMAT", "text").lower()
|
||
|
|
|
||
|
|
handler = logging.StreamHandler(sys.stdout)
|
||
|
|
|
||
|
|
if log_format == "json":
|
||
|
|
handler.setFormatter(JSONFormatter())
|
||
|
|
else:
|
||
|
|
handler.setFormatter(
|
||
|
|
logging.Formatter(
|
||
|
|
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
||
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
root = logging.getLogger()
|
||
|
|
root.handlers.clear()
|
||
|
|
root.addHandler(handler)
|
||
|
|
root.setLevel(level)
|
||
|
|
|
||
|
|
# Quiet noisy third-party loggers
|
||
|
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|