Skip to content

Commit ff410c4

Browse files
committed
Introduce loguru logging with full stdlib and click interception
1 parent c1807fd commit ff410c4

3 files changed

Lines changed: 90 additions & 4 deletions

File tree

ml_rest_api/app.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import os
44
import warnings
55
from logging import Logger, getLogger
6-
import logging.config
76
from flask import Flask
87
from flask_wtf import CSRFProtect # pylint: disable=unused-import # type: ignore
8+
from ml_rest_api.logging_setup import setup_logging
99
from ml_rest_api.settings import get_value
1010
from ml_rest_api.ml_trained_model.wrapper import trained_model_wrapper
1111
from ml_rest_api.api.restx import blueprint
@@ -65,9 +65,7 @@ def main() -> None:
6565

6666

6767
APP = Flask(__name__)
68-
logging.config.fileConfig(
69-
os.path.normpath(os.path.join(os.path.dirname(__file__), "../logging.conf"))
70-
)
68+
setup_logging()
7169
log: Logger = getLogger(__name__)
7270
initialize_app(APP)
7371

ml_rest_api/logging_setup.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Loguru-based logging setup that intercepts all stdlib logging."""
2+
3+
import inspect
4+
import logging
5+
import sys
6+
from typing import Any
7+
import click
8+
from loguru import logger
9+
10+
11+
class InterceptHandler(logging.Handler):
12+
"""Intercepts stdlib logging calls and redirects them to loguru."""
13+
14+
def emit(self, record: logging.LogRecord) -> None:
15+
try:
16+
level: str | int = logger.level(record.levelname).name
17+
except ValueError:
18+
level = record.levelno
19+
20+
frame, depth = inspect.currentframe(), 0
21+
for _ in range(6):
22+
if frame is not None:
23+
frame = frame.f_back
24+
depth += 1
25+
while frame and frame.f_code.co_filename == logging.__file__:
26+
frame = frame.f_back # type: ignore[assignment]
27+
depth += 1
28+
29+
for line in record.getMessage().splitlines():
30+
logger.opt(depth=depth, exception=record.exc_info).log(level, line)
31+
32+
33+
def setup_logging(level: str = "DEBUG") -> None:
34+
"""Configure loguru and intercept all stdlib logging."""
35+
logger.remove()
36+
logger.add(
37+
sys.stdout,
38+
level=level,
39+
format=(
40+
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
41+
"<level>{level: <8}</level> | "
42+
"<cyan>{name}</cyan>:<cyan>{line}</cyan> - "
43+
"<level>{message}</level>"
44+
),
45+
colorize=True,
46+
)
47+
# Replace root logger handler — all child loggers propagate here by default
48+
logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
49+
# Clear handlers on existing child loggers and ensure they propagate to root
50+
for name in logging.root.manager.loggerDict: # pylint: disable=no-member
51+
existing = logging.getLogger(name)
52+
existing.handlers.clear()
53+
existing.propagate = True
54+
# Intercept click.echo (used by Flask/Werkzeug for * startup lines)
55+
_original_echo = click.echo
56+
57+
def _echo_to_loguru(
58+
message: Any = None,
59+
file: Any = None,
60+
nl: bool = True,
61+
err: bool = False,
62+
color: Any = None,
63+
) -> None:
64+
if message is not None and file is None and not err:
65+
logger.opt(depth=1).info(str(message))
66+
else:
67+
_original_echo(message, file=file, nl=nl, err=err, color=color)
68+
69+
click.echo = _echo_to_loguru # type: ignore[assignment]
70+
71+
# Also intercept click.secho (used by Werkzeug for * Running on lines)
72+
_original_secho = click.secho
73+
74+
def _secho_to_loguru(
75+
message: Any = None,
76+
file: Any = None,
77+
nl: bool = True,
78+
err: bool = False,
79+
color: Any = None,
80+
**styles: Any,
81+
) -> None:
82+
if message is not None and file is None and not err:
83+
logger.opt(depth=1).info(str(message))
84+
else:
85+
_original_secho(message, file=file, nl=nl, err=err, color=color, **styles)
86+
87+
click.secho = _secho_to_loguru # type: ignore[assignment]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
setuptools>=82.0.0
2+
loguru==0.7.3
23
Flask==3.1.3
34
flask-restx==1.3.2
45
Flask-WTF==1.2.2

0 commit comments

Comments
 (0)