Spaces:
Running
Running
| import sys | |
| import os | |
| from loguru import logger | |
| from app.core.config import settings | |
| from pathlib import Path | |
| from app.core.request_id import get_request_id | |
| # ββ 1. Remove default loguru handler ββββββββββββββββββββββββββββββββββββββββββ | |
| logger.remove() | |
| # ββ 2. Inject request_id into every log record βββββββββββββββββββββββββββββββ | |
| def _inject_request_id(record): | |
| req_id = get_request_id() | |
| record["extra"]["request_id"] = req_id if req_id else "SYSTEM" | |
| logger.configure(patcher=_inject_request_id) | |
| import logging | |
| logging.getLogger("uvicorn.access").disabled = True | |
| # ββ 3. Log directory ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| log_dir = Path("app_logs") | |
| log_dir.mkdir(parents=True, exist_ok=True) | |
| # ββ 4. Console β structured JSON for production, colorized text for local βββββ | |
| IS_PROD = os.environ.get("ENVIRONMENT", "development").lower() in ("production", "prod") | |
| if IS_PROD: | |
| # Production: pure JSON, machine-readable β timestamp in 12-hour AM/PM UTC | |
| logger.add( | |
| sys.stdout, | |
| format="{message}", | |
| serialize=True, # emits {"text":..., "record":{...}} JSON lines | |
| level=settings.LOG_LEVEL, | |
| enqueue=True, | |
| ) | |
| else: | |
| # Local dev: human-readable with color, 12-hour AM/PM format | |
| logger.add( | |
| sys.stdout, | |
| format=( | |
| "<green>{time:MM-DD-YYYY hh:mm:ss A}</green> | " | |
| "<level>{level:<8}</level> | " | |
| "<cyan>[{extra[request_id]}]</cyan> " | |
| "<white>{name}:{function}:{line}</white> β {message}" | |
| ), | |
| colorize=True, | |
| level=settings.LOG_LEVEL, | |
| enqueue=True, | |
| ) | |
| # ββ 5. File logger β 12-hour AM/PM timestamp βββββββββββββββββββββββββββββββββ | |
| logger.add( | |
| "app_logs/api_{time:YYYY-MM-DD}.log", | |
| rotation="00:00", | |
| retention="30 days", | |
| compression="zip", | |
| level="INFO", | |
| # hh = 12-hour clock, A = AM/PM | |
| format="{time:MM-DD-YYYY hh:mm:ss A} | {level:<8} | [{extra[request_id]}] {name}:{function}:{line} β {message}", | |
| enqueue=True, | |
| ) | |
| # ββ 6. New Relic sink (optional β never crashes the app) ββββββββββββββββββββββ | |
| _NR_KEY = os.environ.get("NEW_RELIC_LICENSE_KEY", "") | |
| if _NR_KEY: | |
| try: | |
| import newrelic.agent | |
| try: | |
| newrelic.agent.initialize() | |
| except Exception: | |
| pass | |
| def _newrelic_sink(message): | |
| record = message.record | |
| try: | |
| newrelic.agent.record_log_event( | |
| message=record["message"], | |
| level=record["level"].name, | |
| timestamp=int(record["time"].timestamp() * 1000), | |
| attributes={ | |
| "request_id": record["extra"].get("request_id", "SYSTEM"), | |
| "method": record["extra"].get("method", ""), | |
| "path": record["extra"].get("path", ""), | |
| "status": record["extra"].get("status", ""), | |
| "duration_ms": record["extra"].get("duration_ms", ""), | |
| "logger": record["name"], | |
| "function": record["function"], | |
| "line": record["line"], | |
| } | |
| ) | |
| except Exception: | |
| pass # Cloud logging must never affect the app | |
| logger.add(_newrelic_sink, level="INFO", enqueue=True) | |
| except ImportError: | |
| pass | |
| except Exception: | |
| pass | |