Skip to content

Commit 07c98d7

Browse files
committed
feat: enhance logging configuration with default values and shared processors
1 parent 4f0dadc commit 07c98d7

1 file changed

Lines changed: 53 additions & 63 deletions

File tree

app/services/logging.py

Lines changed: 53 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,90 +7,80 @@
77
import structlog
88
from whenever._whenever import Instant
99

10-
11-
def _configure_logger() -> structlog.BoundLogger:
12-
"""
13-
Configures and returns a structlog logger with a rotating file handler.
14-
15-
The logger is configured using environment variables for path, file size,
16-
and backup count. It formats logs as JSON.
17-
"""
18-
log_dir = Path(os.environ.get("ROTOGER_LOG_PATH", "."))
10+
# ---------------------------------------------------------------------------
11+
# Constants / defaults
12+
# ---------------------------------------------------------------------------
13+
_DEFAULT_LOG_PATH = "."
14+
_DEFAULT_MAX_BYTES = 10 * 1024 * 1024 # 10 MiB
15+
_DEFAULT_BACKUP_COUNT = 5
16+
17+
# Generic registry: add any stdlib logger name + its desired level here.
18+
_STDLIB_LOGGERS: dict[str, int] = {
19+
"root": logging.INFO,
20+
"uvicorn": logging.INFO,
21+
"sqlalchemy": logging.WARNING,
22+
}
23+
24+
# Shared processor chain used by both structlog and the stdlib formatter.
25+
_SHARED_PROCESSORS: list[structlog.types.Processor] = [
26+
structlog.contextvars.merge_contextvars,
27+
structlog.stdlib.add_log_level,
28+
structlog.stdlib.add_logger_name,
29+
structlog.stdlib.PositionalArgumentsFormatter(),
30+
structlog.processors.TimeStamper(fmt="iso", utc=True),
31+
structlog.processors.format_exc_info,
32+
]
33+
34+
35+
def _build_handler() -> RotatingFileHandler:
36+
log_dir = Path(os.getenv("ROTOGER_LOG_PATH", _DEFAULT_LOG_PATH))
1937
log_dir.mkdir(parents=True, exist_ok=True)
20-
log_date = Instant.now().py_datetime().strftime("%Y%m%d")
21-
log_path = log_dir / f"{log_date}_{os.getpid()}.log"
22-
23-
# Use int() to ensure env var values are correctly typed
24-
max_bytes = int(os.environ.get("ROTOGER_LOG_MAX_BYTES", 10 * 1024 * 1024))
25-
backup_count = int(os.environ.get("ROTOGER_LOG_BACKUP_COUNT", 5))
38+
log_path = log_dir / f"{Instant.now().py_datetime().strftime('%Y%m%d')}_{os.getpid()}.log"
2639

2740
handler = RotatingFileHandler(
2841
filename=log_path,
29-
maxBytes=max_bytes,
30-
backupCount=backup_count,
42+
maxBytes=int(os.getenv("ROTOGER_LOG_MAX_BYTES", _DEFAULT_MAX_BYTES)),
43+
backupCount=int(os.getenv("ROTOGER_LOG_BACKUP_COUNT", _DEFAULT_BACKUP_COUNT)),
3144
encoding="utf-8",
32-
3345
)
46+
handler.setFormatter(
47+
structlog.stdlib.ProcessorFormatter(
48+
foreign_pre_chain=_SHARED_PROCESSORS,
49+
processor=structlog.processors.JSONRenderer(
50+
serializer=lambda *a, **kw: orjson.dumps(*a, **kw).decode()
51+
),
52+
)
53+
)
54+
return handler
55+
3456

35-
# Use structlog's standard library integration
57+
def _configure_logger() -> structlog.BoundLogger:
58+
"""Configure structlog + stdlib loggers and return a bound logger."""
3659
structlog.configure(
3760
processors=[
38-
structlog.contextvars.merge_contextvars,
39-
structlog.stdlib.add_log_level,
40-
structlog.stdlib.PositionalArgumentsFormatter(),
41-
structlog.processors.TimeStamper(fmt="iso", utc=True),
42-
structlog.processors.format_exc_info,
61+
*_SHARED_PROCESSORS,
4362
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
44-
# structlog.stdlib.add_logger_name,
4563
],
4664
logger_factory=structlog.stdlib.LoggerFactory(),
4765
wrapper_class=structlog.stdlib.BoundLogger,
4866
cache_logger_on_first_use=True,
4967
)
5068

51-
# Configure the underlying standard logger
52-
formatter = structlog.stdlib.ProcessorFormatter(
53-
# These run after the processors defined in structlog.configure
54-
foreign_pre_chain=[
55-
structlog.contextvars.merge_contextvars,
56-
structlog.stdlib.add_log_level,
57-
structlog.stdlib.PositionalArgumentsFormatter(),
58-
structlog.processors.TimeStamper(fmt="iso", utc=True),
59-
structlog.processors.format_exc_info,
60-
structlog.stdlib.add_logger_name,
61-
],
62-
processor=structlog.processors.JSONRenderer(
63-
serializer=lambda *args, **kwargs: orjson.dumps(*args, **kwargs).decode()
64-
),
65-
)
66-
handler.setFormatter(formatter)
67-
root_logger = logging.getLogger("root") # Get the root logger
68-
root_logger.addHandler(handler)
69-
root_logger.propagate = False # Prevent logs from being propagated to the root logger
70-
root_logger.setLevel(logging.INFO)
71-
72-
uvicorn_logger = logging.getLogger("uvicorn") # Get the root logger
73-
uvicorn_logger.addHandler(handler)
74-
uvicorn_logger.propagate = False # Prevent logs from being propagated to the root logger
75-
uvicorn_logger.setLevel(logging.INFO)
76-
77-
sa_logger = logging.getLogger("sqlalchemy") # Get the root logger
78-
sa_logger.addHandler(handler)
79-
sa_logger.propagate = False # Prevent logs from being propagated to the root logger
80-
sa_logger.setLevel(logging.WARNING)
81-
82-
# Set SQLAlchemy engine logger level specifically if needed
83-
# logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
84-
return structlog.get_logger()
69+
handler = _build_handler()
70+
71+
for name, level in _STDLIB_LOGGERS.items():
72+
logger = logging.getLogger(name)
73+
logger.addHandler(handler)
74+
logger.propagate = False
75+
logger.setLevel(level)
8576

77+
return structlog.get_logger()
8678

8779

88-
# Module-level singleton instance
80+
# Module-level singleton
8981
_logger_instance = _configure_logger()
9082

9183

9284
def get_logger() -> structlog.BoundLogger:
93-
"""
94-
Returns the configured singleton logger instance.
95-
"""
85+
"""Return the configured singleton logger instance."""
9686
return _logger_instance

0 commit comments

Comments
 (0)