Backend logging: - Centralized JSON logging config with LOG_LEVEL env var - Request logging middleware: user, method, path, status, duration, request_id - Fixed all 9 silent except:pass blocks to log warnings with tracebacks - Celery workers use same structured JSON format Infrastructure: - Loki 3.3.2 for log storage (30-day retention) - Promtail 3.3.2 for Docker container log shipping - Grafana 10.3.1 with auto-provisioned Loki datasource - Grafana on port 3002 (admin/pedshub_grafana) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""Centralized structured JSON logging for backend and Celery workers."""
|
|
import logging
|
|
import logging.config
|
|
import sys
|
|
|
|
|
|
def setup_logging(level: str = "INFO"):
|
|
"""Configure all Python loggers to output structured JSON to stdout."""
|
|
try:
|
|
from pythonjsonlogger.json import JsonFormatter
|
|
formatter_class = "pythonjsonlogger.json.JsonFormatter"
|
|
formatter_fmt = "%(asctime)s %(name)s %(levelname)s %(message)s"
|
|
except ImportError:
|
|
# Fallback if python-json-logger not installed
|
|
formatter_class = "logging.Formatter"
|
|
formatter_fmt = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
|
|
|
config = {
|
|
"version": 1,
|
|
"disable_existing_loggers": False,
|
|
"formatters": {
|
|
"json": {
|
|
"class": formatter_class,
|
|
"format": formatter_fmt,
|
|
"datefmt": "%Y-%m-%dT%H:%M:%S",
|
|
},
|
|
},
|
|
"handlers": {
|
|
"console": {
|
|
"class": "logging.StreamHandler",
|
|
"stream": "ext://sys.stdout",
|
|
"formatter": "json",
|
|
},
|
|
},
|
|
"root": {
|
|
"level": level.upper(),
|
|
"handlers": ["console"],
|
|
},
|
|
"loggers": {
|
|
# Quieten noisy libraries
|
|
"uvicorn.access": {"level": "WARNING"},
|
|
"uvicorn.error": {"level": level.upper()},
|
|
"chromadb": {"level": "WARNING"},
|
|
"httpcore": {"level": "WARNING"},
|
|
"httpx": {"level": "WARNING"},
|
|
"celery": {"level": level.upper()},
|
|
"sqlalchemy.engine": {"level": "WARNING"},
|
|
},
|
|
}
|
|
|
|
logging.config.dictConfig(config)
|
|
logging.getLogger(__name__).debug("Logging configured: level=%s", level)
|