Spaces:
Running
Running
File size: 3,883 Bytes
aa27d2d fe56754 aa27d2d 817ad83 aa27d2d 817ad83 aa27d2d 817ad83 aa27d2d 09cc5a2 817ad83 aa27d2d 817ad83 aa27d2d 817ad83 aa27d2d 817ad83 aa27d2d fe56754 817ad83 c041d0d 942a467 fe56754 942a467 fe56754 942a467 817ad83 942a467 817ad83 fe56754 942a467 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 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
|