SHAFI commited on
Commit
f1f73eb
Β·
1 Parent(s): ae9d312

imporoved observability

Browse files
Files changed (2) hide show
  1. app/main.py +16 -0
  2. 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 uses the AlignedColorFormatter.
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
- This replaces the old:
147
- import logging
148
- logger = logging.getLogger(__name__)
 
 
 
149
 
150
- Why this is safe:
151
- Python's logging module is a global registry. Calling get_logger()
152
- for the same `name` twice returns the same Logger object β€” no
153
- duplicate handlers are added because we check first.
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
- # Only add our handler if none exists yet.
165
- # This prevents duplicate output if get_logger is called multiple times
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