SHAFI commited on
Commit Β·
f1f73eb
1
Parent(s): ae9d312
imporoved observability
Browse files- app/main.py +16 -0
- app/utils/custom_logger.py +18 -27
app/main.py
CHANGED
|
@@ -1,8 +1,24 @@
|
|
| 1 |
import asyncio
|
| 2 |
import sys
|
|
|
|
| 3 |
from fastapi import FastAPI
|
| 4 |
import warnings
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
# Windows-specific fix for Playwright + asyncio subprocesses
|
| 8 |
if sys.platform == 'win32':
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import sys
|
| 3 |
+
import logging
|
| 4 |
from fastapi import FastAPI
|
| 5 |
import warnings
|
| 6 |
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
+
from app.utils.custom_logger import AlignedColorFormatter
|
| 8 |
+
|
| 9 |
+
# ββ Phase 23: Root Logger Configuration ββββββββββββββββββββββββββββββββββββββ
|
| 10 |
+
# Configure the ROOT logger before FastAPI and Uvicorn initialize.
|
| 11 |
+
# Uvicorn resets loggers when it starts, so by configuring root early and
|
| 12 |
+
# letting all other loggers propagate up to it, we ensure every log line
|
| 13 |
+
# (including Uvicorn's access logs) uses our strict AlignedColorFormatter
|
| 14 |
+
# and streams to stderr (for Hugging Face visibility).
|
| 15 |
+
root_logger = logging.getLogger()
|
| 16 |
+
if not root_logger.handlers:
|
| 17 |
+
handler = logging.StreamHandler(sys.stderr)
|
| 18 |
+
handler.setFormatter(AlignedColorFormatter())
|
| 19 |
+
root_logger.addHandler(handler)
|
| 20 |
+
root_logger.setLevel(logging.INFO)
|
| 21 |
+
|
| 22 |
|
| 23 |
# Windows-specific fix for Playwright + asyncio subprocesses
|
| 24 |
if sys.platform == 'win32':
|
app/utils/custom_logger.py
CHANGED
|
@@ -137,47 +137,38 @@ class AlignedColorFormatter(logging.Formatter):
|
|
| 137 |
|
| 138 |
def get_logger(name: str) -> logging.Logger:
|
| 139 |
"""
|
| 140 |
-
Get a logger that
|
| 141 |
|
| 142 |
How to use this in any module:
|
| 143 |
from app.utils.custom_logger import get_logger
|
| 144 |
logger = get_logger(__name__)
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
-
Why
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
|
| 155 |
Args:
|
| 156 |
name: Normally pass __name__ (the module's full dotted path).
|
| 157 |
-
This becomes Column 3 in the log output.
|
| 158 |
|
| 159 |
Returns:
|
| 160 |
A configured Logger instance ready to use.
|
| 161 |
"""
|
| 162 |
log = logging.getLogger(name)
|
| 163 |
|
| 164 |
-
#
|
| 165 |
-
#
|
| 166 |
-
# for the same module (e.g., on server reload).
|
| 167 |
-
if not log.handlers:
|
| 168 |
-
# ββ Why sys.stderr and not sys.stdout? ββββββββββββββββββββββββββββββββ
|
| 169 |
-
# Hugging Face Spaces (Docker containers) capture sys.stderr for the
|
| 170 |
-
# terminal log viewer, not sys.stdout. Python's logging.basicConfig also
|
| 171 |
-
# defaults to stderr. Switching to stdout broke log visibility on HF.
|
| 172 |
-
# PYTHONUNBUFFERED=1 (set in HF Secrets) ensures each line appears
|
| 173 |
-
# immediately without waiting for the buffer to fill.
|
| 174 |
-
handler = logging.StreamHandler(sys.stderr)
|
| 175 |
-
handler.setFormatter(AlignedColorFormatter())
|
| 176 |
-
log.addHandler(handler)
|
| 177 |
-
log.propagate = False # Stop double-printing via the root logger
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
# Set to DEBUG so all levels pass through; the root logger's level still
|
| 181 |
-
# applies above us. Callers can override: logger.setLevel(logging.WARNING).
|
| 182 |
log.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
return log
|
|
|
|
| 137 |
|
| 138 |
def get_logger(name: str) -> logging.Logger:
|
| 139 |
"""
|
| 140 |
+
Get a logger that flows into the root logger configured in main.py.
|
| 141 |
|
| 142 |
How to use this in any module:
|
| 143 |
from app.utils.custom_logger import get_logger
|
| 144 |
logger = get_logger(__name__)
|
| 145 |
|
| 146 |
+
Why we use propagate=True here (and NOT add our own handler):
|
| 147 |
+
Uvicorn calls logging.config.dictConfig() at startup, which can
|
| 148 |
+
silently orphan any handlers we attach to individual module loggers.
|
| 149 |
+
Instead, we configure ONE root handler in main.py (before uvicorn
|
| 150 |
+
starts), and let every module logger propagate its messages up to it.
|
| 151 |
+
This is the standard production pattern for FastAPI + uvicorn.
|
| 152 |
|
| 153 |
+
Why we do NOT call addHandler() here:
|
| 154 |
+
If a module logger has its own handler AND propagate=True, every
|
| 155 |
+
log call would print TWICE β once from the module handler and once
|
| 156 |
+
from the root handler. No handlers here = no duplicates.
|
| 157 |
|
| 158 |
Args:
|
| 159 |
name: Normally pass __name__ (the module's full dotted path).
|
|
|
|
| 160 |
|
| 161 |
Returns:
|
| 162 |
A configured Logger instance ready to use.
|
| 163 |
"""
|
| 164 |
log = logging.getLogger(name)
|
| 165 |
|
| 166 |
+
# Set to DEBUG so all levels pass through to the root logger.
|
| 167 |
+
# The root logger's level and handler decide what is actually printed.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
log.setLevel(logging.DEBUG)
|
| 169 |
+
|
| 170 |
+
# Propagate to root: root handler (set up in main.py) does the printing.
|
| 171 |
+
# DO NOT add a handler here β that would cause duplicate log lines.
|
| 172 |
+
log.propagate = True
|
| 173 |
+
|
| 174 |
return log
|