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